diff --git a/copyright.md b/copyright.md index d764ff95bb..61357b1dcc 100644 --- a/copyright.md +++ b/copyright.md @@ -3,7 +3,7 @@ Copyright notice Version 5.0, October 2024 -Copyright 2005-2024 Qlik +Copyright 2005-2026 Qlik The contents of this open source project are subject to the terms of the Apache 2.0 open source license available at http://www.opensource.org/licenses/apache-2.0 diff --git a/mvnw b/mvnw index aa09908bea..b8d231c9f6 100755 --- a/mvnw +++ b/mvnw @@ -28,7 +28,7 @@ # Optional ENV vars # ----------------- # MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use +# e.g., to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- diff --git a/mvnw.cmd b/mvnw.cmd index 1088545e13..fe66b73afa 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -27,7 +27,7 @@ @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use +@REM e.g., to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @@ -81,7 +81,7 @@ goto error :init -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Find the project base dir, i.e., the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/AwsAuthenticator.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/AwsAuthenticator.java index 94215304db..d5b448f9e3 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/AwsAuthenticator.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/AwsAuthenticator.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto; import org.restlet.Context; @@ -18,19 +17,16 @@ /** * Authenticator supporting the {@link ChallengeScheme#HTTP_AWS_S3} scheme. - * + * * @author Jean-Philippe Steinmetz */ public class AwsAuthenticator extends ChallengeAuthenticator { /** * Creates a new HttpAwsS3Authenticator instance. - * - * @param context - * The context - * @param optional - * Indicates if the authentication success is optional - * @param realm - * The authentication realm + * + * @param context The context + * @param optional Indicates if the authentication success is optional + * @param realm The authentication realm */ public AwsAuthenticator(Context context, boolean optional, String realm) { this(context, optional, realm, new AwsVerifier(null)); @@ -38,38 +34,30 @@ public AwsAuthenticator(Context context, boolean optional, String realm) { /** * Creates a new HttpAwsS3Authenticator instance. - * - * @param context - * The context - * @param optional - * Indicates if the authentication success is optional - * @param realm - * The authentication realm + * + * @param context The context + * @param optional Indicates if the authentication success is optional + * @param realm The authentication realm * @param verifier */ - public AwsAuthenticator(Context context, boolean optional, String realm, - Verifier verifier) { + public AwsAuthenticator(Context context, boolean optional, String realm, Verifier verifier) { super(context, optional, ChallengeScheme.HTTP_AWS_S3, realm, verifier); } /** * Creates a new HttpAwsS3Authenticator instance. - * - * @param context - * The context - * @param realm - * The authentication realm + * + * @param context The context + * @param realm The authentication realm */ public AwsAuthenticator(Context context, String realm) { this(context, false, realm); } /** - * Returns the maximum age of a request, in milliseconds, before it is - * considered stale. - *

- * A negative or zero value indicates no age restriction. The default value - * is 15 minutes. + * Returns the maximum age of a request, in milliseconds, before it is considered stale. + * + *

A negative or zero value indicates no age restriction. The default value is 15 minutes. */ public long getMaxRequestAge() { return getVerifier().getMaxRequestAge(); @@ -81,9 +69,9 @@ public AwsVerifier getVerifier() { } /** - * Returns the secret verifier that will be wrapped by the real verifier - * supporting all the HTTP AWS verifications. - * + * Returns the secret verifier that will be wrapped by the real verifier supporting all the HTTP + * AWS verifications. + * * @return the local wrapped verifier */ public LocalVerifier getWrappedVerifier() { @@ -91,35 +79,30 @@ public LocalVerifier getWrappedVerifier() { } /** - * Sets the maximum age of a request, in milliseconds, before it is - * considered stale. - *

- * A negative or zero value indicates no age restriction. The default value - * is 15 minutes. + * Sets the maximum age of a request, in milliseconds, before it is considered stale. + * + *

A negative or zero value indicates no age restriction. The default value is 15 minutes. */ public void setMaxRequestAge(long value) { getVerifier().setMaxRequestAge(value); } /** - * Sets the internal verifier. In general you shouldn't replace it but - * instead set the {@code wrappedVerifier} via the - * {@link #setWrappedVerifier(LocalVerifier)} method. + * Sets the internal verifier. In general, you shouldn't replace it but instead set the {@code + * wrappedVerifier} via the {@link #setWrappedVerifier(LocalVerifier)} method. */ @Override public void setVerifier(Verifier verifier) { - if (!(verifier instanceof AwsVerifier)) - throw new IllegalArgumentException(); + if (!(verifier instanceof AwsVerifier)) throw new IllegalArgumentException(); super.setVerifier(verifier); } /** - * Sets the secret verifier that will be wrapped by the real verifier - * supporting all the HTTP AWS verifications. - * - * @param verifier - * The local verifier to wrap + * Sets the secret verifier that will be wrapped by the real verifier supporting all the HTTP + * AWS verifications. + * + * @param verifier The local verifier to wrap */ public void setWrappedVerifier(LocalVerifier verifier) { getVerifier().setWrappedVerifier(verifier); diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/CookieAuthenticator.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/CookieAuthenticator.java index 3a2de458cc..cb38076388 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/CookieAuthenticator.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/CookieAuthenticator.java @@ -1,18 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto; import java.security.GeneralSecurityException; import java.util.Base64; import java.util.logging.Level; - import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -28,33 +26,29 @@ import org.restlet.security.ChallengeAuthenticator; /** - * Challenge authenticator based on browser cookies. This is useful when the web - * application requires a finer grained control on the login and logout process - * and can't rely solely on standard schemes such as - * {@link ChallengeScheme#HTTP_BASIC}.
+ * Challenge authenticator based on browser cookies. This is useful when the web application + * requires a finer grained control on the login and logout process and can't rely solely on + * standard schemes such as {@link ChallengeScheme#HTTP_BASIC}.
*
- * Login can be automatically handled by intercepting HTTP POST calls to the - * {@link #getLoginPath()} URI. The request entity should contain an HTML form - * with two fields, the first one named {@link #getIdentifierFormName()} and the - * second one named {@link #getSecretFormName()}.
+ * Login can be automatically handled by intercepting HTTP POST calls to the {@link #getLoginPath()} + * URI. The request entity should contain an HTML form with two fields, the first one named {@link + * #getIdentifierFormName()} and the second one named {@link #getSecretFormName()}.
*
- * Logout can be automatically handled as well by intercepting HTTP GET or POST - * calls to the {@link #getLogoutPath()} URI.
+ * Logout can be automatically handled as well by intercepting HTTP GET or POST calls to the {@link + * #getLogoutPath()} URI.
*
- * After login or logout, the user's browser can be redirected to the URI - * provided in a query parameter named by {@link #getRedirectQueryName()}.
+ * After login or logout, the user's browser can be redirected to the URI provided in a query + * parameter named by {@link #getRedirectQueryName()}.
*
- * When the credentials are missing or stale, the - * {@link #challenge(Response, boolean)} method is invoked by the parent class, - * and its default behavior is to redirect the user's browser to the - * {@link #getLoginFormPath()} URI, adding the URI of the target resource as a - * query parameter of name {@link #getRedirectQueryName()}.
+ * When the credentials are missing or stale, the {@link #challenge(Response, boolean)} method is + * invoked by the parent class, and its default behavior is to redirect the user's browser to the + * {@link #getLoginFormPath()} URI, adding the URI of the target resource as a query parameter of + * name {@link #getRedirectQueryName()}.
*
- * Note that credentials, both identifier and secret, are stored in a cookie in - * an encrypted manner. The default encryption algorithm is AES but can be - * changed with {@link #setEncryptAlgorithm(String)}. It is also strongly - * recommended to - * + * Note that credentials, both identifier and secret, are stored in a cookie in an encrypted manner. + * The default encryption algorithm is AES but can be changed with {@link + * #setEncryptAlgorithm(String)}. It is also strongly recommended to + * * @author Remi Dewitte * @author Jerome Louvel */ @@ -66,10 +60,7 @@ public class CookieAuthenticator extends ChallengeAuthenticator { /** The name of the algorithm used to encrypt the log info cookie value. */ private volatile String encryptAlgorithm; - /** - * The secret key for the algorithm used to encrypt the log info cookie - * value. - */ + /** The secret key for the algorithm used to encrypt the log info cookie value. */ private volatile byte[] encryptSecretKey; /** The name of the HTML login form field containing the identifier. */ @@ -94,8 +85,8 @@ public class CookieAuthenticator extends ChallengeAuthenticator { private volatile int maxCookieAge; /** - * The name of the query parameter containing the URI to redirect the - * browser to after login or logout. + * The name of the query parameter containing the URI to redirect the browser to after login or + * logout. */ private volatile String redirectQueryName; @@ -104,19 +95,15 @@ public class CookieAuthenticator extends ChallengeAuthenticator { /** * Constructor. Use the {@link ChallengeScheme#HTTP_COOKIE} pseudo-scheme. - * - * @param context - * The parent context. - * @param optional - * Indicates if this authenticator is optional so alternative - * authenticators down the chain can be attempted. - * @param realm - * The name of the security realm. - * @param encryptSecretKey - * The secret key used to encrypt the cookie value. - */ - public CookieAuthenticator(Context context, boolean optional, String realm, - byte[] encryptSecretKey) { + * + * @param context The parent context. + * @param optional Indicates if this authenticator is optional, so alternative authenticators + * down the chain can be attempted. + * @param realm The name of the security realm. + * @param encryptSecretKey The secret key used to encrypt the cookie value. + */ + public CookieAuthenticator( + Context context, boolean optional, String realm, byte[] encryptSecretKey) { super(context, optional, ChallengeScheme.HTTP_COOKIE, realm); this.cookieName = "Credentials"; this.interceptingLogin = true; @@ -133,31 +120,25 @@ public CookieAuthenticator(Context context, boolean optional, String realm, /** * Constructor for mandatory cookie authenticators. - * - * @param context - * The parent context. - * @param realm - * The name of the security realm. - * @param encryptSecretKey - * The secret key used to encrypt the cookie value. - */ - public CookieAuthenticator(Context context, String realm, - byte[] encryptSecretKey) { + * + * @param context The parent context. + * @param realm The name of the security realm. + * @param encryptSecretKey The secret key used to encrypt the cookie value. + */ + public CookieAuthenticator(Context context, String realm, byte[] encryptSecretKey) { this(context, false, realm, encryptSecretKey); } /** - * Attempts to redirect the user's browser to the URI provided in a query - * parameter named by {@link #getRedirectQueryName()}. - * - * @param request - * The current request. - * @param response - * The current response. + * Attempts to redirect the user's browser to the URI provided in a query parameter named by + * {@link #getRedirectQueryName()}. + * + * @param request The current request. + * @param response The current response. */ protected void attemptRedirect(Request request, Response response) { - String targetUri = request.getResourceRef().getQueryAsForm() - .getFirstValue(getRedirectQueryName()); + String targetUri = + request.getResourceRef().getQueryAsForm().getFirstValue(getRedirectQueryName()); if (targetUri != null) { response.redirectSeeOther(Reference.decode(targetUri)); @@ -165,45 +146,38 @@ protected void attemptRedirect(Request request, Response response) { } /** - * Restores credentials from the cookie named {@link #getCookieName()} if - * available. The usual processing is the followed. + * Restores credentials from the cookie named {@link #getCookieName()} if available. The usual + * processing is the followed. */ @Override protected boolean authenticate(Request request, Response response) { // Restore credentials from the cookie - Cookie credentialsCookie = request.getCookies().getFirst( - getCookieName()); + Cookie credentialsCookie = request.getCookies().getFirst(getCookieName()); if (credentialsCookie != null) { - request.setChallengeResponse(parseCredentials(credentialsCookie - .getValue())); + request.setChallengeResponse(parseCredentials(credentialsCookie.getValue())); } return super.authenticate(request, response); } - /** - * Sets or updates the credentials cookie. - */ + /** Sets or updates the credentials cookie. */ @Override protected int authenticated(Request request, Response response) { try { - CookieSetting credentialsCookie = getCredentialsCookie(request, - response); - credentialsCookie.setValue(formatCredentials(request - .getChallengeResponse())); + CookieSetting credentialsCookie = getCredentialsCookie(request, response); + credentialsCookie.setValue(formatCredentials(request.getChallengeResponse())); credentialsCookie.setMaxAge(getMaxCookieAge()); } catch (GeneralSecurityException e) { - getLogger().log(Level.SEVERE, - "Could not format credentials cookie", e); + getLogger().log(Level.SEVERE, "Could not format credentials cookie", e); } return super.authenticated(request, response); } /** - * Optionally handles the login and logout actions by intercepting the HTTP - * calls to the {@link #getLoginPath()} and {@link #getLogoutPath()} URIs. + * Optionally handles the login and logout actions by intercepting the HTTP calls to the {@link + * #getLoginPath()} and {@link #getLogoutPath()} URIs. */ @Override protected int beforeHandle(Request request, Response response) { @@ -218,9 +192,9 @@ protected int beforeHandle(Request request, Response response) { /** * This method should be overridden to return a login form representation.
- * By default, it redirects the user's browser to the - * {@link #getLoginFormPath()} URI, adding the URI of the target resource as - * a query parameter of name {@link #getRedirectQueryName()}.
+ * By default, it redirects the user's browser to the {@link #getLoginFormPath()} URI, adding + * the URI of the target resource as a query parameter of name {@link #getRedirectQueryName()}. + *
* In case the getLoginFormPath() is not set, it calls the parent's method. */ @Override @@ -230,13 +204,13 @@ public void challenge(Response response, boolean stale) { } else { Reference ref = response.getRequest().getResourceRef(); String redirectQueryName = getRedirectQueryName(); - String redirectQueryValue = ref.getQueryAsForm().getFirstValue( - redirectQueryName, ""); + String redirectQueryValue = ref.getQueryAsForm().getFirstValue(redirectQueryName, ""); if ("".equals(redirectQueryValue)) { - redirectQueryValue = new Reference(getLoginFormPath()) - .addQueryParameter(redirectQueryName, ref.toString()) - .toString(); + redirectQueryValue = + new Reference(getLoginFormPath()) + .addQueryParameter(redirectQueryName, ref.toString()) + .toString(); } response.redirectSeeOther(redirectQueryValue); @@ -245,19 +219,17 @@ public void challenge(Response response, boolean stale) { /** * Formats the raws credentials to store in the cookie. - * - * @param challenge - * The challenge response to format. + * + * @param challenge The challenge response to format. * @return The raw credentials. * @throws GeneralSecurityException */ - public String formatCredentials(ChallengeResponse challenge) - throws GeneralSecurityException { + public String formatCredentials(ChallengeResponse challenge) throws GeneralSecurityException { // Data buffer - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); // Indexes buffer - StringBuffer isb = new StringBuffer(); + StringBuilder isb = new StringBuilder(); String timeIssued = Long.toString(System.currentTimeMillis()); int i = timeIssued.length(); sb.append(timeIssued); @@ -278,14 +250,16 @@ public String formatCredentials(ChallengeResponse challenge) sb.append('/'); sb.append(isb); - return Base64.getEncoder().encodeToString(CryptoUtils.encrypt(getEncryptAlgorithm(), - getEncryptSecretKey(), sb.toString())); + return Base64.getEncoder() + .encodeToString( + CryptoUtils.encrypt( + getEncryptAlgorithm(), getEncryptSecretKey(), sb.toString())); } /** - * Returns the cookie name to use for the authentication credentials. By - * default, it is is "Credentials". - * + * Returns the cookie name to use for the authentication credentials. By default, it is is + * "Credentials". + * * @return The cookie name to use for the authentication credentials. */ public String getCookieName() { @@ -293,19 +267,15 @@ public String getCookieName() { } /** - * Returns the credentials cookie setting. It first try to find an existing - * cookie. If necessary, it creates a new one. - * - * @param request - * The current request. - * @param response - * The current response. + * Returns the credentials cookie setting. It first try to find an existing cookie. If + * necessary, it creates a new one. + * + * @param request The current request. + * @param response The current response. * @return The credentials cookie setting. */ - protected CookieSetting getCredentialsCookie(Request request, - Response response) { - CookieSetting credentialsCookie = response.getCookieSettings() - .getFirst(getCookieName()); + protected CookieSetting getCredentialsCookie(Request request, Response response) { + CookieSetting credentialsCookie = response.getCookieSettings().getFirst(getCookieName()); if (credentialsCookie == null) { credentialsCookie = new CookieSetting(getCookieName(), null); @@ -326,31 +296,28 @@ protected CookieSetting getCredentialsCookie(Request request, } /** - * Returns the name of the algorithm used to encrypt the log info cookie - * value. By default, it returns "AES". - * - * @return The name of the algorithm used to encrypt the log info cookie - * value. + * Returns the name of the algorithm used to encrypt the log info cookie value. By default, it + * returns "AES". + * + * @return The name of the algorithm used to encrypt the log info cookie value. */ public String getEncryptAlgorithm() { return encryptAlgorithm; } /** - * Returns the secret key for the algorithm used to encrypt the log info - * cookie value. - * - * @return The secret key for the algorithm used to encrypt the log info - * cookie value. + * Returns the secret key for the algorithm used to encrypt the log info cookie value. + * + * @return The secret key for the algorithm used to encrypt the log info cookie value. */ public byte[] getEncryptSecretKey() { return encryptSecretKey; } /** - * Returns the name of the HTML login form field containing the identifier. - * Returns "login" by default. - * + * Returns the name of the HTML login form field containing the identifier. Returns "login" by + * default. + * * @return The name of the HTML login form field containing the identifier. */ public String getIdentifierFormName() { @@ -359,7 +326,7 @@ public String getIdentifierFormName() { /** * Returns the URI path of the HTML login form to use to challenge the user. - * + * * @return The URI path of the HTML login form to use to challenge the user. */ public String getLoginFormPath() { @@ -368,7 +335,7 @@ public String getLoginFormPath() { /** * Returns the login URI path to intercept. - * + * * @return The login URI path to intercept. */ public String getLoginPath() { @@ -377,7 +344,7 @@ public String getLoginPath() { /** * Returns the logout URI path to intercept. - * + * * @return The logout URI path to intercept. */ public String getLogoutPath() { @@ -385,9 +352,9 @@ public String getLogoutPath() { } /** - * Returns the maximum age of the log info cookie. By default, it uses -1 to - * make the cookie only last until the end of the current browser session. - * + * Returns the maximum age of the log info cookie. By default, it uses -1 to make the cookie + * only last until the end of the current browser session. + * * @return The maximum age of the log info cookie. * @see CookieSetting#getMaxAge() */ @@ -396,20 +363,20 @@ public int getMaxCookieAge() { } /** - * Returns the name of the query parameter containing the URI to redirect - * the browser to after login or logout. By default, it uses "targetUri". - * - * @return The name of the query parameter containing the URI to redirect - * the browser to after login or logout. + * Returns the name of the query parameter containing the URI to redirect the browser to after + * login or logout. By default, it uses "targetUri". + * + * @return The name of the query parameter containing the URI to redirect the browser to after + * login or logout. */ public String getRedirectQueryName() { return redirectQueryName; } /** - * Returns the name of the HTML login form field containing the secret. - * Returns "password" by default. - * + * Returns the name of the HTML login form field containing the secret. Returns "password" by + * default. + * * @return The name of the HTML login form field containing the secret. */ public String getSecretFormName() { @@ -418,7 +385,7 @@ public String getSecretFormName() { /** * Indicates if the login requests should be intercepted. - * + * * @return True if the login requests should be intercepted. */ public boolean isInterceptingLogin() { @@ -427,7 +394,7 @@ public boolean isInterceptingLogin() { /** * Indicates if the logout requests should be intercepted. - * + * * @return True if the logout requests should be intercepted. */ public boolean isInterceptingLogout() { @@ -435,51 +402,37 @@ public boolean isInterceptingLogout() { } /** - * Indicates if the request is an attempt to log in and should be - * intercepted. - * - * @param request - * The current request. - * @param response - * The current response. - * @return True if the request is an attempt to log in and should be - * intercepted. + * Indicates if the request is an attempt to log in and should be intercepted. + * + * @param request The current request. + * @param response The current response. + * @return True if the request is an attempt to log in and should be intercepted. */ protected boolean isLoggingIn(Request request, Response response) { return isInterceptingLogin() - && getLoginPath() - .equals(request.getResourceRef().getRemainingPart( - false, false)) + && getLoginPath().equals(request.getResourceRef().getRemainingPart(false, false)) && Method.POST.equals(request.getMethod()); } /** - * Indicates if the request is an attempt to log out and should be - * intercepted. - * - * @param request - * The current request. - * @param response - * The current response. - * @return True if the request is an attempt to log out and should be - * intercepted. + * Indicates if the request is an attempt to log out and should be intercepted. + * + * @param request The current request. + * @param response The current response. + * @return True if the request is an attempt to log out and should be intercepted. */ protected boolean isLoggingOut(Request request, Response response) { return isInterceptingLogout() - && getLogoutPath() - .equals(request.getResourceRef().getRemainingPart( - false, false)) - && (Method.GET.equals(request.getMethod()) || Method.POST - .equals(request.getMethod())); + && getLogoutPath().equals(request.getResourceRef().getRemainingPart(false, false)) + && (Method.GET.equals(request.getMethod()) + || Method.POST.equals(request.getMethod())); } /** * Processes the login request. - * - * @param request - * The current request. - * @param response - * The current response. + * + * @param request The current request. + * @param response The current response. */ protected void login(Request request, Response response) { // Login detected @@ -488,9 +441,11 @@ protected void login(Request request, Response response) { Parameter secret = form.getFirst(getSecretFormName()); // Set credentials - ChallengeResponse cr = new ChallengeResponse(getScheme(), - identifier != null ? identifier.getValue() : null, - secret != null ? secret.getValue() : null); + ChallengeResponse cr = + new ChallengeResponse( + getScheme(), + identifier != null ? identifier.getValue() : null, + secret != null ? secret.getValue() : null); request.setChallengeResponse(cr); // Attempt to redirect @@ -499,17 +454,14 @@ protected void login(Request request, Response response) { /** * Processes the logout request. - * - * @param request - * The current request. - * @param response - * The current response. + * + * @param request The current request. + * @param response The current response. */ protected int logout(Request request, Response response) { // Clears the credentials request.setChallengeResponse(null); - CookieSetting credentialsCookie = getCredentialsCookie(request, - response); + CookieSetting credentialsCookie = getCredentialsCookie(request, response); credentialsCookie.setMaxAge(0); // Attempt to redirect @@ -519,26 +471,23 @@ protected int logout(Request request, Response response) { } /** - * Decodes the credentials stored in a cookie into a proper - * {@link ChallengeResponse} object. - * - * @param cookieValue - * The credentials to decode from cookie value. + * Decodes the credentials stored in a cookie into a proper {@link ChallengeResponse} object. + * + * @param cookieValue The credentials to decode from cookie value. * @return The credentials as a proper challenge response. */ protected ChallengeResponse parseCredentials(String cookieValue) { + if (cookieValue == null) { + return null; + } + try { // 1) Decode Base64 string byte[] encrypted = Base64.getDecoder().decode(cookieValue); - - if (encrypted == null) { - getLogger().warning( - "Cannot decode cookie credentials : " + cookieValue); - } - + // 2) Decrypt the credentials - String decrypted = CryptoUtils.decrypt(getEncryptAlgorithm(), - getEncryptSecretKey(), encrypted); + String decrypted = + CryptoUtils.decrypt(getEncryptAlgorithm(), getEncryptSecretKey(), encrypted); // 3) Parse the decrypted cookie value int lastSlash = decrypted.lastIndexOf('/'); @@ -549,24 +498,20 @@ protected ChallengeResponse parseCredentials(String cookieValue) { // 4) Create the challenge response ChallengeResponse cr = new ChallengeResponse(getScheme()); cr.setRawValue(cookieValue); - cr.setTimeIssued(Long.parseLong(decrypted.substring(0, - identifierIndex))); - cr.setIdentifier(decrypted.substring(identifierIndex + 1, - secretIndex)); + cr.setTimeIssued(Long.parseLong(decrypted.substring(0, identifierIndex))); + cr.setIdentifier(decrypted.substring(identifierIndex + 1, secretIndex)); cr.setSecret(decrypted.substring(secretIndex + 1, lastSlash)); return cr; } catch (Exception e) { - getLogger().log(Level.INFO, "Unable to decrypt cookie credentials", - e); + getLogger().log(Level.INFO, "Unable to decrypt cookie credentials", e); return null; } } /** * Sets the cookie name to use for the authentication credentials. - * - * @param cookieName - * The cookie name to use for the authentication credentials. + * + * @param cookieName The cookie name to use for the authentication credentials. */ public void setCookieName(String cookieName) { this.cookieName = cookieName; @@ -574,22 +519,17 @@ public void setCookieName(String cookieName) { /** * Sets the name of the algorithm used to encrypt the log info cookie value. - * - * @param secretAlgorithm - * The name of the algorithm used to encrypt the log info cookie - * value. + * + * @param secretAlgorithm The name of the algorithm used to encrypt the log info cookie value. */ public void setEncryptAlgorithm(String secretAlgorithm) { this.encryptAlgorithm = secretAlgorithm; } /** - * Sets the secret key for the algorithm used to encrypt the log info cookie - * value. - * - * @param secretKey - * The secret key for the algorithm used to encrypt the log info - * cookie value. + * Sets the secret key for the algorithm used to encrypt the log info cookie value. + * + * @param secretKey The secret key for the algorithm used to encrypt the log info cookie value. */ public void setEncryptSecretKey(byte[] secretKey) { this.encryptSecretKey = secretKey; @@ -597,10 +537,8 @@ public void setEncryptSecretKey(byte[] secretKey) { /** * Sets the name of the HTML login form field containing the identifier. - * - * @param loginInputName - * The name of the HTML login form field containing the - * identifier. + * + * @param loginInputName The name of the HTML login form field containing the identifier. */ public void setIdentifierFormName(String loginInputName) { this.identifierFormName = loginInputName; @@ -608,9 +546,8 @@ public void setIdentifierFormName(String loginInputName) { /** * Indicates if the login requests should be intercepted. - * - * @param intercepting - * True if the login requests should be intercepted. + * + * @param intercepting True if the login requests should be intercepted. */ public void setInterceptingLogin(boolean intercepting) { this.interceptingLogin = intercepting; @@ -618,9 +555,8 @@ public void setInterceptingLogin(boolean intercepting) { /** * Indicates if the logout requests should be intercepted. - * - * @param intercepting - * True if the logout requests should be intercepted. + * + * @param intercepting True if the logout requests should be intercepted. */ public void setInterceptingLogout(boolean intercepting) { this.interceptingLogout = intercepting; @@ -628,10 +564,8 @@ public void setInterceptingLogout(boolean intercepting) { /** * Sets the URI path of the HTML login form to use to challenge the user. - * - * @param loginFormPath - * The URI path of the HTML login form to use to challenge the - * user. + * + * @param loginFormPath The URI path of the HTML login form to use to challenge the user. */ public void setLoginFormPath(String loginFormPath) { this.loginFormPath = loginFormPath; @@ -639,9 +573,8 @@ public void setLoginFormPath(String loginFormPath) { /** * Sets the login URI path to intercept. - * - * @param loginPath - * The login URI path to intercept. + * + * @param loginPath The login URI path to intercept. */ public void setLoginPath(String loginPath) { this.loginPath = loginPath; @@ -649,9 +582,8 @@ public void setLoginPath(String loginPath) { /** * Sets the logout URI path to intercept. - * - * @param logoutPath - * The logout URI path to intercept. + * + * @param logoutPath The logout URI path to intercept. */ public void setLogoutPath(String logoutPath) { this.logoutPath = logoutPath; @@ -659,9 +591,8 @@ public void setLogoutPath(String logoutPath) { /** * Sets the maximum age of the log info cookie. - * - * @param timeout - * The maximum age of the log info cookie. + * + * @param timeout The maximum age of the log info cookie. * @see CookieSetting#setMaxAge(int) */ public void setMaxCookieAge(int timeout) { @@ -669,12 +600,11 @@ public void setMaxCookieAge(int timeout) { } /** - * Sets the name of the query parameter containing the URI to redirect the - * browser to after login or logout. - * - * @param redirectQueryName - * The name of the query parameter containing the URI to redirect - * the browser to after login or logout. + * Sets the name of the query parameter containing the URI to redirect the browser to after + * login or logout. + * + * @param redirectQueryName The name of the query parameter containing the URI to redirect the + * browser to after login or logout. */ public void setRedirectQueryName(String redirectQueryName) { this.redirectQueryName = redirectQueryName; @@ -682,12 +612,10 @@ public void setRedirectQueryName(String redirectQueryName) { /** * Sets the name of the HTML login form field containing the secret. - * - * @param passwordInputName - * The name of the HTML login form field containing the secret. + * + * @param passwordInputName The name of the HTML login form field containing the secret. */ public void setSecretFormName(String passwordInputName) { this.secretFormName = passwordInputName; } - } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestAuthenticator.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestAuthenticator.java index f8c1f410dc..7a6c959867 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestAuthenticator.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestAuthenticator.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; - import org.restlet.Context; import org.restlet.data.ChallengeRequest; import org.restlet.data.ChallengeScheme; @@ -23,9 +21,9 @@ import org.restlet.security.Verifier; /** - * Authenticator supporting the digest challenge authentication schemes. By - * default, it only knows about the {@link ChallengeScheme#HTTP_DIGEST} scheme. - * + * Authenticator supporting the digest challenge authentication schemes. By default, it only knows + * about the {@link ChallengeScheme#HTTP_DIGEST} scheme. + * * @see DigestVerifier * @see DigestAuthenticator * @author Jerome Louvel @@ -45,41 +43,35 @@ public class DigestAuthenticator extends ChallengeAuthenticator { private volatile String serverKey; /** - * Constructor. Sets the challenge scheme to - * {@link ChallengeScheme#HTTP_DIGEST} and the nonce lifespan to 5 minutes - * by default. - * - * @param context - * The context. - * @param optional - * Indicates if the authentication success is optional. - * @param realm - * The authentication realm. - * @param domainRefs - * The URI references that define the protection domains. - * @param serverKey - * The secret key known only to server. + * Constructor. Sets the challenge scheme to {@link ChallengeScheme#HTTP_DIGEST} and the nonce + * lifespan to 5 minutes by default. + * + * @param context The context. + * @param optional Indicates if the authentication success is optional. + * @param realm The authentication realm. + * @param domainRefs The URI references that define the protection domains. + * @param serverKey The secret key known only to server. */ - public DigestAuthenticator(Context context, boolean optional, String realm, - List domainRefs, String serverKey) { + public DigestAuthenticator( + Context context, + boolean optional, + String realm, + List domainRefs, + String serverKey) { super(context, optional, ChallengeScheme.HTTP_DIGEST, realm); this.domainRefs = domainRefs; this.maxServerNonceAge = DEFAULT_MAX_SERVER_NONCE_AGE; this.serverKey = serverKey; - setVerifier(new org.restlet.ext.crypto.internal.HttpDigestVerifier( - this, null, null)); + setVerifier(new org.restlet.ext.crypto.internal.HttpDigestVerifier(this, null, null)); } /** - * Constructor. By default, it set the "optional" property to 'false' and - * the "domainUris" property to a single '/' URI. - * - * @param context - * The context. - * @param realm - * The authentication realm. - * @param serverKey - * secret key known only to server + * Constructor. By default, it sets the "optional" property to 'false' and the "domainUris" + * property to a single '/' URI. + * + * @param context The context. + * @param realm The authentication realm. + * @param serverKey secret key known only to server */ public DigestAuthenticator(Context context, String realm, String serverKey) { this(context, false, realm, null, serverKey); @@ -95,19 +87,18 @@ protected ChallengeRequest createChallengeRequest(boolean stale) { } /** - * Generates a server nonce. - * - * @return A new server nonce. + * Generates server nonce. + * + * @return New server nonce. */ public String generateServerNonce() { return CryptoUtils.makeNonce(getServerKey()); } /** - * Returns the base URI references that collectively define the protected - * domains for the digest authentication. By default, it returns a list with a - * single "/" URI reference. - * + * Returns the base URI references that collectively define the protected domains for the digest + * authentication. By default, it returns a list with a single "/" URI reference. + * * @return The base URI references. */ public List getDomainRefs() { @@ -117,7 +108,7 @@ public List getDomainRefs() { synchronized (this) { r = this.domainRefs; if (r == null) { - this.domainRefs = r = new CopyOnWriteArrayList(); + this.domainRefs = r = new CopyOnWriteArrayList<>(); this.domainRefs.add(new Reference("/")); } } @@ -126,15 +117,12 @@ public List getDomainRefs() { } /** - * Return the hashed secret. By default, it knows how to hash HTTP DIGEST - * secrets, specified as A1 in section 3.2.2.2 of RFC2617, or null if the - * identifier has no corresponding secret. - * - * @param identifier - * The user identifier to hash. - * @param secret - * The user secret. - * @return A hash of the user name, realm, and password. + * Return the hashed secret. By default, it knows how to hash HTTP DIGEST secrets, specified as + * A1 in section 3.2.2.2 of RFC2617, or null if the identifier has no corresponding secret. + * + * @param identifier The user identifier to hash. + * @param secret The user secret. + * @return A hash of the username, realm, and password. */ public String getHashedSecret(String identifier, char[] secret) { if (ChallengeScheme.HTTP_DIGEST.equals(getScheme())) { @@ -146,7 +134,7 @@ public String getHashedSecret(String identifier, char[] secret) { /** * Returns the number of milliseconds between each mandatory nonce refresh. - * + * * @return The server nonce lifespan. */ public long getMaxServerNonceAge() { @@ -155,7 +143,7 @@ public long getMaxServerNonceAge() { /** * Returns the secret key known only by server. - * + * * @return The server secret key. */ public String getServerKey() { @@ -169,11 +157,9 @@ public DigestVerifier getVerifier() { } /** - * Sets the URI references that define the protection domains for the digest - * authentication. - * - * @param domainRefs - * The base URI references. + * Sets the URI references that define the protection domains for the digest authentication. + * + * @param domainRefs The base URI references. */ public void setDomainRefs(List domainRefs) { this.domainRefs = domainRefs; @@ -181,30 +167,27 @@ public void setDomainRefs(List domainRefs) { /** * Sets the number of milliseconds between each mandatory nonce refresh. - * - * @param maxServerNonceAge - * The nonce lifespan in milliseconds. + * + * @param maxServerNonceAge The nonce lifespan in milliseconds. */ public void setMaxServerNonceAge(long maxServerNonceAge) { this.maxServerNonceAge = maxServerNonceAge; } /** - * Sets the secret key known only by server. - * - * @param serverKey - * The server secret key. + * Sets the secret key known only by the server. + * + * @param serverKey The server secret key. */ public void setServerKey(String serverKey) { this.serverKey = serverKey; } /** - * Set the internal verifier. In general you shouldn't replace it and - * instead use the {@link #setWrappedVerifier(LocalVerifier)} method. - * - * @param verifier - * The internal verifier. + * Set the internal verifier. In general, you shouldn't replace it and instead use the {@link + * #setWrappedVerifier(LocalVerifier)} method. + * + * @param verifier The internal verifier. */ @Override public void setVerifier(Verifier verifier) { @@ -226,13 +209,11 @@ public void setVerifier(Verifier verifier) { } /** - * Sets the digest algorithm of secrets returned by the wrapped verifier. - * The secrets from the wrapped verifier are the ones used by the verifier - * to compare those sent by clients when attempting to authenticate. - * - * @param wrappedAlgorithm - * The digest algorithm of secrets returned by the wrapped - * verifier. + * Sets the digest algorithm of secrets returned by the wrapped verifier. The secrets from the + * wrapped verifier are the ones used by the verifier to compare those sent by clients when + * attempting to authenticate. + * + * @param wrappedAlgorithm The digest algorithm of secrets returned by the wrapped verifier. * @see Digest */ public void setWrappedAlgorithm(String wrappedAlgorithm) { @@ -240,14 +221,12 @@ public void setWrappedAlgorithm(String wrappedAlgorithm) { } /** - * Sets the secret verifier that will be wrapped by real verifier supporting - * all the HTTP DIGEST verifications (nonce, domain URIs, etc.). - * - * @param localVerifier - * The local verifier to wrap. + * Sets the secret verifier that will be wrapped by real verifier supporting all the HTTP DIGEST + * verifications (nonce, domain URIs, etc.). + * + * @param localVerifier The local verifier to wrap. */ public void setWrappedVerifier(LocalVerifier localVerifier) { getVerifier().setWrappedVerifier(localVerifier); } - } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestUtils.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestUtils.java index 14faff4087..16cf37e8d1 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestUtils.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestUtils.java @@ -1,49 +1,44 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; - import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; - import org.restlet.data.Digest; /** * Security data manipulation utilities. - * + * * @author Jerome Louvel */ public class DigestUtils { /** - * General regex pattern to extract comma separated name-value components. - * This pattern captures one name and value per match(), and is repeatedly - * applied to the input string to extract all components. Must handle both - * quoted and unquoted values as RFC2617 isn't consistent in this respect. - * Pattern is immutable and thread-safe so reuse one static instance. + * General regex pattern to extract comma separated name-value components. This pattern captures + * one name and value per match() and is repeatedly applied to the input string to extract all + * components. Must handle both quoted and unquoted values as RFC2617 isn't consistent in this + * respect. Pattern is immutable and thread-safe, so reuse one static instance. */ - private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray(); + private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); /** - * Returns the digest of the target string. Target is decoded to bytes using - * the US-ASCII charset. Supports MD5 and SHA-1 algorithms. - * - * @param target - * The string to encode. - * @param algorithm - * The digest algorithm to use. + * Returns the digest of the target string. Target is decoded to bytes using the US-ASCII + * charset. Supports MD5 and SHA-1 algorithms. + * + * @param target The string to encode. + * @param algorithm The digest algorithm to use. * @return The digest of the target string. */ public static char[] digest(char[] target, String algorithm) { @@ -51,13 +46,11 @@ public static char[] digest(char[] target, String algorithm) { } /** - * Returns the digest of the target string. Target is decoded to bytes using - * the US-ASCII charset. Supports MD5 and SHA-1 algorithms. - * - * @param target - * The string to encode. - * @param algorithm - * The digest algorithm to use. + * Returns the digest of the target string. Target is decoded to bytes using the US-ASCII + * charset. Supports MD5 and SHA-1 algorithms. + * + * @param target The string to encode. + * @param algorithm The digest algorithm to use. * @return The digest of the target string. */ public static String digest(String target, String algorithm) { @@ -68,15 +61,13 @@ public static String digest(String target, String algorithm) { } throw new IllegalArgumentException("Unsupported algorithm."); - }; + } /** * Converts a source string to its HMAC/SHA-1 value. - * - * @param source - * The source string to convert. - * @param secretKey - * The secret key to use for conversion. + * + * @param source The source string to convert. + * @param secretKey The secret key to use for conversion. * @return The HMac value of the source string. */ public static byte[] toHMacSha1(String source, byte[] secretKey) { @@ -94,12 +85,10 @@ public static byte[] toHMacSha1(String source, byte[] secretKey) { result = mac.doFinal(source.getBytes()); } catch (NoSuchAlgorithmException nsae) { throw new RuntimeException( - "Could not find the SHA-1 algorithm. HMac conversion failed.", - nsae); + "Could not find the SHA-1 algorithm. HMac conversion failed.", nsae); } catch (InvalidKeyException ike) { throw new RuntimeException( - "Invalid key exception detected. HMac conversion failed.", - ike); + "Invalid key exception detected. HMac conversion failed.", ike); } return result; @@ -107,11 +96,9 @@ public static byte[] toHMacSha1(String source, byte[] secretKey) { /** * Converts a source string to its HMAC/SHA-1 value. - * - * @param source - * The source string to convert. - * @param secretKey - * The secret key to use for conversion. + * + * @param source The source string to convert. + * @param secretKey The secret key to use for conversion. * @return The HMac value of the source string. */ public static byte[] toHMacSha1(String source, String secretKey) { @@ -120,11 +107,9 @@ public static byte[] toHMacSha1(String source, String secretKey) { /** * Converts a source string to its HMAC/SHA256 value. - * - * @param source - * The source string to convert. - * @param secretKey - * The secret key to use for conversion. + * + * @param source The source string to convert. + * @param secretKey The secret key to use for conversion. * @return The HMac value of the source string. */ public static byte[] toHMacSha256(String source, byte[] secretKey) { @@ -132,30 +117,23 @@ public static byte[] toHMacSha256(String source, byte[] secretKey) { try { // Create the HMAC/SHA256 key - SecretKeySpec signingKey = new SecretKeySpec(secretKey, - "HmacSHA256"); + SecretKeySpec signingKey = new SecretKeySpec(secretKey, "HmacSHA256"); // Create the message authentication code (MAC) Mac mac = Mac.getInstance("HmacSHA256"); mac.init(signingKey); // Compute the HMAC value - result = mac.doFinal(source.getBytes("UTF-8")); + result = mac.doFinal(source.getBytes(StandardCharsets.UTF_8)); } catch (NoSuchAlgorithmException nsae) { throw new RuntimeException( - "Could not find the SHA256 algorithm. HMac conversion failed.", - nsae); + "Could not find the SHA256 algorithm. HMac conversion failed.", nsae); } catch (InvalidKeyException ike) { throw new RuntimeException( - "Invalid key exception detected. HMac conversion failed.", - ike); + "Invalid key exception detected. HMac conversion failed.", ike); } catch (IllegalStateException ise) { throw new RuntimeException( - "IIllegal state exception detected. HMac conversion failed.", - ise); - } catch (UnsupportedEncodingException uee) { - throw new RuntimeException( - "Unsuported encoding UTF-8. HMac conversion failed.", uee); + "IIllegal state exception detected. HMac conversion failed.", ise); } return result; @@ -163,11 +141,9 @@ public static byte[] toHMacSha256(String source, byte[] secretKey) { /** * Converts a source string to its HMAC/SHA256 value. - * - * @param source - * The source string to convert. - * @param secretKey - * The secret key to use for conversion. + * + * @param source The source string to convert. + * @param secretKey The secret key to use for conversion. * @return The HMac value of the source string. */ public static byte[] toHMacSha256(String source, String secretKey) { @@ -175,21 +151,16 @@ public static byte[] toHMacSha256(String source, String secretKey) { } /** - * Return the HTTP DIGEST hashed secret. It concatenates the identifier, - * realm and secret, separated by a comma and digest them using MD5. - * - * @param identifier - * The user identifier to hash. - * @param secret - * The user secret. - * @param realm - * The authentication realm. - * @return A hash of the user name, realm, and password, specified as A1 in - * section 3.2.2.2 of RFC2617, or null if the identifier has no - * corresponding secret. + * Return the HTTP DIGEST hashed secret. It concatenates the identifier, realm and secret, + * separated by a comma and digests them using MD5. + * + * @param identifier The user identifier to hash. + * @param secret The user secret. + * @param realm The authentication realm. + * @return A hash of the username, realm, and password, specified as A1 in section 3.2.2.2 of + * RFC2617, or null if the identifier has no corresponding secret. */ - public static String toHttpDigest(String identifier, char[] secret, - String realm) { + public static String toHttpDigest(String identifier, char[] secret, String realm) { if (secret != null) { return toMd5(identifier + ":" + realm + ":" + new String(secret)); } @@ -198,13 +169,12 @@ public static String toHttpDigest(String identifier, char[] secret, } /** - * Returns the MD5 digest of the target string. Target is decoded to bytes - * using the US-ASCII charset. The returned hexadecimal String always - * contains 32 lowercase alphanumeric characters. For example, if target is - * "HelloWorld", this method returns "68e109f0f40ca72a15e05cc22786f8e6". - * - * @param target - * The string to encode. + * Returns the MD5 digest of the target string. Target is decoded to bytes using the US-ASCII + * charset. The returned hexadecimal String always contains 32 lowercase alphanumeric + * characters. For example, if the target is "HelloWorld", this method returns + * "68e109f0f40ca72a15e05cc22786f8e6". + * + * @param target The string to encode. * @return The MD5 digest of the target string. */ public static String toMd5(String target) { @@ -218,43 +188,38 @@ public static String toMd5(String target) { } /** - * Returns the MD5 digest of target string. Target is decoded to bytes using - * the named charset. The returned hexadecimal String always contains 32 - * lowercase alphanumeric characters. For example, if target is - * "HelloWorld", this method returns "68e109f0f40ca72a15e05cc22786f8e6". - * - * @param target - * The string to encode. - * @param charsetName - * The character set. + * Returns the MD5 digest of the target string. Target is decoded to bytes using the named + * charset. The returned hexadecimal String always contains 32 lowercase alphanumeric + * characters. For example, if the target is "HelloWorld", this method returns + * "68e109f0f40ca72a15e05cc22786f8e6". + * + * @param target The string to encode. + * @param charsetName The character set. * @return The MD5 digest of the target string. - * * @throws UnsupportedEncodingException */ public static String toMd5(String target, String charsetName) throws UnsupportedEncodingException { try { - final byte[] md5 = MessageDigest.getInstance("MD5").digest( - target.getBytes(charsetName)); + final byte[] md5 = + MessageDigest.getInstance("MD5").digest(target.getBytes(charsetName)); final char[] md5Chars = new char[32]; int i = 0; for (final byte b : md5) { - md5Chars[i++] = HEXDIGITS[(b >> 4) & 0xF]; - md5Chars[i++] = HEXDIGITS[b & 0xF]; + md5Chars[i++] = HEX_DIGITS[(b >> 4) & 0xF]; + md5Chars[i++] = HEX_DIGITS[b & 0xF]; } return new String(md5Chars); } catch (NoSuchAlgorithmException nsae) { - throw new RuntimeException( - "No MD5 algorithm, unable to compute MD5"); + throw new RuntimeException("No MD5 algorithm, unable to compute MD5"); } } /** - * Returns the SHA1 digest of the target string. Target is decoded to bytes - * using the US-ASCII charset. - * - * @param target - * The string to encode. + * Returns the SHA1 digest of the target string. Target is decoded to bytes using the US-ASCII + * charset. + * + * @param target The string to encode. * @return The MD5 digest of the target string. */ public static String toSha1(String target) { @@ -268,34 +233,28 @@ public static String toSha1(String target) { } /** - * Returns the SHA1 digest of target string. Target is decoded to bytes - * using the named charset. - * - * @param target - * The string to encode. - * @param charsetName - * The character set. + * Returns the SHA1 digest of the target string. Target is decoded to bytes using the named + * charset. + * + * @param target The string to encode. + * @param charsetName The character set. * @return The SHA1 digest of the target string. - * * @throws UnsupportedEncodingException */ public static String toSha1(String target, String charsetName) throws UnsupportedEncodingException { try { - return Base64.getEncoder().encodeToString( - MessageDigest.getInstance("SHA1").digest( - target.getBytes(charsetName))); + return Base64.getEncoder() + .encodeToString( + MessageDigest.getInstance("SHA1").digest(target.getBytes(charsetName))); } catch (NoSuchAlgorithmException nsae) { - throw new RuntimeException( - "No SHA1 algorithm, unable to compute SHA1"); + throw new RuntimeException("No SHA1 algorithm, unable to compute SHA1"); } } /** - * Private constructor to ensure that the class acts as a true utility class - * i.e. it isn't instantiable and extensible. + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. */ - private DigestUtils() { - } - + private DigestUtils() {} } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestVerifier.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestVerifier.java index 2fcac96935..f5f4cd92be 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestVerifier.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/DigestVerifier.java @@ -1,29 +1,26 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.data.Digest; import org.restlet.security.LocalVerifier; import org.restlet.security.SecretVerifier; -import java.util.logging.Level; - /** - * Wrapper verifier that can verify digested secrets. If the provided secret is - * a digest, then the local secret must either be a digest of the same algorithm - * or the wrapped verifier must be a {@link LocalVerifier} returning secrets in - * clear.
+ * Wrapper verifier that can verify digested secrets. If the provided secret is a digest, then the + * local secret must either be a digest of the same algorithm or the wrapped verifier must be a + * {@link LocalVerifier} returning secrets in clear.
*
- * If the provided secret is a regular secret, then the local secret can be in - * any digest algorithm or a regular secret. + * If the provided secret is a regular secret, then the local secret can be in any digest algorithm + * or a regular secret. * * @see Digest * @see DigestAuthenticator @@ -31,7 +28,7 @@ */ public class DigestVerifier extends SecretVerifier { - /** The digest algorithm of provided secrets. */ + /** The digest algorithm. */ private String algorithm; /** The digest algorithm of secrets returned by the wrapped verifier. */ @@ -43,34 +40,25 @@ public class DigestVerifier extends SecretVerifier { /** * Constructor. * - * @param algorithm - * The digest algorithm of provided secrets. - * @param wrappedVerifier - * The wrapped secret verifier. - * @param wrappedAlgorithm - * The digest algorithm of secrets provided by the wrapped - * verifier. + * @param algorithm The digest algorithm. + * @param wrappedVerifier The wrapped secret verifier. + * @param wrappedAlgorithm The digest algorithm of secrets provided by the wrapped verifier. * @see Digest */ - public DigestVerifier(String algorithm, T wrappedVerifier, - String wrappedAlgorithm) { + public DigestVerifier(String algorithm, T wrappedVerifier, String wrappedAlgorithm) { this.algorithm = algorithm; this.wrappedAlgorithm = wrappedAlgorithm; this.wrappedVerifier = wrappedVerifier; } /** - * Computes the digest of a secret according to a specified algorithm. By - * default, MD5 hashes (represented as a sequence of 32 hexadecimal digits) - * and SHA-1 hashes are supported. For additional algorithm, override this - * method. + * Computes the digest of a secret according to a specified algorithm. By default, MD5 hashes + * (represented as a sequence of 32 hexadecimal digits) and SHA-1 hashes are supported. For an + * additional algorithm, override this method. * - * @param identifier - * The user identifier. - * @param secret - * The regular secret to digest. - * @param algorithm - * The digest algorithm to use. + * @param identifier The user identifier. + * @param secret The regular secret to digest. + * @param algorithm The digest algorithm to use. * @return The digested secret. * @see Digest */ @@ -79,8 +67,8 @@ protected char[] digest(String identifier, char[] secret, String algorithm) { } /** - * Returns the digest algorithm of provided secrets. Provided secrets are - * the ones sent by clients when attempting to authenticate. + * Returns the digest algorithm of provided secrets. Provided secrets are the ones sent by + * clients when attempting to authenticate. * * @return The digest algorithm of input secrets. */ @@ -89,11 +77,10 @@ public String getAlgorithm() { } /** - * Sets the digest algorithm of provided secrets. Provided secrets are the - * ones sent by clients when attempting to authenticate. + * Sets the digest algorithm of provided secrets. Provided secrets are the ones sent by clients + * when attempting to authenticate. * - * @param algorithm - * The digest algorithm of secrets provided by the user. + * @param algorithm The digest algorithm of secrets provided by the user. * @see Digest */ public void setAlgorithm(String algorithm) { @@ -101,9 +88,9 @@ public void setAlgorithm(String algorithm) { } /** - * Returns the digest algorithm of secrets returned by the wrapped verifier. - * The secrets from the wrapped verifier are the ones used by the verifier - * to compare those sent by clients when attempting to authenticate. + * Returns the digest algorithm of secrets returned by the wrapped verifier. The secrets from + * the wrapped verifier are the ones used by the verifier to compare those sent by clients when + * attempting to authenticate. * * @return The digest algorithm of secrets returned by the wrapped verifier. */ @@ -112,13 +99,11 @@ public String getWrappedAlgorithm() { } /** - * Sets the digest algorithm of secrets returned by the wrapped verifier. - * The secrets from the wrapped verifier are the ones used by the verifier - * to compare those sent by clients when attempting to authenticate. + * Sets the digest algorithm of secrets returned by the wrapped verifier. The secrets from the + * wrapped verifier are the ones used by the verifier to compare those sent by clients when + * attempting to authenticate. * - * @param wrappedAlgorithm - * The digest algorithm of secrets returned by the wrapped - * verifier. + * @param wrappedAlgorithm The digest algorithm of secrets returned by the wrapped verifier. * @see Digest */ public void setWrappedAlgorithm(String wrappedAlgorithm) { @@ -126,12 +111,11 @@ public void setWrappedAlgorithm(String wrappedAlgorithm) { } /** - * Returns the wrapped secret associated to a given identifier. This method - * can only be called if the wrapped verifier is a {@link LocalVerifier}. + * Returns the wrapped secret associated with a given identifier. This method can only be called + * if the wrapped verifier is a {@link LocalVerifier}. * - * @param identifier - * The identifier to lookup. - * @return The secret associated to the identifier or null. + * @param identifier The identifier to lookup. + * @return The secret associated with the identifier or null. */ public char[] getWrappedSecret(String identifier) { char[] result = null; @@ -140,7 +124,8 @@ public char[] getWrappedSecret(String identifier) { result = localVerifier.getLocalSecret(identifier); } else { Context.getCurrentLogger() - .log(Level.WARNING, + .log( + Level.WARNING, "The wrapped verifier must be a LocalVerifier to allow digesting of wrapped secrets."); } @@ -148,26 +133,24 @@ public char[] getWrappedSecret(String identifier) { } /** - * Returns the digest of the wrapped secret associated to a given - * identifier. If the wrapped algorithm is null it returns the digest of the - * wrapped secret, otherwise the algorithms must be identical. This method - * can only be called if the wrapped verifier is a {@link LocalVerifier}. + * Returns the digest of the wrapped secret associated with a given identifier. If the wrapped + * algorithm is null, it returns the digest of the wrapped secret; otherwise the algorithms must + * be identical. This method can only be called if the wrapped verifier is a {@link + * LocalVerifier}. * - * @param identifier - * The identifier to lookup. - * @return The secret associated to the identifier or null. + * @param identifier The identifier to lookup. + * @return The secret associated with the identifier or null. */ public char[] getWrappedSecretDigest(String identifier) { char[] result = null; if (getWrappedAlgorithm() == null) { - result = digest(identifier, getWrappedSecret(identifier), - getAlgorithm()); + result = digest(identifier, getWrappedSecret(identifier), getAlgorithm()); } else if (getAlgorithm().equals(getWrappedAlgorithm())) { result = getWrappedSecret(identifier); } else { - Context.getCurrentLogger().log(Level.WARNING, - "The digest algorithms can't be different."); + Context.getCurrentLogger() + .log(Level.WARNING, "The digest algorithms can't be different."); } return result; @@ -185,8 +168,7 @@ public T getWrappedVerifier() { /** * Sets the wrapped secret verifier. * - * @param wrappedVerifier - * The wrapped secret verifier. + * @param wrappedVerifier The wrapped secret verifier. */ public void setWrappedVerifier(T wrappedVerifier) { this.wrappedVerifier = wrappedVerifier; @@ -201,21 +183,22 @@ public int verify(String identifier, char[] secret) { if (getWrappedAlgorithm() != null) { secretDigest = digest(identifier, secret, getWrappedAlgorithm()); } else { - // Both secrets should be in clear + // Both secrets should be clear } result = getWrappedVerifier().verify(identifier, secretDigest); } else { if (getWrappedAlgorithm() == null) { - result = compare(secretDigest, getWrappedSecretDigest(identifier)) - ? RESULT_VALID - : RESULT_INVALID; + result = + compare(secretDigest, getWrappedSecretDigest(identifier)) + ? RESULT_VALID + : RESULT_INVALID; } else if (getAlgorithm().equals(getWrappedAlgorithm())) { result = getWrappedVerifier().verify(identifier, secretDigest); } else { result = RESULT_UNSUPPORTED; - Context.getCurrentLogger().log(Level.WARNING, - "The input and output algorithms can't be different."); + Context.getCurrentLogger() + .log(Level.WARNING, "The input and output algorithms can't be different."); } } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/AwsUtils.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/AwsUtils.java index 120129c7ac..5d6770be96 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/AwsUtils.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/AwsUtils.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; import java.util.Base64; @@ -18,7 +17,6 @@ import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.restlet.Request; import org.restlet.data.Header; import org.restlet.data.Method; @@ -32,30 +30,26 @@ import org.restlet.util.Series; /** - * Provides utility functions for implementing the Amazon S3 Authentication - * scheme. - * + * Provides utility functions for implementing the Amazon S3 Authentication scheme. + * * @author Jean-Philippe Steinmetz - * @see - * Authenticating REST Requests + * @see + * Authenticating REST Requests */ public class AwsUtils { /** * Returns the canonicalized AMZ headers. - * - * @param requestHeaders - * The list of request headers. + * + * @param requestHeaders The list of request headers. * @return The canonicalized AMZ headers. */ - public static String getCanonicalizedAmzHeaders( - Series

requestHeaders) { + public static String getCanonicalizedAmzHeaders(Series
requestHeaders) { StringBuilder sb = new StringBuilder(); Pattern spacePattern = Pattern.compile("\\s+"); - // Create a lexographically sorted list of headers that begin with x-amz - SortedMap amzHeaders = new TreeMap(); + // Create a lexicographically sorted list of headers that begin with x-amz + SortedMap amzHeaders = new TreeMap<>(); if (requestHeaders != null) { for (Header header : requestHeaders) { @@ -66,8 +60,7 @@ public static String getCanonicalizedAmzHeaders( if (amzHeaders.containsKey(name)) value = amzHeaders.get(name) + "," + header.getValue(); - else - value = header.getValue(); + else value = header.getValue(); // All newlines and multiple spaces must be replaced with a // single space character. @@ -81,8 +74,7 @@ public static String getCanonicalizedAmzHeaders( // Concatenate all AMZ headers for (Entry entry : amzHeaders.entrySet()) { - sb.append(entry.getKey()).append(':').append(entry.getValue()) - .append("\n"); + sb.append(entry.getKey()).append(':').append(entry.getValue()).append("\n"); } return sb.toString(); @@ -90,121 +82,97 @@ public static String getCanonicalizedAmzHeaders( /** * Returns the canonicalized resource name. - * - * @param reference - * The resource reference + * + * @param reference The resource reference * @return The canonicalized resource name. */ public static String getCanonicalizedResourceName(Reference reference) { String hostName = reference.getHostDomain(); String path = reference.getPath(); - Pattern hostNamePattern = Pattern - .compile("s3[a-z0-1\\-]*.amazonaws.com"); + Pattern hostNamePattern = Pattern.compile("s3[a-z0-1\\-]*.amazonaws.com"); StringBuilder sb = new StringBuilder(); // Append the bucket if (hostName != null) { // If the host name contains a port number, remove it - if (hostName.contains(":")) - hostName = hostName.substring(0, hostName.indexOf(":")); + if (hostName.contains(":")) hostName = hostName.substring(0, hostName.indexOf(':')); Matcher hostNameMatcher = hostNamePattern.matcher(hostName); if (hostName.endsWith(".s3.amazonaws.com")) { - String bucketName = hostName.substring(0, - hostName.length() - 17); + String bucketName = hostName.substring(0, hostName.length() - 17); sb.append("/").append(bucketName); } else if (!hostNameMatcher.matches()) { sb.append("/").append(hostName); } } - int queryIdx = path.indexOf("?"); + int queryIdx = path.indexOf('?'); // Append the resource path - if (queryIdx >= 0) - sb.append(path, 0, queryIdx); - else - sb.append(path); + if (queryIdx >= 0) sb.append(path, 0, queryIdx); + else sb.append(path); // Append the AWS sub-resource if (queryIdx >= 0) { String query = path.substring(queryIdx - 1); - if (query.contains("?acl")) - sb.append("?acl"); - else if (query.contains("?location")) - sb.append("?location"); - else if (query.contains("?logging")) - sb.append("?logging"); - else if (query.contains("?torrent")) - sb.append("?torrent"); + if (query.contains("?acl")) sb.append("?acl"); + else if (query.contains("?location")) sb.append("?location"); + else if (query.contains("?logging")) sb.append("?logging"); + else if (query.contains("?torrent")) sb.append("?torrent"); } return sb.toString(); } /** - * Returns the AWS authentication compatible signature for the given string - * to sign and secret. - * - * @param stringToSign - * The string to sign. - * @param secret - * The user secret to sign with + * Returns the AWS authentication compatible signature for the given string to sign and secret. + * + * @param stringToSign The string to sign. + * @param secret The user secret to sign with * @return The AWS compatible signature */ public static String getHmacSha1Signature(String stringToSign, char[] secret) { - return Base64.getEncoder().encodeToString( - DigestUtils.toHMacSha1(stringToSign, - IoUtils.toByteArray(secret))); + return Base64.getEncoder() + .encodeToString(DigestUtils.toHMacSha1(stringToSign, IoUtils.toByteArray(secret))); } /** - * Returns the AWS authentication compatible signature for the given string - * to sign and secret. - * - * @param stringToSign - * The string to sign. - * @param secret - * The user secret to sign with + * Returns the AWS authentication compatible signature for the given string to sign and secret. + * + * @param stringToSign The string to sign. + * @param secret The user secret to sign with * @return The AWS compatible signature */ - public static String getHmacSha256Signature(String stringToSign, - char[] secret) { - return Base64.getEncoder().encodeToString( - DigestUtils.toHMacSha256(stringToSign, - IoUtils.toByteArray(secret))); + public static String getHmacSha256Signature(String stringToSign, char[] secret) { + return Base64.getEncoder() + .encodeToString( + DigestUtils.toHMacSha256(stringToSign, IoUtils.toByteArray(secret))); } /** - * Returns the AWS SimpleDB authentication compatible signature for the - * given request and secret. - * - * @param method - * The request method. - * @param resourceRef - * The target resource reference. - * @param params - * The request parameters. - * @param secret - * The user secret to sign with + * Returns the AWS SimpleDB authentication compatible signature for the given request and + * secret. + * + * @param method The request method. + * @param resourceRef The target resource reference. + * @param params The request parameters. + * @param secret The user secret to sign with * @return The AWS SimpleDB compatible signature */ - public static String getQuerySignature(Method method, - Reference resourceRef, List params, char[] secret) { - return getHmacSha256Signature( - getQueryStringToSign(method, resourceRef, params), secret); + public static String getQuerySignature( + Method method, Reference resourceRef, List params, char[] secret) { + return getHmacSha256Signature(getQueryStringToSign(method, resourceRef, params), secret); } /** * Returns the SimpleDB string to sign. - * - * @param resourceRef - * The target resource reference. + * + * @param resourceRef The target resource reference. * @return The string to sign. */ - public static String getQueryStringToSign(Method method, - Reference resourceRef, List params) { + public static String getQueryStringToSign( + Method method, Reference resourceRef, List params) { StringBuilder toSign = new StringBuilder(); // Append HTTP method @@ -232,8 +200,7 @@ public static String getQueryStringToSign(Method method, toSign.append(Reference.encode(param.getName())); if (param.getValue() != null) { - toSign.append('=').append( - Reference.encode(param.getValue(), true)); + toSign.append('=').append(Reference.encode(param.getValue(), true)); } } @@ -241,86 +208,76 @@ public static String getQueryStringToSign(Method method, } /** - * Returns the AWS S3 authentication compatible signature for the given - * request and secret. - * - * @param request - * The request to create the signature for - * @param secret - * The user secret to sign with + * Returns the AWS S3 authentication compatible signature for the given request and secret. + * + * @param request The request to create the signature for + * @param secret The user secret to sign with * @return The AWS S3 compatible signature */ public static String getS3Signature(Request request, char[] secret) { @SuppressWarnings("unchecked") - Series
headers = (Series
) request.getAttributes().get( - HeaderConstants.ATTRIBUTE_HEADERS); + Series
headers = + (Series
) request.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS); return getS3Signature(request, headers, secret); } /** - * Returns the AWS S3 authentication compatible signature for the given - * request and secret. - * - * @param request - * The request to create the signature for - * @param headers - * The HTTP headers associated with the request - * @param secret - * The user secret to sign with + * Returns the AWS S3 authentication compatible signature for the given request and secret. + * + * @param request The request to create the signature for + * @param headers The HTTP headers associated with the request + * @param secret The user secret to sign with * @return The AWS S3 compatible signature */ - public static String getS3Signature(Request request, - Series
headers, char[] secret) { + public static String getS3Signature(Request request, Series
headers, char[] secret) { return getHmacSha1Signature(getS3StringToSign(request, headers), secret); } /** * Returns the string to sign. - * - * @param request - * The request to generate the signature string from + * + * @param request The request to generate the signature string from * @return The string to sign */ public static String getS3StringToSign(Request request) { @SuppressWarnings("unchecked") - Series
headers = (Series
) request.getAttributes().get( - HeaderConstants.ATTRIBUTE_HEADERS); + Series
headers = + (Series
) request.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS); return getS3StringToSign(request, headers); } /** * Returns the S3 string to sign. - * - * @param request - * The request to generate the signature string from - * @param headers - * The HTTP headers associated with the request + * + * @param request The request to generate the signature string from + * @param headers The HTTP headers associated with the request * @return The string to sign */ - public static String getS3StringToSign(Request request, - Series
headers) { + public static String getS3StringToSign(Request request, Series
headers) { String canonicalizedAmzHeaders = getCanonicalizedAmzHeaders(headers); - String canonicalizedResource = getCanonicalizedResourceName(request - .getResourceRef()); - String contentMD5 = (headers == null) ? null : headers.getFirstValue( - HeaderConstants.HEADER_CONTENT_MD5, true); - String contentType = (headers == null) ? null : headers.getFirstValue( - HeaderConstants.HEADER_CONTENT_TYPE, true); - String date = (headers == null) ? null : headers.getFirstValue( - "X-Amz-Date", true); + String canonicalizedResource = getCanonicalizedResourceName(request.getResourceRef()); + String contentMD5 = + (headers == null) + ? null + : headers.getFirstValue(HeaderConstants.HEADER_CONTENT_MD5, true); + String contentType = + (headers == null) + ? null + : headers.getFirstValue(HeaderConstants.HEADER_CONTENT_TYPE, true); + String date = (headers == null) ? null : headers.getFirstValue("X-Amz-Date", true); String method = request.getMethod().getName(); - // If amazon's date header wasn't found, try to grab the regular date - // header + // If amazon's date header wasn't found, try to grab the regular date header if (date == null || (date.isEmpty())) { - date = (headers == null) ? null : headers.getFirstValue( - HeaderConstants.HEADER_DATE, true); + date = + (headers == null) + ? null + : headers.getFirstValue(HeaderConstants.HEADER_DATE, true); } // If no date header exists, make one if (date == null || (date.isEmpty())) { - date = DateUtils.format(new Date(), - DateUtils.FORMAT_RFC_1123.get(0)); + date = DateUtils.format(new Date(), DateUtils.FORMAT_RFC_1123.getFirst()); if (headers != null) { headers.add(HeaderConstants.HEADER_DATE, date); } @@ -331,12 +288,9 @@ public static String getS3StringToSign(Request request, // This patch seems to apply to Sun JVM only. final String jvmVendor = System.getProperty("java.vm.vendor"); - if ((jvmVendor != null) - && (jvmVendor.toLowerCase()).startsWith("sun")) { - final int majorVersionNumber = SystemUtils - .getJavaMajorVersion(); - final int minorVersionNumber = SystemUtils - .getJavaMinorVersion(); + if ((jvmVendor != null) && (jvmVendor.toLowerCase()).startsWith("sun")) { + final int majorVersionNumber = SystemUtils.getJavaMajorVersion(); + final int minorVersionNumber = SystemUtils.getJavaMinorVersion(); if (majorVersionNumber == 1) { if (minorVersionNumber < 5) { @@ -352,16 +306,15 @@ public static String getS3StringToSign(Request request, contentType = "application/x-www-form-urlencoded"; } - StringBuilder toSign = new StringBuilder(); - toSign.append(method != null ? method : "").append("\n"); - toSign.append(contentMD5 != null ? contentMD5 : "").append("\n"); - toSign.append(contentType != null ? contentType : "").append("\n"); - toSign.append(date != null ? date : "").append("\n"); - toSign.append(canonicalizedAmzHeaders != null ? canonicalizedAmzHeaders - : ""); - toSign.append(canonicalizedResource != null ? canonicalizedResource - : ""); - - return toSign.toString(); + return (method != null ? method : "") + + "\n" + + (contentMD5 != null ? contentMD5 : "") + + "\n" + + (contentType != null ? contentType : "") + + "\n" + + (date != null ? date : "") + + "\n" + + (canonicalizedAmzHeaders != null ? canonicalizedAmzHeaders : "") + + (canonicalizedResource != null ? canonicalizedResource : ""); } } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/AwsVerifier.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/AwsVerifier.java index 9a598d797e..ae5548d0f0 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/AwsVerifier.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/AwsVerifier.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; import org.restlet.Request; @@ -20,31 +19,23 @@ import org.restlet.util.Series; /** - * Wrapped verifier that can verify HTTP requests using the Amazon S3 - * authentication scheme. Verifies the user by computing the request signature - * using the local secret and comparing it to the signature provided in the - * request. - *

- * Per the Amazon S3 specification the {@code Date} header is required. If the - * {@code Date} header is missing or the request is older than the allowed time - * limit, specified by the {@code maxRequestAge} property, the request fails - * verification. - * + * Wrapped verifier that can verify HTTP requests using the Amazon S3 authentication scheme. + * Verifies the user by computing the request signature using the local secret and comparing it to + * the signature provided in the request. + * + *

Per the Amazon S3 specification the {@code Date} header is required. If the {@code Date} + * header is missing or the request is older than the allowed time limit, specified by the {@code + * maxRequestAge} property, the request fails verification. + * * @author Jean-Philippe Steinmetz - * @see - * Authenticating REST Requests + * @see + * Authenticating REST Requests */ public class AwsVerifier extends SecretVerifier { - /** - * Default maximum request age (15 minutes) - */ + /** Default maximum request age (15 minutes) */ private static final long DEFAULT_MAX_REQUEST_AGE = 15 * 60 * 1000L; - /** - * The maximum age of a request, in milliseconds, before it is considered - * stale. - */ + /** The maximum age of a request, in milliseconds, before it is considered stale. */ private long maxRequestAge; /** The local secret verifier. */ @@ -52,10 +43,8 @@ public class AwsVerifier extends SecretVerifier { /** * Creates a new HttpAwsS3Verifier instance. - * - * @param wrappedVerifier - * The wrapped verifier containing local identifier/secret - * couples + * + * @param wrappedVerifier The wrapped verifier containing local identifier/secret couples */ public AwsVerifier(LocalVerifier wrappedVerifier) { this(wrappedVerifier, DEFAULT_MAX_REQUEST_AGE); @@ -63,13 +52,10 @@ public AwsVerifier(LocalVerifier wrappedVerifier) { /** * Creates a new HttpAwsS3Verifier instance. - * - * @param wrappedVerifier - * The wrapped verifier containing local identifier/secret - * couples - * @param maxRequestAge - * The maximum age of a request, in milliseconds, before it is - * considered stale + * + * @param wrappedVerifier The wrapped verifier containing local identifier/secret couples + * @param maxRequestAge The maximum age of a request, in milliseconds, before it is considered + * stale */ public AwsVerifier(LocalVerifier wrappedVerifier, long maxRequestAge) { super(); @@ -78,34 +64,26 @@ public AwsVerifier(LocalVerifier wrappedVerifier, long maxRequestAge) { } /** - * Returns the user identifier portion of an Amazon S3 compatible - * {@code Authorization} header. - *

- * An Amazon S3 compatible {@code Authorization} header has the following - * pattern.
+ * Returns the user identifier portion of an Amazon S3 compatible {@code Authorization} header. + * + *

An Amazon S3 compatible {@code Authorization} header has the following pattern.
* {@code Authorization: AWS id:signature} */ @Override protected String getIdentifier(Request request, Response response) { if (request.getChallengeResponse() == null - || request.getChallengeResponse().getRawValue() == null) - return null; + || request.getChallengeResponse().getRawValue() == null) return null; - String[] parts = request.getChallengeResponse().getRawValue() - .split(":"); + String[] parts = request.getChallengeResponse().getRawValue().split(":"); - if (parts != null && parts.length == 2) - return parts[0]; - else - return null; + return (parts.length == 2) ? parts[0] : null; } /** - * Returns the local secret associated to a given identifier. - * - * @param identifier - * The identifier to lookup. - * @return The secret associated to the identifier or null. + * Returns the local secret associated with a given identifier. + * + * @param identifier The identifier to lookup. + * @return The secret associated with the identifier or null. */ public char[] getLocalSecret(String identifier) { char[] result = null; @@ -114,42 +92,33 @@ public char[] getLocalSecret(String identifier) { } /** - * Returns the maximum age of a request, in milliseconds, before it is - * considered stale. - *

- * A negative or zero value indicates no age restriction. The default value - * is 15 minutes. + * Returns the maximum age of a request, in milliseconds, before it is considered stale. + * + *

A negative or zero value indicates no age restriction. The default value is 15 minutes. */ public long getMaxRequestAge() { return this.maxRequestAge; } /** - * Returns the signature portion of an Amazon S3 compatible - * {@code Authorization} header. - *

- * An Amazon S3 compatible {@code Authorization} header has the following - * pattern.
+ * Returns the signature portion of an Amazon S3 compatible {@code Authorization} header. + * + *

An Amazon S3 compatible {@code Authorization} header has the following pattern.
* {@code Authorization: AWS id:signature} */ @Override protected char[] getSecret(Request request, Response response) { if (request.getChallengeResponse() == null - || request.getChallengeResponse().getRawValue() == null) - return null; + || request.getChallengeResponse().getRawValue() == null) return null; - String[] parts = request.getChallengeResponse().getRawValue() - .split(":"); + String[] parts = request.getChallengeResponse().getRawValue().split(":"); - if (parts != null && parts.length == 2) - return parts[1].toCharArray(); - else - return null; + return (parts.length == 2) ? parts[1].toCharArray() : null; } /** * Returns the wrapped local secret verifier. - * + * * @return The local secret verifier. */ public LocalVerifier getWrappedVerifier() { @@ -157,23 +126,19 @@ public LocalVerifier getWrappedVerifier() { } /** - * Sets the maximum age of a request, in milliseconds, before it is - * considered stale. - *

- * A negative or zero value indicates no age restriction. The default value - * is 15 minutes. + * Sets the maximum age of a request, in milliseconds, before it is considered stale. + * + *

A negative or zero value indicates no age restriction. The default value is 15 minutes. */ public void setMaxRequestAge(long value) { - if (value < 0) - value = 0; + if (value < 0) value = 0; this.maxRequestAge = value; } /** * Sets the wrapped local secret verifier. - * - * @param wrappedVerifier - * The local secret verifier. + * + * @param wrappedVerifier The local secret verifier. */ public void setWrappedVerifier(LocalVerifier wrappedVerifier) { this.wrappedVerifier = wrappedVerifier; @@ -181,37 +146,32 @@ public void setWrappedVerifier(LocalVerifier wrappedVerifier) { @Override public int verify(Request request, Response response) { - if (request.getChallengeResponse() == null) - return RESULT_MISSING; + if (request.getChallengeResponse() == null) return RESULT_MISSING; @SuppressWarnings("unchecked") - Series

headers = (Series
) request.getAttributes().get( - HeaderConstants.ATTRIBUTE_HEADERS); + Series
headers = + (Series
) request.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS); String userId = getIdentifier(request, response); - if (userId == null || (userId.length() == 0)) - return RESULT_MISSING; + if (userId == null || (userId.isEmpty())) return RESULT_MISSING; // A date header is always required - if (headers.getFirstValue(HeaderConstants.HEADER_DATE, true) == null) - return RESULT_INVALID; + if (headers.getFirstValue(HeaderConstants.HEADER_DATE, true) == null) return RESULT_INVALID; // Make sure the date is not stale if (getMaxRequestAge() > 0) { - Long date = DateUtils.parse( - headers.getFirstValue(HeaderConstants.HEADER_DATE, true)) - .getTime(); + Long date = + DateUtils.parse(headers.getFirstValue(HeaderConstants.HEADER_DATE, true)) + .getTime(); Long now = System.currentTimeMillis(); - if (now - date > getMaxRequestAge()) - return RESULT_STALE; + if (now - date > getMaxRequestAge()) return RESULT_STALE; } char[] userSecret = getLocalSecret(userId); char[] signature = getSecret(request, response); String sigToCompare = AwsUtils.getS3Signature(request, userSecret); - if (!compare(signature, sigToCompare.toCharArray())) - return RESULT_INVALID; + if (!compare(signature, sigToCompare.toCharArray())) return RESULT_INVALID; request.getClientInfo().setUser(new User(userId)); @@ -219,13 +179,11 @@ public int verify(Request request, Response response) { } /** - * This function is not implemented because the authorization scheme - * requires direct access to the request. See - * {@link #verify(Request, Response)}. + * This function is not implemented because the authorization scheme requires direct access to + * the request. See {@link #verify(Request, Response)}. */ @Override - public int verify(String identifier, char[] secret) - throws IllegalArgumentException { + public int verify(String identifier, char[] secret) throws IllegalArgumentException { throw new RuntimeException("Method not implemented"); } } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/CryptoUtils.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/CryptoUtils.java index 5e548499f7..1534462157 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/CryptoUtils.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/CryptoUtils.java @@ -1,26 +1,23 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.util.Base64; - import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; - import org.restlet.ext.crypto.DigestUtils; /** * Simple usage of standard cipher features from JRE. - * + * * @author Remi Dewitte * @author Jerome Louvel */ @@ -28,19 +25,16 @@ public final class CryptoUtils { /** * Creates a cipher for a given algorithm and secret. - * - * @param algorithm - * The cryptographic algorithm. - * @param secretKey - * The cryptographic secret. - * @param mode - * The cipher mode, either {@link Cipher#ENCRYPT_MODE} or - * {@link Cipher#DECRYPT_MODE}. + * + * @param algorithm The cryptographic algorithm. + * @param secretKey The cryptographic secret. + * @param mode The cipher mode, either {@link Cipher#ENCRYPT_MODE} or {@link + * Cipher#DECRYPT_MODE}. * @return The new cipher. * @throws GeneralSecurityException */ - private static Cipher createCipher(String algorithm, byte[] secretKey, - int mode) throws GeneralSecurityException { + private static Cipher createCipher(String algorithm, byte[] secretKey, int mode) + throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(algorithm); cipher.init(mode, new SecretKeySpec(secretKey, algorithm)); return cipher; @@ -48,117 +42,99 @@ private static Cipher createCipher(String algorithm, byte[] secretKey, /** * Decrypts a bytes array. - * - * @param algo - * The cryptographic algorithm. - * @param secretKey - * The cryptographic secret key. - * @param encrypted - * The encrypted bytes. + * + * @param algo The cryptographic algorithm. + * @param secretKey The cryptographic secret key. + * @param encrypted The encrypted bytes. * @return The decrypted content string. * @throws GeneralSecurityException */ public static String decrypt(String algo, byte[] secretKey, byte[] encrypted) throws GeneralSecurityException { - byte[] original = doFinal(algo, secretKey, Cipher.DECRYPT_MODE, - encrypted); - return new String(original, Charset.forName("UTF-8")); + byte[] original = doFinal(algo, secretKey, Cipher.DECRYPT_MODE, encrypted); + return new String(original, StandardCharsets.UTF_8); } /** * Decrypts a bytes array. - * - * @param algo - * The cryptographic algorithm. - * @param base64Secret - * The cryptographic secret key, encoded as a Base64 string. - * @param encrypted - * The encrypted bytes. + * + * @param algo The cryptographic algorithm. + * @param base64Secret The cryptographic secret key, encoded as a Base64 string. + * @param encrypted The encrypted bytes. * @return The decrypted content string. * @throws GeneralSecurityException */ - public static String decrypt(String algo, String base64Secret, - byte[] encrypted) throws GeneralSecurityException { + public static String decrypt(String algo, String base64Secret, byte[] encrypted) + throws GeneralSecurityException { return decrypt(algo, Base64.getDecoder().decode(base64Secret), encrypted); } /** * Does final processing. - * - * @param algo - * The cryptographic algorithm. - * @param secretKey - * The cryptographic secret key. - * @param mode - * The processing mode, either {@link Cipher#DECRYPT_MODE} or - * {@link Cipher#ENCRYPT_MODE}. - * @param what - * The byte array to process. + * + * @param algo The cryptographic algorithm. + * @param secretKey The cryptographic secret key. + * @param mode The processing mode, either {@link Cipher#DECRYPT_MODE} or {@link + * Cipher#ENCRYPT_MODE}. + * @param what The byte array to process. * @return The processed byte array. * @throws GeneralSecurityException */ - private static byte[] doFinal(String algo, byte[] secretKey, int mode, - byte[] what) throws GeneralSecurityException { + private static byte[] doFinal(String algo, byte[] secretKey, int mode, byte[] what) + throws GeneralSecurityException { return createCipher(algo, secretKey, mode).doFinal(what); } /** * Encrypts a content string. - * - * @param algo - * The cryptographic algorithm. - * @param secretKey - * The cryptographic secret key. - * @param content - * The content string to encrypt. + * + * @param algo The cryptographic algorithm. + * @param secretKey The cryptographic secret key. + * @param content The content string to encrypt. * @return The encrypted bytes. * @throws GeneralSecurityException */ public static byte[] encrypt(String algo, byte[] secretKey, String content) throws GeneralSecurityException { - return doFinal(algo, secretKey, Cipher.ENCRYPT_MODE, content.getBytes(Charset.forName("UTF-8"))); + return doFinal( + algo, secretKey, Cipher.ENCRYPT_MODE, content.getBytes(StandardCharsets.UTF_8)); } /** * Encrypts a content string. - * - * @param algo - * The cryptographic algorithm. - * @param base64Secret - * The cryptographic secret, encoded as a Base64 string. - * @param content - * The content string to encrypt. + * + * @param algo The cryptographic algorithm. + * @param base64Secret The cryptographic secret, encoded as a Base64 string. + * @param content The content string to encrypt. * @return The encrypted bytes. * @throws GeneralSecurityException */ - public static byte[] encrypt(String algo, String base64Secret, - String content) throws GeneralSecurityException { + public static byte[] encrypt(String algo, String base64Secret, String content) + throws GeneralSecurityException { return encrypt(algo, Base64.getDecoder().decode(base64Secret), content); } /** - * Generates a nonce as recommended in section 3.2.1 of RFC-2617, but - * without the ETag field. The format is:

+     * Generates nonce as recommended in section 3.2.1 of RFC-2617, but without the ETag field. The
+     * format is: 
      * Base64.encodeBytes(currentTimeMS + ":"
      *         + md5String(currentTimeMS + ":" + secretKey))
      * 
- * - * @param secretKey - * a secret value known only to the creator of the nonce. It's - * inserted into the nonce, and can be used later to validate the - * nonce. + * + * @param secretKey a secret value known only to the creator of the nonce. It's inserted into + * the nonce and can be used later to validate the nonce. */ public static String makeNonce(String secretKey) { final long currentTimeMS = System.currentTimeMillis(); - return Base64.getEncoder().encodeToString( - (currentTimeMS + ":" + DigestUtils.toMd5(currentTimeMS + ":" - + secretKey)).getBytes()); + return Base64.getEncoder() + .encodeToString( + (currentTimeMS + ":" + DigestUtils.toMd5(currentTimeMS + ":" + secretKey)) + .getBytes()); } /** - * Private constructor to ensure that the class acts as a true utility class - * i.e. it isn't instantiable and extensible. + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. */ - private CryptoUtils() { - } + private CryptoUtils() {} } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsQueryHelper.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsQueryHelper.java index 3f5f5ccf4e..d2e5747c8e 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsQueryHelper.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsQueryHelper.java @@ -1,16 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; import java.util.Date; - import org.restlet.Request; import org.restlet.data.ChallengeResponse; import org.restlet.data.ChallengeScheme; @@ -21,21 +19,19 @@ /** * Implements the HTTP authentication for the Amazon Web Services. - * + * * @author Jerome Louvel */ public class HttpAwsQueryHelper extends AuthenticatorHelper { - /** - * Constructor. - */ + /** Constructor. */ public HttpAwsQueryHelper() { super(ChallengeScheme.HTTP_AWS_QUERY, true, false); } @Override - public Reference updateReference(Reference resourceRef, - ChallengeResponse challengeResponse, Request request) { + public Reference updateReference( + Reference resourceRef, ChallengeResponse challengeResponse, Request request) { Reference result = resourceRef; Form query = result.getQueryAsForm(); @@ -44,13 +40,16 @@ public Reference updateReference(Reference resourceRef, query.add("SignatureMethod", "HmacSHA256"); query.add("SignatureVersion", "2"); query.add("Version", "2009-04-15"); - String df = DateUtils.format(new Date(), DateUtils.FORMAT_ISO_8601.get(0)); + String df = DateUtils.format(new Date(), DateUtils.FORMAT_ISO_8601.getFirst()); query.add("Timestamp", df); // Compute then add the signature parameter - String signature = AwsUtils.getQuerySignature(request.getMethod(), - request.getResourceRef(), query, request - .getChallengeResponse().getSecret()); + String signature = + AwsUtils.getQuerySignature( + request.getMethod(), + request.getResourceRef(), + query, + request.getChallengeResponse().getSecret()); query.add("Signature", signature); result = new Reference(resourceRef); result.setQuery(query.getQueryString()); diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsS3Helper.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsS3Helper.java index 23be92e432..7097b396cb 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsS3Helper.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAwsS3Helper.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; import org.restlet.Request; @@ -19,26 +18,25 @@ /** * Implements the HTTP authentication for the Amazon S3 service. - * + * * @author Jerome Louvel */ public class HttpAwsS3Helper extends AuthenticatorHelper { - /** - * Constructor. - */ + /** Constructor. */ public HttpAwsS3Helper() { super(ChallengeScheme.HTTP_AWS_S3, true, true); } @Override - public void formatResponse(ChallengeWriter cw, ChallengeResponse challenge, - Request request, Series

httpHeaders) { + public void formatResponse( + ChallengeWriter cw, + ChallengeResponse challenge, + Request request, + Series
httpHeaders) { // Append the AWS credentials cw.append(challenge.getIdentifier()) .append(':') - .append(AwsUtils.getS3Signature(request, httpHeaders, - challenge.getSecret())); + .append(AwsUtils.getS3Signature(request, httpHeaders, challenge.getSecret())); } - } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyHelper.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyHelper.java index 63635b8667..a6bf2d1316 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyHelper.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyHelper.java @@ -1,20 +1,18 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; import java.util.Base64; import java.util.Date; -import java.util.Iterator; +import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; - import org.restlet.Request; import org.restlet.data.ChallengeResponse; import org.restlet.data.ChallengeScheme; @@ -33,49 +31,41 @@ import org.restlet.util.Series; /** - * Implements the Shared Key authentication for Azure services. This concerns - * Blob and Queues on Azure Storage.
+ * Implements the Shared Key authentication for Azure services. This concerns Blob and Queues on + * Azure Storage.
*
* More documentation is available here - * + * * @author Thierry Boileau */ public class HttpAzureSharedKeyHelper extends AuthenticatorHelper { /** * Returns the canonicalized Azure headers. - * - * @param requestHeaders - * The list of request headers. + * + * @param requestHeaders The list of request headers. * @return The canonicalized Azure headers. */ - private static String getCanonicalizedAzureHeaders( - Series

requestHeaders) { + private static String getCanonicalizedAzureHeaders(Series
requestHeaders) { // Filter out all the Azure headers required for SharedKey // authentication - SortedMap azureHeaders = new TreeMap(); + SortedMap azureHeaders = new TreeMap<>(); String headerName; for (Header header : requestHeaders) { headerName = header.getName().toLowerCase(); if (headerName.startsWith("x-ms-")) { - if (!azureHeaders.containsKey(headerName)) { - azureHeaders.put(headerName, - requestHeaders.getValues(headerName)); - } + azureHeaders.computeIfAbsent(headerName, requestHeaders::getValues); } } // Concatenate all Azure headers StringBuilder sb = new StringBuilder(); - for (Iterator iterator = azureHeaders.keySet().iterator(); iterator - .hasNext();) { - String key = iterator.next(); - sb.append(key).append(':').append(azureHeaders.get(key)) - .append("\n"); + for (Map.Entry entry : azureHeaders.entrySet()) { + sb.append(entry.getKey()).append(':').append(entry.getValue()).append("\n"); } return sb.toString(); @@ -83,9 +73,8 @@ private static String getCanonicalizedAzureHeaders( /** * Returns the canonicalized resource name. - * - * @param resourceRef - * The resource reference. + * + * @param resourceRef The resource reference. * @return The canonicalized resource name. */ private static String getCanonicalizedResourceName(Reference resourceRef) { @@ -93,100 +82,108 @@ private static String getCanonicalizedResourceName(Reference resourceRef) { Parameter param = form.getFirst("comp", true); if (param != null) { - StringBuilder sb = new StringBuilder(resourceRef.getPath()); - return sb.append("?").append("comp=").append(param.getValue()) - .toString(); + return resourceRef.getPath() + "?" + "comp=" + param.getValue(); } return resourceRef.getPath(); } - /** - * Constructor. - */ + /** Constructor. */ public HttpAzureSharedKeyHelper() { super(ChallengeScheme.HTTP_AZURE_SHAREDKEY, true, false); } @Override - public void formatResponse(ChallengeWriter cw, ChallengeResponse challenge, - Request request, Series
httpHeaders) { - - // Setup the method name - final String methodName = request.getMethod().getName(); + public void formatResponse( + ChallengeWriter cw, + ChallengeResponse challenge, + Request request, + Series
httpHeaders) { + + // Set up the message part + final String rest = + request.getMethod().getName() + + '\n' + + getContentMd5(httpHeaders) + + '\n' + + getContentTypeHeader(request, httpHeaders) + + '\n' + + getDateHeader(httpHeaders) + + '\n' + + getCanonicalizedAzureHeaders(httpHeaders) + + '/' + + challenge.getIdentifier() + + getCanonicalizedResourceName(request.getResourceRef()); - // Setup the Date header - String date = ""; + // Append the SharedKey credentials + cw.append(challenge.getIdentifier()) + .append(':') + .append( + Base64.getEncoder() + .encodeToString( + DigestUtils.toHMacSha256( + rest, + Base64.getDecoder() + .decode( + IoUtils.toByteArray( + challenge.getSecret()))))); + } - if (httpHeaders.getFirstValue("x-ms-date", true) == null) { - // X-ms-Date header didn't override the standard Date header - date = httpHeaders.getFirstValue(HeaderConstants.HEADER_DATE, true); - if (date == null) { - // Add a fresh Date header - date = DateUtils.format(new Date(), - DateUtils.FORMAT_RFC_1123.get(0)); - httpHeaders.add(HeaderConstants.HEADER_DATE, date); - } - } - // Setup the ContentType header - String contentMd5 = httpHeaders.getFirstValue( - HeaderConstants.HEADER_CONTENT_MD5, true); + private static String getContentMd5(final Series
httpHeaders) { + String contentMd5 = httpHeaders.getFirstValue(HeaderConstants.HEADER_CONTENT_MD5, true); if (contentMd5 == null) { contentMd5 = ""; } + return contentMd5; + } - // Setup the ContentType header - String contentType = httpHeaders.getFirstValue( - HeaderConstants.HEADER_CONTENT_TYPE, true); - if (contentType == null) { - boolean applyPatch = false; - - // This patch seems to apply to Sun JVM only. - final String jvmVendor = System.getProperty("java.vm.vendor"); - if ((jvmVendor != null) - && (jvmVendor.toLowerCase()).startsWith("sun")) { - final int majorVersionNumber = SystemUtils - .getJavaMajorVersion(); - final int minorVersionNumber = SystemUtils - .getJavaMinorVersion(); - - if (majorVersionNumber == 1) { - if (minorVersionNumber < 5) { - applyPatch = true; - } else if (minorVersionNumber == 5) { - // Sun fixed the bug in update 10 - applyPatch = (SystemUtils.getJavaUpdateVersion() < 10); - } - } - } + private static String getContentTypeHeader( + final Request request, final Series
httpHeaders) { + String contentType = httpHeaders.getFirstValue(HeaderConstants.HEADER_CONTENT_TYPE, true); + + if (contentType != null) { + return contentType; + } + + boolean applyPatch = false; + + // This patch seems to apply to Sun JVM only. + final String jvmVendor = System.getProperty("java.vm.vendor"); + if ((jvmVendor != null) && (jvmVendor.toLowerCase()).startsWith("sun")) { + final int majorVersionNumber = SystemUtils.getJavaMajorVersion(); + final int minorVersionNumber = SystemUtils.getJavaMinorVersion(); - if (applyPatch && !request.getMethod().equals(Method.PUT)) { - contentType = "application/x-www-form-urlencoded"; - } else { - contentType = ""; + if (majorVersionNumber == 1) { + if (minorVersionNumber < 5) { + applyPatch = true; + } else if (minorVersionNumber == 5) { + // Sun fixed the bug in update 10 + applyPatch = (SystemUtils.getJavaUpdateVersion() < 10); + } } } - // Setup the canonicalized AzureHeaders - final String canonicalizedAzureHeaders = getCanonicalizedAzureHeaders(httpHeaders); + if (applyPatch && !request.getMethod().equals(Method.PUT)) { + contentType = "application/x-www-form-urlencoded"; + } else { + contentType = ""; + } - // Setup the canonicalized path - final String canonicalizedResource = getCanonicalizedResourceName(request - .getResourceRef()); + return contentType; + } - // Setup the message part - final StringBuilder rest = new StringBuilder(); - rest.append(methodName).append('\n').append(contentMd5).append('\n') - .append(contentType).append('\n').append(date).append('\n') - .append(canonicalizedAzureHeaders).append('/') - .append(challenge.getIdentifier()) - .append(canonicalizedResource); + private static String getDateHeader(final Series
httpHeaders) { + String date = ""; - // Append the SharedKey credentials - cw.append(challenge.getIdentifier()) - .append(':') - .append(Base64.getEncoder().encodeToString( - DigestUtils.toHMacSha256(rest.toString(), - Base64.getDecoder().decode(IoUtils.toByteArray(challenge.getSecret()))))); + if (httpHeaders.getFirstValue("x-ms-date", true) == null) { + // X-ms-Date header didn't override the standard Date header + date = httpHeaders.getFirstValue(HeaderConstants.HEADER_DATE, true); + if (date == null) { + // Add a fresh Date header + date = DateUtils.format(new Date(), DateUtils.FORMAT_RFC_1123.getFirst()); + httpHeaders.add(HeaderConstants.HEADER_DATE, date); + } + } + return date; } } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyLiteHelper.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyLiteHelper.java index 12d87aadbf..c80866f9ae 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyLiteHelper.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpAzureSharedKeyLiteHelper.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; import java.util.Base64; import java.util.Date; - import org.restlet.Request; import org.restlet.data.ChallengeResponse; import org.restlet.data.ChallengeScheme; @@ -28,21 +26,20 @@ import org.restlet.util.Series; /** - * Implements the Shared Key Lite authentication for Azure services. This - * concerns Table storage on Azure Storage.
+ * Implements the Shared Key Lite authentication for Azure services. This concerns Table storage on + * Azure Storage.
*
* More documentation is available here - * + * * @author Thierry Boileau */ public class HttpAzureSharedKeyLiteHelper extends AuthenticatorHelper { /** * Returns the canonicalized resource name. - * - * @param resourceRef - * The resource reference. + * + * @param resourceRef The resource reference. * @return The canonicalized resource name. */ private static String getCanonicalizedResourceName(Reference resourceRef) { @@ -50,26 +47,25 @@ private static String getCanonicalizedResourceName(Reference resourceRef) { Parameter param = form.getFirst("comp", true); if (param != null) { - StringBuilder sb = new StringBuilder(resourceRef.getPath()); - return sb.append("?").append("comp=").append(param.getValue()) - .toString(); + return resourceRef.getPath() + "?" + "comp=" + param.getValue(); } return resourceRef.getPath(); } - /** - * Constructor. - */ + /** Constructor. */ public HttpAzureSharedKeyLiteHelper() { super(ChallengeScheme.HTTP_AZURE_SHAREDKEY_LITE, true, false); } @Override - public void formatResponse(ChallengeWriter cw, ChallengeResponse challenge, - Request request, Series

httpHeaders) { + public void formatResponse( + ChallengeWriter cw, + ChallengeResponse challenge, + Request request, + Series
httpHeaders) { - // Setup the Date header + // Set up the Date header String date = ""; if (httpHeaders.getFirstValue("x-ms-date", true) == null) { @@ -78,29 +74,30 @@ public void formatResponse(ChallengeWriter cw, ChallengeResponse challenge, if (date == null) { // Add a fresh Date header - date = DateUtils.format(new Date(), - DateUtils.FORMAT_RFC_1123.get(0)); + date = DateUtils.format(new Date(), DateUtils.FORMAT_RFC_1123.getFirst()); httpHeaders.add(HeaderConstants.HEADER_DATE, date); } } else { date = httpHeaders.getFirstValue("x-ms-date", true); } - // Setup the canonicalized path - String canonicalizedResource = getCanonicalizedResourceName(request - .getResourceRef()); + // Set up the canonicalized path + String canonicalizedResource = getCanonicalizedResourceName(request.getResourceRef()); - // Setup the message part - StringBuilder rest = new StringBuilder(); - rest.append(date).append('\n').append('/') - .append(challenge.getIdentifier()) - .append(canonicalizedResource); + // Set up the message part + final String rest = date + '\n' + '/' + challenge.getIdentifier() + canonicalizedResource; // Append the SharedKey credentials cw.append(challenge.getIdentifier()) .append(':') - .append(Base64.getEncoder().encodeToString( - DigestUtils.toHMacSha256(rest.toString(), - Base64.getDecoder().decode(IoUtils.toByteArray(challenge.getSecret()))))); + .append( + Base64.getEncoder() + .encodeToString( + DigestUtils.toHMacSha256( + rest, + Base64.getDecoder() + .decode( + IoUtils.toByteArray( + challenge.getSecret()))))); } } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpDigestHelper.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpDigestHelper.java index 4a79b533c5..090d3c7098 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpDigestHelper.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpDigestHelper.java @@ -1,18 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; -import java.io.IOException; import java.util.Base64; import java.util.logging.Level; - import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -33,38 +30,43 @@ /** * Implements the HTTP DIGEST authentication. - * + * * @author Jerome Louvel */ public class HttpDigestHelper extends AuthenticatorHelper { + private static final String ALGORITHM = "algorithm"; + private static final String CNONCE = "cnonce"; + private static final String DOMAIN = "domain"; + private static final String NC = "nc"; + private static final String NONCE = "nonce"; + private static final String OPAQUE = "opaque"; + private static final String QUALITY_OPTION = "qop"; + private static final String REALM = "realm"; + private static final String RESPONSE = "response"; + private static final String STALE = "stale"; + private static final String URI = "uri"; + private static final String USERNAME = "username"; + /** - * Checks whether the specified nonce is valid with respect to the specified - * secretKey, and further confirms that the nonce was generated less than - * lifespanMillis milliseconds ago - * - * @param nonce - * The nonce value. - * @param secretKey - * The same secret value that was inserted into the nonce when it - * was generated - * @param lifespan - * The nonce lifespan in milliseconds. - * @return True if the nonce was generated less than lifespan milliseconds - * ago, false otherwise. - * @throws Exception - * If the nonce does not match the specified secretKey, or if it - * can't be parsed + * Checks whether the specified nonce is valid with respect to the specified secretKey and + * further confirms that the nonce was generated less than lifespanMillis milliseconds ago + * + * @param nonce The nonce value. + * @param secretKey The same secret value that was inserted into the nonce when it was generated + * @param lifespan The nonce lifespan in milliseconds. + * @return True if the nonce was generated less than lifespan milliseconds ago, false otherwise. + * @throws Exception If the nonce does not match the specified secretKey, or if it can't be + * parsed */ - public static boolean isNonceValid(String nonce, String secretKey, - long lifespan) throws Exception { + public static boolean isNonceValid(String nonce, String secretKey, long lifespan) + throws Exception { try { String decodedNonce = new String(Base64.getDecoder().decode(nonce)); - long nonceTimeMS = Long.parseLong(decodedNonce.substring(0, - decodedNonce.indexOf(':'))); + long nonceTimeMS = Long.parseLong(decodedNonce.substring(0, decodedNonce.indexOf(':'))); - if (decodedNonce.equals(nonceTimeMS + ":" - + DigestUtils.toMd5(nonceTimeMS + ":" + secretKey))) { + if (decodedNonce.equals( + nonceTimeMS + ":" + DigestUtils.toMd5(nonceTimeMS + ":" + secretKey))) { // Valid with regard to the secretKey, now check lifespan return lifespan > (System.currentTimeMillis() - nonceTimeMS); } @@ -75,19 +77,20 @@ public static boolean isNonceValid(String nonce, String secretKey, throw new Exception("The nonce does not match secretKey"); } - /** - * Constructor. - */ + /** Constructor. */ public HttpDigestHelper() { super(ChallengeScheme.HTTP_DIGEST, true, true); } @Override - public void formatRequest(ChallengeWriter cw, ChallengeRequest challenge, - Response response, Series

httpHeaders) throws IOException { + public void formatRequest( + ChallengeWriter cw, + ChallengeRequest challenge, + Response response, + Series
httpHeaders) { if (challenge.getRealm() != null) { - cw.appendQuotedChallengeParameter("realm", challenge.getRealm()); + cw.appendQuotedChallengeParameter(REALM, challenge.getRealm()); } else { getLogger() .warning( @@ -95,7 +98,7 @@ public void formatRequest(ChallengeWriter cw, ChallengeRequest challenge, } if (!challenge.getDomainRefs().isEmpty()) { - cw.append(", domain=\""); + cw.append(", " + DOMAIN + "=\""); for (int i = 0; i < challenge.getDomainRefs().size(); i++) { if (i > 0) { @@ -109,25 +112,23 @@ public void formatRequest(ChallengeWriter cw, ChallengeRequest challenge, } if (challenge.getServerNonce() != null) { - cw.appendQuotedChallengeParameter("nonce", - challenge.getServerNonce()); + cw.appendQuotedChallengeParameter(NONCE, challenge.getServerNonce()); } if (challenge.getOpaque() != null) { - cw.appendQuotedChallengeParameter("opaque", challenge.getOpaque()); + cw.appendQuotedChallengeParameter(OPAQUE, challenge.getOpaque()); } if (challenge.isStale()) { - cw.appendChallengeParameter("stale", "true"); + cw.appendChallengeParameter(STALE, "true"); } if (challenge.getDigestAlgorithm() != null) { - cw.appendChallengeParameter("algorithm", - challenge.getDigestAlgorithm()); + cw.appendChallengeParameter(ALGORITHM, challenge.getDigestAlgorithm()); } if (!challenge.getQualityOptions().isEmpty()) { - cw.append(", qop=\""); + cw.append(", " + QUALITY_OPTION + "=\""); for (int i = 0; i < challenge.getQualityOptions().size(); i++) { if (i > 0) { @@ -150,60 +151,54 @@ public void formatRequest(ChallengeWriter cw, ChallengeRequest challenge, } @Override - public void formatResponse(ChallengeWriter cw, ChallengeResponse challenge, - Request request, Series
httpHeaders) { + public void formatResponse( + ChallengeWriter cw, + ChallengeResponse challenge, + Request request, + Series
httpHeaders) { if (challenge.getIdentifier() != null) { - cw.appendQuotedChallengeParameter("username", - challenge.getIdentifier()); + cw.appendQuotedChallengeParameter(USERNAME, challenge.getIdentifier()); } if (challenge.getRealm() != null) { - cw.appendQuotedChallengeParameter("realm", challenge.getRealm()); + cw.appendQuotedChallengeParameter(REALM, challenge.getRealm()); } if (challenge.getServerNonce() != null) { - cw.appendQuotedChallengeParameter("nonce", - challenge.getServerNonce()); + cw.appendQuotedChallengeParameter(NONCE, challenge.getServerNonce()); } if (challenge.getDigestRef() != null) { - challenge.setDigestRef(new Reference(request.getResourceRef() - .getPath())); - cw.appendQuotedChallengeParameter("uri", challenge.getDigestRef() - .toString()); + challenge.setDigestRef(new Reference(request.getResourceRef().getPath())); + cw.appendQuotedChallengeParameter(URI, challenge.getDigestRef().toString()); } char[] responseDigest = formatResponseDigest(challenge, request); if (responseDigest != null) { - cw.appendQuotedChallengeParameter("response", new String( - responseDigest)); + cw.appendQuotedChallengeParameter(RESPONSE, new String(responseDigest)); } if ((challenge.getDigestAlgorithm() != null) && !Digest.ALGORITHM_MD5.equals(challenge.getDigestAlgorithm())) { - cw.appendChallengeParameter("algorithm", - challenge.getDigestAlgorithm()); + cw.appendChallengeParameter(ALGORITHM, challenge.getDigestAlgorithm()); } if (challenge.getClientNonce() != null) { - cw.appendQuotedChallengeParameter("cnonce", - challenge.getClientNonce()); + cw.appendQuotedChallengeParameter(CNONCE, challenge.getClientNonce()); } if (challenge.getOpaque() != null) { - cw.appendQuotedChallengeParameter("opaque", challenge.getOpaque()); + cw.appendQuotedChallengeParameter(OPAQUE, challenge.getOpaque()); } if (challenge.getQuality() != null) { - cw.appendChallengeParameter("qop", challenge.getQuality()); + cw.appendChallengeParameter(QUALITY_OPTION, challenge.getQuality()); } - if ((challenge.getQuality() != null) - && (challenge.getServerNonceCount() > 0)) { - cw.appendChallengeParameter("nc", - challenge.getServerNonceCountAsHex()); + if ((challenge.getQuality() != null) && (challenge.getServerNonceCount() > 0)) { + cw.appendChallengeParameter(NC, challenge.getServerNonceCountAsHex()); } for (Parameter param : challenge.getParameters()) { @@ -217,48 +212,57 @@ public void formatResponse(ChallengeWriter cw, ChallengeResponse challenge, /** * Formats the response digest. - * - * @param challengeResponse - * The challenge response. - * @param request - * The request if available. + * + * @param challengeResponse The challenge response. + * @param request The request if available. * @return The formatted secret of a challenge response. */ - public char[] formatResponseDigest(ChallengeResponse challengeResponse, - Request request) { + public char[] formatResponseDigest(ChallengeResponse challengeResponse, Request request) { String a1 = null; - if (!Digest.ALGORITHM_HTTP_DIGEST.equals(challengeResponse - .getSecretAlgorithm())) { - if (!AuthenticatorUtils - .anyNull(challengeResponse.getIdentifier(), - challengeResponse.getSecret(), - challengeResponse.getRealm())) { - a1 = DigestUtils.toHttpDigest( - challengeResponse.getIdentifier(), - challengeResponse.getSecret(), - challengeResponse.getRealm()); + if (!Digest.ALGORITHM_HTTP_DIGEST.equals(challengeResponse.getSecretAlgorithm())) { + if (!AuthenticatorUtils.anyNull( + challengeResponse.getIdentifier(), + challengeResponse.getSecret(), + challengeResponse.getRealm())) { + a1 = + DigestUtils.toHttpDigest( + challengeResponse.getIdentifier(), + challengeResponse.getSecret(), + challengeResponse.getRealm()); } } else { a1 = new String(challengeResponse.getSecret()); } if (a1 != null - && !AuthenticatorUtils.anyNull(request.getMethod(), - challengeResponse.getDigestRef())) { - StringBuilder sb = new StringBuilder().append(a1).append(':') - .append(challengeResponse.getServerNonce()); - - if (!AuthenticatorUtils.anyNull(challengeResponse.getQuality(), + && !AuthenticatorUtils.anyNull( + request.getMethod(), challengeResponse.getDigestRef())) { + StringBuilder sb = + new StringBuilder() + .append(a1) + .append(':') + .append(challengeResponse.getServerNonce()); + + if (!AuthenticatorUtils.anyNull( + challengeResponse.getQuality(), challengeResponse.getClientNonce(), challengeResponse.getServerNonceCount())) { - sb.append(':').append(AuthenticatorUtils.formatNonceCount(challengeResponse.getServerNonceCount())) - .append(':').append(challengeResponse.getClientNonce()) - .append(':').append(challengeResponse.getQuality()); + sb.append(':') + .append( + AuthenticatorUtils.formatNonceCount( + challengeResponse.getServerNonceCount())) + .append(':') + .append(challengeResponse.getClientNonce()) + .append(':') + .append(challengeResponse.getQuality()); } - String a2 = DigestUtils.toMd5(request.getMethod().toString() + ":" - + challengeResponse.getDigestRef().toString()); + String a2 = + DigestUtils.toMd5( + request.getMethod().toString() + + ":" + + challengeResponse.getDigestRef().toString()); sb.append(':').append(a2); return DigestUtils.toMd5(sb.toString()).toCharArray(); @@ -268,32 +272,29 @@ public char[] formatResponseDigest(ChallengeResponse challengeResponse, } @Override - public void parseRequest(ChallengeRequest challenge, Response response, - Series
httpHeaders) { + public void parseRequest( + ChallengeRequest challenge, Response response, Series
httpHeaders) { if (challenge.getRawValue() != null) { - HeaderReader hr = new HeaderReader( - challenge.getRawValue()); + HeaderReader hr = new HeaderReader<>(challenge.getRawValue()); try { Parameter param = hr.readParameter(); while (param != null) { try { - if ("realm".equals(param.getName())) { + if (REALM.equals(param.getName())) { challenge.setRealm(param.getValue()); - } else if ("domain".equals(param.getName())) { - challenge.getDomainRefs().add( - new Reference(param.getValue())); - } else if ("nonce".equals(param.getName())) { + } else if (DOMAIN.equals(param.getName())) { + challenge.getDomainRefs().add(new Reference(param.getValue())); + } else if (NONCE.equals(param.getName())) { challenge.setServerNonce(param.getValue()); - } else if ("opaque".equals(param.getName())) { + } else if (OPAQUE.equals(param.getName())) { challenge.setOpaque(param.getValue()); - } else if ("stale".equals(param.getName())) { - challenge - .setStale(Boolean.valueOf(param.getValue())); - } else if ("algorithm".equals(param.getName())) { + } else if (STALE.equals(param.getName())) { + challenge.setStale(Boolean.parseBoolean(param.getValue())); + } else if (ALGORITHM.equals(param.getName())) { challenge.setDigestAlgorithm(param.getValue()); - } else if ("qop".equals(param.getName())) { + } else if (QUALITY_OPTION.equals(param.getName())) { // challenge.setDigestAlgorithm(param.getValue()); } else { challenge.getParameters().add(param); @@ -306,14 +307,16 @@ public void parseRequest(ChallengeRequest challenge, Response response, } } catch (Exception e) { Context.getCurrentLogger() - .log(Level.WARNING, + .log( + Level.WARNING, "Unable to parse the challenge request header parameter", e); } } } catch (Exception e) { Context.getCurrentLogger() - .log(Level.WARNING, + .log( + Level.WARNING, "Unable to parse the challenge request header parameter", e); } @@ -321,45 +324,43 @@ public void parseRequest(ChallengeRequest challenge, Response response, } @Override - public void parseResponse(ChallengeResponse challenge, Request request, - Series
httpHeaders) { + public void parseResponse( + ChallengeResponse challenge, Request request, Series
httpHeaders) { if (challenge.getRawValue() != null) { - HeaderReader hr = new HeaderReader( - challenge.getRawValue()); + HeaderReader hr = new HeaderReader<>(challenge.getRawValue()); try { Parameter param = hr.readParameter(); while (param != null) { try { - if ("username".equals(param.getName())) { + if (USERNAME.equals(param.getName())) { challenge.setIdentifier(param.getValue()); - } else if ("realm".equals(param.getName())) { + } else if (REALM.equals(param.getName())) { challenge.setRealm(param.getValue()); - } else if ("nonce".equals(param.getName())) { + } else if (NONCE.equals(param.getName())) { challenge.setServerNonce(param.getValue()); - } else if ("uri".equals(param.getName())) { - challenge.setDigestRef(new Reference(param - .getValue())); - } else if ("response".equals(param.getName())) { + } else if (URI.equals(param.getName())) { + challenge.setDigestRef(new Reference(param.getValue())); + } else if (RESPONSE.equals(param.getName())) { challenge.setSecret(param.getValue()); - } else if ("algorithm".equals(param.getName())) { + } else if (ALGORITHM.equals(param.getName())) { challenge.setDigestAlgorithm(param.getValue()); - } else if ("cnonce".equals(param.getName())) { + } else if (CNONCE.equals(param.getName())) { challenge.setClientNonce(param.getValue()); - } else if ("opaque".equals(param.getName())) { + } else if (OPAQUE.equals(param.getName())) { challenge.setOpaque(param.getValue()); - } else if ("qop".equals(param.getName())) { + } else if (QUALITY_OPTION.equals(param.getName())) { challenge.setQuality(param.getValue()); - } else if ("nc".equals(param.getName())) { - challenge.setServerNonceCount(Integer.valueOf( - param.getValue(), 16)); + } else if (NC.equals(param.getName())) { + challenge.setServerNonceCount(Integer.valueOf(param.getValue(), 16)); } else { challenge.getParameters().add(param); } } catch (Throwable e) { Context.getCurrentLogger() - .log(Level.WARNING, + .log( + Level.WARNING, "Unable to parse the challenge request header parameter", e); } @@ -371,11 +372,11 @@ public void parseResponse(ChallengeResponse challenge, Request request, } } catch (Exception e) { Context.getCurrentLogger() - .log(Level.WARNING, + .log( + Level.WARNING, "Unable to parse the challenge request header parameter", e); } } } - } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpDigestVerifier.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpDigestVerifier.java index c9a0c82fa4..f19fa60056 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpDigestVerifier.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/internal/HttpDigestVerifier.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; import org.restlet.Request; @@ -21,45 +20,42 @@ import org.restlet.security.User; /** - * Verifier for the HTTP DIGEST authentication scheme. Note that the "A1" hash - * specified in RFC 2617 is available via the - * {@link #getWrappedSecretDigest(String)} method. - * + * Verifier for the HTTP DIGEST authentication scheme. Note that the "A1" hash specified in RFC 2617 + * is available via the {@link #getWrappedSecretDigest(String)} method. + * * @author Jerome Louvel */ -public class HttpDigestVerifier extends - org.restlet.ext.crypto.DigestVerifier { +public class HttpDigestVerifier extends org.restlet.ext.crypto.DigestVerifier { /** The associated digest authenticator. */ private DigestAuthenticator digestAuthenticator; /** * Constructor. - * - * @param digestAuthenticator - * The associated digest authenticator. - * @param wrappedAlgorithm - * The digest algorithm of secrets provided by the wrapped - * verifier. - * @param wrappedVerifier - * The wrapped secret verifier. + * + * @param digestAuthenticator The associated digest authenticator. + * @param wrappedAlgorithm The digest algorithm of secrets provided by the wrapped verifier. + * @param wrappedVerifier The wrapped secret verifier. */ - public HttpDigestVerifier(DigestAuthenticator digestAuthenticator, - LocalVerifier wrappedVerifier, String wrappedAlgorithm) { + public HttpDigestVerifier( + DigestAuthenticator digestAuthenticator, + LocalVerifier wrappedVerifier, + String wrappedAlgorithm) { super(Digest.ALGORITHM_HTTP_DIGEST, wrappedVerifier, wrappedAlgorithm); this.digestAuthenticator = digestAuthenticator; } /** - * If the algorithm is {@link Digest#ALGORITHM_HTTP_DIGEST}, then is - * retrieves the realm for {@link #getDigestAuthenticator()} to compute the - * digest, otherwise, it keeps the default behavior. + * If the algorithm is {@link Digest#ALGORITHM_HTTP_DIGEST}, then it retrieves the realm for + * {@link #getDigestAuthenticator()} to compute the digest, otherwise, it keeps the default + * behavior. */ @Override protected char[] digest(String identifier, char[] secret, String algorithm) { if (Digest.ALGORITHM_HTTP_DIGEST.equals(algorithm)) { - String result = DigestUtils.toHttpDigest(identifier, secret, - getDigestAuthenticator().getRealm()); + String result = + DigestUtils.toHttpDigest( + identifier, secret, getDigestAuthenticator().getRealm()); if (result != null) { return result.toCharArray(); } @@ -72,7 +68,7 @@ protected char[] digest(String identifier, char[] secret, String algorithm) { /** * Returns the associated digest authenticator. - * + * * @return The associated digest authenticator. */ public DigestAuthenticator getDigestAuthenticator() { @@ -81,9 +77,8 @@ public DigestAuthenticator getDigestAuthenticator() { /** * Sets the associated digest authenticator. - * - * @param digestAuthenticator - * The associated digest authenticator. + * + * @param digestAuthenticator The associated digest authenticator. */ public void setDigestAuthenticator(DigestAuthenticator digestAuthenticator) { this.digestAuthenticator = digestAuthenticator; @@ -113,10 +108,11 @@ public int verify(Request request, Response response) { } try { - if (!HttpDigestHelper.isNonceValid(serverNonce, + if (!HttpDigestHelper.isNonceValid( + serverNonce, getDigestAuthenticator().getServerKey(), getDigestAuthenticator().getMaxServerNonceAge())) { - // Nonce expired, send challenge request with stale=true + // Nonce expired, send a challenge request with stale=true result = RESULT_STALE; } } catch (Exception ce) { @@ -131,8 +127,7 @@ public int verify(Request request, Response response) { Reference resourceRef = request.getResourceRef(); String requestUri = resourceRef.getPath(); - if ((resourceRef.getQuery() != null) - && (uri.indexOf('?') > -1)) { + if ((resourceRef.getQuery() != null) && (uri.indexOf('?') > -1)) { // IE neglects to include the query string, so // the workaround is to leave it off // unless both the calculated URI and the @@ -143,14 +138,20 @@ public int verify(Request request, Response response) { if (uri.equals(requestUri)) { char[] a1 = getWrappedSecretDigest(username); if (a1 != null) { - StringBuilder expectedResponse = new StringBuilder().append(a1).append(':').append(serverNonce); + StringBuilder expectedResponse = + new StringBuilder().append(a1).append(':').append(serverNonce); if (!AuthenticatorUtils.anyNull(qop, clientNonce, nc)) { expectedResponse - .append(':').append(AuthenticatorUtils.formatNonceCount(nc)) - .append(':').append(clientNonce) - .append(':').append(qop); + .append(':') + .append(AuthenticatorUtils.formatNonceCount(nc)) + .append(':') + .append(clientNonce) + .append(':') + .append(qop); } - String a2 = DigestUtils.toMd5(request.getMethod().toString() + ":" + requestUri); + String a2 = + DigestUtils.toMd5( + request.getMethod().toString() + ":" + requestUri); expectedResponse.append(':').append(a2); if (!DigestUtils.toMd5(expectedResponse.toString()).equals(cresponse)) { @@ -174,5 +175,4 @@ public int verify(Request request, Response response) { return result; } - } diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/package-info.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/package-info.java new file mode 100644 index 0000000000..056c91ed15 --- /dev/null +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/package-info.java @@ -0,0 +1,6 @@ +/** + * Support for cryptography including Amazon S3 and Windows Azure client authentication. + * + * @since Restlet 2.0 + */ +package org.restlet.ext.crypto; diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/package.html b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/package.html deleted file mode 100644 index c2afcf8cf3..0000000000 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Support for cryptography including Amazon S3 and Windows Azure client authentication. - -@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java index 4c74e7e91f..dfb97cd502 100644 --- a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java @@ -1,16 +1,23 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.Test; -import org.restlet.*; +import org.restlet.Application; +import org.restlet.Component; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.data.CookieSetting; import org.restlet.data.Form; import org.restlet.data.MediaType; @@ -19,34 +26,33 @@ import org.restlet.resource.ResourceException; import org.restlet.security.MapVerifier; -import static org.junit.jupiter.api.Assertions.*; - /** * Unit test for the {@link CookieAuthenticator} class. - * + * * @author Jerome Louvel */ -public class CookieAuthenticatorTestCase { +class CookieAuthenticatorTestCase { public static class CookieGuardedApplication extends Application { @Override public Restlet createInboundRoot() { - CookieAuthenticator co = new CookieAuthenticator(getContext(), - false, "My cookie realm", "MyExtraSecretKey".getBytes()); + CookieAuthenticator co = + new CookieAuthenticator( + getContext(), false, "My cookie realm", "MyExtraSecretKey".getBytes()); MapVerifier mapVerifier = new MapVerifier(); mapVerifier.getLocalSecrets().put("scott", "tiger".toCharArray()); co.setVerifier(mapVerifier); - Restlet hr = new Restlet() { - - @Override - public void handle(Request request, Response response) { - response.setEntity("Hello, world!", MediaType.TEXT_PLAIN); - } + Restlet hr = + new Restlet() { - }; + @Override + public void handle(Request request, Response response) { + response.setEntity("Hello, world!", MediaType.TEXT_PLAIN); + } + }; co.setNext(hr); return co; @@ -54,7 +60,7 @@ public void handle(Request request, Response response) { } @Test - public void testCookieAuth1() { + void testCookieAuth1() { CookieGuardedApplication cga = new CookieGuardedApplication(); Component c = new Component(); c.getDefaultHost().attachDefault(cga); @@ -71,8 +77,8 @@ public void testCookieAuth1() { loginForm.add("login", "scott"); loginForm.add("password", "titi"); - ResourceException exception = assertThrows(ResourceException.class, - () -> loginCr.post(loginForm)); + ResourceException exception = + assertThrows(ResourceException.class, () -> loginCr.post(loginForm)); assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, exception.getStatus()); // 3) Login with right credentials diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/DigestVerifierTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/DigestVerifierTestCase.java index 2fda07938c..f51da96cb4 100644 --- a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/DigestVerifierTestCase.java +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/DigestVerifierTestCase.java @@ -1,73 +1,65 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.restlet.data.Digest; import org.restlet.security.MapVerifier; import org.restlet.security.Verifier; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Restlet unit tests for the DigestVerifierTestCase class. - * + * * @author Jerome Louvel */ -public class DigestVerifierTestCase { +class DigestVerifierTestCase { @Test - public void test1() { + void test1() { MapVerifier mv = new MapVerifier(); mv.getLocalSecrets().put("scott", "tiger".toCharArray()); - DigestVerifier sdv = new DigestVerifier<>( - Digest.ALGORITHM_SHA_1, mv, null); + DigestVerifier sdv = new DigestVerifier<>(Digest.ALGORITHM_SHA_1, mv, null); assertEquals( Verifier.RESULT_VALID, - sdv.verify("scott", - "RuPXcqGIjq3/JsetpH/XUC15bgc=".toCharArray())); + sdv.verify("scott", "RuPXcqGIjq3/JsetpH/XUC15bgc=".toCharArray())); } @Test - public void test2() { + void test2() { MapVerifier mv = new MapVerifier(); - mv.getLocalSecrets().put("scott", - "RuPXcqGIjq3/JsetpH/XUC15bgc=".toCharArray()); + mv.getLocalSecrets().put("scott", "RuPXcqGIjq3/JsetpH/XUC15bgc=".toCharArray()); - DigestVerifier sdv = new DigestVerifier<>( - Digest.ALGORITHM_SHA_1, mv, Digest.ALGORITHM_SHA_1); + DigestVerifier sdv = + new DigestVerifier<>(Digest.ALGORITHM_SHA_1, mv, Digest.ALGORITHM_SHA_1); assertEquals( Verifier.RESULT_VALID, - sdv.verify("scott", - "RuPXcqGIjq3/JsetpH/XUC15bgc=".toCharArray())); + sdv.verify("scott", "RuPXcqGIjq3/JsetpH/XUC15bgc=".toCharArray())); - assertEquals(Verifier.RESULT_INVALID, - sdv.verify("scott", "xxxxx".toCharArray())); + assertEquals(Verifier.RESULT_INVALID, sdv.verify("scott", "xxxxx".toCharArray())); - assertEquals(Verifier.RESULT_INVALID, + assertEquals( + Verifier.RESULT_INVALID, sdv.verify("tom", "RuPXcqGIjq3/JsetpH/XUC15bgc=".toCharArray())); } @Test - public void test3() { + void test3() { MapVerifier mv = new MapVerifier(); - mv.getLocalSecrets().put("scott", - "RuPXcqGIjq3/JsetpH/XUC15bgc=".toCharArray()); + mv.getLocalSecrets().put("scott", "RuPXcqGIjq3/JsetpH/XUC15bgc=".toCharArray()); - DigestVerifier sdv = new DigestVerifier<>(null, - mv, Digest.ALGORITHM_SHA_1); + DigestVerifier sdv = new DigestVerifier<>(null, mv, Digest.ALGORITHM_SHA_1); - assertEquals(Verifier.RESULT_VALID, - sdv.verify("scott", "tiger".toCharArray())); + assertEquals(Verifier.RESULT_VALID, sdv.verify("scott", "tiger".toCharArray())); } } diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/HttpDigestTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/HttpDigestTestCase.java index a60bf758a3..97d4799457 100644 --- a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/HttpDigestTestCase.java +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/HttpDigestTestCase.java @@ -1,49 +1,55 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.restlet.data.ChallengeScheme.HTTP_DIGEST; +import static org.restlet.engine.security.AuthenticatorUtils.formatResponse; +import static org.restlet.engine.security.AuthenticatorUtils.parseResponse; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.restlet.Application; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; -import org.restlet.data.*; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Status; import org.restlet.engine.Engine; import org.restlet.routing.Router; import org.restlet.security.MapVerifier; -import static org.junit.jupiter.api.Assertions.*; -import static org.restlet.data.ChallengeScheme.HTTP_DIGEST; -import static org.restlet.engine.security.AuthenticatorUtils.formatResponse; -import static org.restlet.engine.security.AuthenticatorUtils.parseResponse; - /** * Restlet unit tests for HTTP DIGEST authentication client/server. * * @author Jerome Louvel */ -public class HttpDigestTestCase { +class HttpDigestTestCase { @Test - public void testDigest() { + void testDigest() { // Try unauthenticated request Request request = new Request(Method.GET, "/"); Response response = testApplication.handle(request); assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); - ChallengeRequest httpDigestChallengeRequest = response.getChallengeRequests() - .stream().filter(cr -> HTTP_DIGEST.equals(cr.getScheme())) - .findFirst() - .orElse(null); + ChallengeRequest httpDigestChallengeRequest = + response.getChallengeRequests().stream() + .filter(cr -> HTTP_DIGEST.equals(cr.getScheme())) + .findFirst() + .orElse(null); assertNotNull(httpDigestChallengeRequest); String realm = httpDigestChallengeRequest.getRealm(); @@ -57,10 +63,13 @@ public void testDigest() { // Try authenticated request request = new Request(Method.GET, "/"); - ChallengeResponse challengeResponseAsDefinedByClient = new ChallengeResponse(httpDigestChallengeRequest, response, - "scott", "tiger".toCharArray()); - String authHeaderAsSentByHttpClient = formatResponse(challengeResponseAsDefinedByClient, request, request.getHeaders()); - ChallengeResponse challengeResponseAsParsedByServer = parseResponse(request, authHeaderAsSentByHttpClient, request.getHeaders()); + ChallengeResponse challengeResponseAsDefinedByClient = + new ChallengeResponse( + httpDigestChallengeRequest, response, "scott", "tiger".toCharArray()); + String authHeaderAsSentByHttpClient = + formatResponse(challengeResponseAsDefinedByClient, request, request.getHeaders()); + ChallengeResponse challengeResponseAsParsedByServer = + parseResponse(request, authHeaderAsSentByHttpClient, request.getHeaders()); request.setChallengeResponse(challengeResponseAsParsedByServer); response = testApplication.handle(request); @@ -80,21 +89,22 @@ private static class MyApplication extends Application { public Restlet createInboundRoot() { Router router = new Router(getContext()); - DigestAuthenticator authenticator = new DigestAuthenticator(getContext(),"TestRealm", "mySecretServerKey"); + DigestAuthenticator authenticator = + new DigestAuthenticator(getContext(), "TestRealm", "mySecretServerKey"); MapVerifier mapVerifier = new MapVerifier(); mapVerifier.getLocalSecrets().put("scott", "tiger".toCharArray()); authenticator.setWrappedVerifier(mapVerifier); - Restlet restlet = new Restlet(getContext()) { - @Override - public void handle(Request request, Response response) { - response.setEntity("hello, world", MediaType.TEXT_PLAIN); - } - }; + Restlet restlet = + new Restlet(getContext()) { + @Override + public void handle(Request request, Response response) { + response.setEntity("hello, world", MediaType.TEXT_PLAIN); + } + }; authenticator.setNext(restlet); router.attach("/", authenticator); return router; } } - } diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3HelperTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3HelperTestCase.java index 2ce0e0b6eb..b8ce657a44 100644 --- a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3HelperTestCase.java +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3HelperTestCase.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.restlet.Request; import org.restlet.data.ChallengeResponse; @@ -19,40 +20,34 @@ import org.restlet.engine.header.HeaderConstants; import org.restlet.util.Series; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class HttpAwsS3HelperTestCase { - /** - * Test Amazon S3 authentication. - */ +class HttpAwsS3HelperTestCase { + /** Test Amazon S3 authentication. */ @Test - public void testAwsS3() { + void testAwsS3() { HttpAwsS3Helper helper = new HttpAwsS3Helper(); // Example Object GET ChallengeWriter cw = new ChallengeWriter(); - ChallengeResponse challenge = new ChallengeResponse( - ChallengeScheme.HTTP_AWS_S3, "0PN5J17HBGZHT7JJ3X82", - "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"); - Request request = new Request(Method.GET, - "http://johnsmith.s3.amazonaws.com/photos/puppy.jpg"); + ChallengeResponse challenge = + new ChallengeResponse( + ChallengeScheme.HTTP_AWS_S3, + "0PN5J17HBGZHT7JJ3X82", + "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"); + Request request = + new Request(Method.GET, "http://johnsmith.s3.amazonaws.com/photos/puppy.jpg"); Series

httpHeaders = new Series<>(Header.class); - httpHeaders.add(HeaderConstants.HEADER_DATE, - "Tue, 27 Mar 2007 19:36:42 +0000"); + httpHeaders.add(HeaderConstants.HEADER_DATE, "Tue, 27 Mar 2007 19:36:42 +0000"); helper.formatResponse(cw, challenge, request, httpHeaders); - assertEquals("0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA=", - cw.toString()); + assertEquals("0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA=", cw.toString()); // Example Object PUT cw = new ChallengeWriter(); request.setMethod(Method.PUT); - httpHeaders.set(HeaderConstants.HEADER_DATE, - "Tue, 27 Mar 2007 21:15:45 +0000", true); + httpHeaders.set(HeaderConstants.HEADER_DATE, "Tue, 27 Mar 2007 21:15:45 +0000", true); httpHeaders.add(HeaderConstants.HEADER_CONTENT_LENGTH, "94328"); httpHeaders.add(HeaderConstants.HEADER_CONTENT_TYPE, "image/jpeg"); helper.formatResponse(cw, challenge, request, httpHeaders); - assertEquals("0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ=", - cw.toString()); + assertEquals("0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ=", cw.toString()); } } diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3HostNameTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3HostNameTestCase.java index 4eb78b3a22..6fcf678ad0 100644 --- a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3HostNameTestCase.java +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3HostNameTestCase.java @@ -1,58 +1,55 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.restlet.data.Reference; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class HttpAwsS3HostNameTestCase { +class HttpAwsS3HostNameTestCase { private String checkAddress(final String host, final String path) { - return AwsUtils.getCanonicalizedResourceName(new Reference() { - public String getHostDomain() { - return host; - } - - public String getPath() { - return path; - } - }); + return AwsUtils.getCanonicalizedResourceName( + new Reference() { + public String getHostDomain() { + return host; + } + + public String getPath() { + return path; + } + }); } @Test - public void testGetCanonicalizedResourceName1() { + void testGetCanonicalizedResourceName1() { // http://s3-website-eu-west-1.amazonaws.com/reiabucket/louvel_cover150.jpg assertEquals( "/reiabucket/louvel_cover150.jpg", - checkAddress("s3-website-eu-west-1.amazonaws.com", - "/reiabucket/louvel_cover150.jpg")); + checkAddress( + "s3-website-eu-west-1.amazonaws.com", "/reiabucket/louvel_cover150.jpg")); } @Test - public void testGetCanonicalizedResourceName2() { + void testGetCanonicalizedResourceName2() { // http://reiabucket.s3.amazonaws.com/louvel_cover150.jpg assertEquals( "/reiabucket/louvel_cover150.jpg", - checkAddress("reiabucket.s3.amazonaws.com", - "/louvel_cover150.jpg")); + checkAddress("reiabucket.s3.amazonaws.com", "/louvel_cover150.jpg")); } @Test - public void testGetCanonicalizedResourceName3() { + void testGetCanonicalizedResourceName3() { // http://s3.amazonaws.com/reiabucket/louvel_cover150.jpg assertEquals( "/reiabucket/louvel_cover150.jpg", - checkAddress("s3.amazonaws.com", - "/reiabucket/louvel_cover150.jpg")); + checkAddress("s3.amazonaws.com", "/reiabucket/louvel_cover150.jpg")); } - } diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3SigningTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3SigningTestCase.java index f712db4ea4..c8c564493f 100644 --- a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3SigningTestCase.java +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3SigningTestCase.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -18,17 +19,14 @@ import org.restlet.engine.header.HeaderConstants; import org.restlet.util.Series; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** - * Unit test for {@link AwsUtils}. Test cases are taken from the examples - * provided from Authenticating REST Requests * * @author Jean-Philippe Steinmetz */ -public class HttpAwsS3SigningTestCase { +class HttpAwsS3SigningTestCase { private static final String ACCESS_KEY = "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"; private Request getRequest; @@ -38,43 +36,32 @@ public class HttpAwsS3SigningTestCase { private Request uploadRequest; @BeforeEach - public void setUpEach() throws Exception { + void setUpEach() { getRequest = new Request(); - Series

headers = new Series
(Header.class); - getRequest.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, - headers); - headers.add(HeaderConstants.HEADER_DATE, - "Tue, 27 Mar 2007 19:36:42 +0000"); + Series
headers = new Series<>(Header.class); + getRequest.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, headers); + headers.add(HeaderConstants.HEADER_DATE, "Tue, 27 Mar 2007 19:36:42 +0000"); getRequest.setMethod(Method.GET); - getRequest - .setResourceRef("http://johnsmith.s3.amazonaws.com/photos/puppy.jpg"); + getRequest.setResourceRef("http://johnsmith.s3.amazonaws.com/photos/puppy.jpg"); putRequest = new Request(); - headers = new Series
(Header.class); - putRequest.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, - headers); + headers = new Series<>(Header.class); + putRequest.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, headers); headers.add(HeaderConstants.HEADER_CONTENT_LENGTH, "94328"); headers.add(HeaderConstants.HEADER_CONTENT_TYPE, "image/jpeg"); - headers.add(HeaderConstants.HEADER_DATE, - "Tue, 27 Mar 2007 21:15:45 +0000"); + headers.add(HeaderConstants.HEADER_DATE, "Tue, 27 Mar 2007 21:15:45 +0000"); putRequest.setMethod(Method.PUT); - putRequest - .setResourceRef("http://johnsmith.s3.amazonaws.com/photos/puppy.jpg"); + putRequest.setResourceRef("http://johnsmith.s3.amazonaws.com/photos/puppy.jpg"); uploadRequest = new Request(); - headers = new Series
(Header.class); - uploadRequest.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, - headers); + headers = new Series<>(Header.class); + uploadRequest.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, headers); headers.add(HeaderConstants.HEADER_CONTENT_LENGTH, "5913339"); - headers.add(HeaderConstants.HEADER_CONTENT_MD5, - "4gJE4saaMU4BqNR0kLY+lw=="); - headers.add(HeaderConstants.HEADER_CONTENT_TYPE, - "application/x-download"); - headers.add(HeaderConstants.HEADER_DATE, - "Tue, 27 Mar 2007 21:06:08 +0000"); + headers.add(HeaderConstants.HEADER_CONTENT_MD5, "4gJE4saaMU4BqNR0kLY+lw=="); + headers.add(HeaderConstants.HEADER_CONTENT_TYPE, "application/x-download"); + headers.add(HeaderConstants.HEADER_DATE, "Tue, 27 Mar 2007 21:06:08 +0000"); uploadRequest.setMethod(Method.PUT); - uploadRequest - .setResourceRef("http://static.johnsmith.net:8080/db-backup.dat.gz"); + uploadRequest.setResourceRef("http://static.johnsmith.net:8080/db-backup.dat.gz"); headers.add("x-amz-acl", "public-read"); headers.add("X-Amz-Meta-ReviewedBy", "joe@johnsmith.net"); headers.add("X-Amz-Meta-ReviewedBy", "jane@johnsmith.net"); @@ -83,72 +70,83 @@ public void setUpEach() throws Exception { } @AfterEach - public void tearDownEach() throws Exception { + void tearDownEach() { getRequest = null; putRequest = null; uploadRequest = null; } @Test - public void testGetCanonicalizedAmzHeaders() { + void testGetCanonicalizedAmzHeaders() { Series
headers = getRequest.getHeaders(); String expected = ""; String actual = AwsUtils.getCanonicalizedAmzHeaders(headers); assertEquals(expected, actual); headers = uploadRequest.getHeaders(); - expected = "x-amz-acl:public-read\n" - + "x-amz-meta-checksumalgorithm:crc32\n" - + "x-amz-meta-filechecksum:0x02661779\n" - + "x-amz-meta-reviewedby:joe@johnsmith.net,jane@johnsmith.net\n"; + expected = + """ + x-amz-acl:public-read + x-amz-meta-checksumalgorithm:crc32 + x-amz-meta-filechecksum:0x02661779 + x-amz-meta-reviewedby:joe@johnsmith.net,jane@johnsmith.net + """; actual = AwsUtils.getCanonicalizedAmzHeaders(headers); assertEquals(expected, actual); } @Test - public void testGetCanonicalizedResourceName() { - String result = AwsUtils.getCanonicalizedResourceName(getRequest - .getResourceRef()); + void testGetCanonicalizedResourceName() { + String result = AwsUtils.getCanonicalizedResourceName(getRequest.getResourceRef()); assertEquals("/johnsmith/photos/puppy.jpg", result); } @Test - public void testGetSignature() { - String result = AwsUtils.getS3Signature(getRequest, - ACCESS_KEY.toCharArray()); + void testGetSignature() { + String result = AwsUtils.getS3Signature(getRequest, ACCESS_KEY.toCharArray()); assertEquals("xXjDGYUmKxnwqr5KXNPGldn5LbA=", result); result = AwsUtils.getS3Signature(putRequest, ACCESS_KEY.toCharArray()); assertEquals("hcicpDDvL9SsO6AkvxqmIWkmOuQ=", result); - result = AwsUtils.getS3Signature(uploadRequest, - ACCESS_KEY.toCharArray()); + result = AwsUtils.getS3Signature(uploadRequest, ACCESS_KEY.toCharArray()); assertEquals("C0FlOtU8Ylb9KDTpZqYkZPX91iI=", result); } @Test - public void testGetStringToSign() { - String expected = "GET\n" + "\n" + "\n" - + "Tue, 27 Mar 2007 19:36:42 +0000\n" - + "/johnsmith/photos/puppy.jpg"; + void testGetStringToSign() { + String expected = + """ + GET + + + Tue, 27 Mar 2007 19:36:42 +0000 + /johnsmith/photos/puppy.jpg"""; String actual = AwsUtils.getS3StringToSign(getRequest); assertEquals(expected, actual); - expected = "PUT\n" + "\n" + "image/jpeg\n" - + "Tue, 27 Mar 2007 21:15:45 +0000\n" - + "/johnsmith/photos/puppy.jpg"; + expected = + """ + PUT + + image/jpeg + Tue, 27 Mar 2007 21:15:45 +0000 + /johnsmith/photos/puppy.jpg"""; actual = AwsUtils.getS3StringToSign(putRequest); assertEquals(expected, actual); - expected = "PUT\n" + "4gJE4saaMU4BqNR0kLY+lw==\n" - + "application/x-download\n" - + "Tue, 27 Mar 2007 21:06:08 +0000\n" - + "x-amz-acl:public-read\n" - + "x-amz-meta-checksumalgorithm:crc32\n" - + "x-amz-meta-filechecksum:0x02661779\n" - + "x-amz-meta-reviewedby:" - + "joe@johnsmith.net,jane@johnsmith.net\n" - + "/static.johnsmith.net/db-backup.dat.gz"; + expected = + """ + PUT + 4gJE4saaMU4BqNR0kLY+lw== + application/x-download + Tue, 27 Mar 2007 21:06:08 +0000 + x-amz-acl:public-read + x-amz-meta-checksumalgorithm:crc32 + x-amz-meta-filechecksum:0x02661779 + x-amz-meta-reviewedby:\ + joe@johnsmith.net,jane@johnsmith.net + /static.johnsmith.net/db-backup.dat.gz"""; actual = AwsUtils.getS3StringToSign(uploadRequest); assertEquals(expected, actual); } diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3VerifierTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3VerifierTestCase.java index 9810e20f9a..f86ee2f22f 100644 --- a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3VerifierTestCase.java +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpAwsS3VerifierTestCase.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,14 +23,12 @@ import org.restlet.security.Verifier; import org.restlet.util.Series; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Unit tests for {@link AwsVerifier}. * * @author Jean-Philippe Steinmetz */ -public class HttpAwsS3VerifierTestCase { +class HttpAwsS3VerifierTestCase { private static final String ACCESS_ID = "0PN5J17HBGZHT7JJ3X82"; private static final String ACCESS_KEY = "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"; @@ -50,15 +49,14 @@ private Request createRequest() { @BeforeEach public void setUpEach() throws Exception { - localVerifier = new LocalVerifier() { - @Override - public char[] getLocalSecret(String identifier) { - if (ACCESS_ID.equals(identifier)) - return ACCESS_KEY.toCharArray(); - else - return "password".toCharArray(); - } - }; + localVerifier = + new LocalVerifier() { + @Override + public char[] getLocalSecret(String identifier) { + if (ACCESS_ID.equals(identifier)) return ACCESS_KEY.toCharArray(); + else return "password".toCharArray(); + } + }; awsVerifier = new AwsVerifier(localVerifier); } @@ -70,25 +68,23 @@ public void tearDownEach() throws Exception { } @Test - public void testVerify() { + void testVerify() { Request request = createRequest(); @SuppressWarnings("unchecked") - Series

headers = (Series
) request.getAttributes().get( - HeaderConstants.ATTRIBUTE_HEADERS); + Series
headers = + (Series
) request.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS); // Test for missing due to no challenge response assertEquals(Verifier.RESULT_MISSING, awsVerifier.verify(request, null)); - ChallengeResponse cr = new ChallengeResponse( - ChallengeScheme.HTTP_AWS_S3); + ChallengeResponse cr = new ChallengeResponse(ChallengeScheme.HTTP_AWS_S3); request.setChallengeResponse(cr); // Test missing due to no identifier assertEquals(Verifier.RESULT_MISSING, awsVerifier.verify(request, null)); // Test authentication with bad credentials - String sig = AwsUtils.getS3Signature(request, - "badpassword".toCharArray()); + String sig = AwsUtils.getS3Signature(request, "badpassword".toCharArray()); cr.setRawValue(ACCESS_ID + ":" + sig); assertEquals(Verifier.RESULT_INVALID, awsVerifier.verify(request, null)); @@ -102,8 +98,7 @@ public void testVerify() { assertEquals(Verifier.RESULT_INVALID, awsVerifier.verify(request, null)); // Test stale due to out of date header - headers.add(HeaderConstants.HEADER_DATE, - "Tue, 27 Mar 1999 19:36:42 +0000"); + headers.add(HeaderConstants.HEADER_DATE, "Tue, 27 Mar 1999 19:36:42 +0000"); assertEquals(Verifier.RESULT_STALE, awsVerifier.verify(request, null)); } } diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpDigestHelperTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpDigestHelperTestCase.java index 2d4e8ac240..52f0362820 100644 --- a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpDigestHelperTestCase.java +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/internal/HttpDigestHelperTestCase.java @@ -1,65 +1,62 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.crypto.internal; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; import org.junit.jupiter.api.Test; import org.restlet.Request; -import org.restlet.data.*; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.Digest; +import org.restlet.data.Method; +import org.restlet.data.Reference; import org.restlet.engine.Engine; import org.restlet.engine.security.AuthenticatorUtils; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class HttpDigestHelperTestCase { +class HttpDigestHelperTestCase { - /** - * Tests the authentication parsing for HTTP DIGEST. - * - */ + /** Tests the authentication parsing for HTTP DIGEST. */ @Test - public void testParsingDigest() { + void testParsingDigest() { // make sure the Digest authentication scheme is registered Engine.getInstance().getRegisteredAuthenticators().add(new HttpDigestHelper()); - ChallengeResponse cres1 = new ChallengeResponse( - ChallengeScheme.HTTP_DIGEST, - null, - "admin", - "12345".toCharArray(), - Digest.ALGORITHM_NONE, - null, - "qop", - new Reference("/protected/asdass"), - null, - null, - "MTE3NzEwMzIwMjkwMDoxNmMzODFiYzRjNWRjMmMyOTVkMWFhNDdkMTQ4OGFlMw==", - "MTE3NzEwMzIwMjkwMDoxNmMzODFiYzRjNWRjMmMyOTVkMWFhNDdkMTQ4OGFlMw==", - 1, 0L); - - Request request = new Request(Method.GET, - "http://remote.com/protected/asdass"); - String authorization1 = AuthenticatorUtils.formatResponse(cres1, - request, null); - String authenticate1 = "Digest realm=realm, domain=\"/protected/ /alsoProtected/\", qop=auth, algorithm=MD5, nonce=\"MTE3NzEwMzIwMjg0Mjo2NzFjODQyMjAyOWRlNWQ1YjFjNmEzYzJmOWRlZmE2Mw==\""; - - ChallengeResponse cres = AuthenticatorUtils.parseResponse(null, - authorization1, null); + ChallengeResponse cres1 = + new ChallengeResponse( + ChallengeScheme.HTTP_DIGEST, + null, + "admin", + "12345".toCharArray(), + Digest.ALGORITHM_NONE, + null, + "qop", + new Reference("/protected/asdass"), + null, + null, + "MTE3NzEwMzIwMjkwMDoxNmMzODFiYzRjNWRjMmMyOTVkMWFhNDdkMTQ4OGFlMw==", + "MTE3NzEwMzIwMjkwMDoxNmMzODFiYzRjNWRjMmMyOTVkMWFhNDdkMTQ4OGFlMw==", + 1, + 0L); + + Request request = new Request(Method.GET, "http://remote.com/protected/asdass"); + String authorization1 = AuthenticatorUtils.formatResponse(cres1, request, null); + String authenticate1 = + "Digest realm=realm, domain=\"/protected/ /alsoProtected/\", qop=auth, algorithm=MD5, nonce=\"MTE3NzEwMzIwMjg0Mjo2NzFjODQyMjAyOWRlNWQ1YjFjNmEzYzJmOWRlZmE2Mw==\""; + + ChallengeResponse cres = AuthenticatorUtils.parseResponse(null, authorization1, null); cres.setRawValue(null); - assertEquals(authorization1, - AuthenticatorUtils.formatResponse(cres, request, null)); + assertEquals(authorization1, AuthenticatorUtils.formatResponse(cres, request, null)); - List creq = AuthenticatorUtils.parseRequest(null, - authenticate1, null); + List creq = AuthenticatorUtils.parseRequest(null, authenticate1, null); assertEquals(creq.size(), 1); - assertEquals(authenticate1, - AuthenticatorUtils.formatRequest(creq.get(0), null, null)); + assertEquals(authenticate1, AuthenticatorUtils.formatRequest(creq.getFirst(), null, null)); } } diff --git a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/ContextTemplateLoader.java b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/ContextTemplateLoader.java index c934bf40f3..2b7bdf9ca4 100644 --- a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/ContextTemplateLoader.java +++ b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/ContextTemplateLoader.java @@ -1,33 +1,29 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.freemarker; +import freemarker.cache.TemplateLoader; +import freemarker.template.Configuration; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.util.Date; - import org.restlet.Context; import org.restlet.Request; import org.restlet.data.Method; import org.restlet.data.Reference; import org.restlet.representation.Representation; -import freemarker.cache.TemplateLoader; -import freemarker.template.Configuration; - /** - * FreeMarker template loader based on a Context's client dispatcher. You can - * set an instance on a FreeMarker configuration via the - * {@link Configuration#setTemplateLoader(TemplateLoader)} method. - * + * FreeMarker template loader based on a Context's client dispatcher. You can set an instance on a + * FreeMarker configuration via the {@link Configuration#setTemplateLoader(TemplateLoader)} method. + * * @author Jerome Louvel */ public class ContextTemplateLoader implements TemplateLoader { @@ -40,11 +36,9 @@ public class ContextTemplateLoader implements TemplateLoader { /** * Constructor. - * - * @param context - * The Restlet context. - * @param baseRef - * The base reference. + * + * @param context The Restlet context. + * @param baseRef The base reference. */ public ContextTemplateLoader(Context context, Reference baseRef) { this(context, baseRef.toString()); @@ -52,11 +46,9 @@ public ContextTemplateLoader(Context context, Reference baseRef) { /** * Constructor. - * - * @param context - * The Restlet context. - * @param baseUri - * The base URI. + * + * @param context The Restlet context. + * @param baseUri The base URI. */ public ContextTemplateLoader(Context context, String baseUri) { this.context = context; @@ -65,22 +57,19 @@ public ContextTemplateLoader(Context context, String baseUri) { /** * Close the template source. - * - * @param templateSource - * The template source {@link Representation}. + * + * @param templateSource The template source {@link Representation}. */ - public void closeTemplateSource(Object templateSource) throws IOException { - if (templateSource instanceof Representation) { - ((Representation) templateSource).release(); + public void closeTemplateSource(Object templateSource) { + if (templateSource instanceof Representation representation) { + representation.release(); } } /** - * Finds the object that acts as the source of the template with the given - * name. - * - * @param name - * The template name. + * Finds the object that acts as the source of the template with the given name. + * + * @param name The template name. * @return The template source {@link Representation}. */ public Object findTemplateSource(String name) throws IOException { @@ -92,14 +81,17 @@ public Object findTemplateSource(String name) throws IOException { fullUri = getBaseUri() + "/" + name; } - return (getContext() == null) ? null : getContext() - .getClientDispatcher().handle(new Request(Method.GET, fullUri)) - .getEntity(); + return (getContext() == null) + ? null + : getContext() + .getClientDispatcher() + .handle(new Request(Method.GET, fullUri)) + .getEntity(); } /** * Returns the base URI. - * + * * @return The base URI. */ private String getBaseUri() { @@ -108,7 +100,7 @@ private String getBaseUri() { /** * Returns the Restlet context. - * + * * @return The Restlet context. */ private Context getContext() { @@ -117,29 +109,23 @@ private Context getContext() { /** * Returns the modification time. - * - * @param templateSource - * The template source {@link Representation}. + * + * @param templateSource The template source {@link Representation}. * @return The modification time. */ public long getLastModified(Object templateSource) { - Date lastModified = ((Representation) templateSource) - .getModificationDate(); + Date lastModified = ((Representation) templateSource).getModificationDate(); return (lastModified == null) ? -1L : lastModified.getTime(); } /** * Returns the reader for the template source. - * - * @param templateSource - * The template source {@link Representation}. - * @param characterSet - * The reader character set. + * + * @param templateSource The template source {@link Representation}. + * @param characterSet The reader character set. */ - public Reader getReader(Object templateSource, String characterSet) - throws IOException { + public Reader getReader(Object templateSource, String characterSet) throws IOException { Representation r = (Representation) templateSource; return new InputStreamReader(r.getStream(), characterSet); } - } diff --git a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/FreemarkerConverter.java b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/FreemarkerConverter.java index eb4c36a21d..823c38f8e9 100644 --- a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/FreemarkerConverter.java +++ b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/FreemarkerConverter.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.freemarker; -import java.io.IOException; +import freemarker.template.Template; import java.util.List; - import org.restlet.engine.converter.ConverterHelper; import org.restlet.engine.resource.VariantInfo; import org.restlet.ext.freemarker.internal.ResolverHashModel; @@ -20,12 +18,10 @@ import org.restlet.resource.Resource; import org.restlet.util.Resolver; -import freemarker.template.Template; - /** - * Converter between the FreeMarker Template objects and Representations. The - * adjoined data model is based on the request and response objects. - * + * Converter between the FreeMarker Template objects and Representations. The adjoined data model is + * based on the request and response objects. + * * @author Thierry Boileau. */ public class FreemarkerConverter extends ConverterHelper { @@ -50,30 +46,26 @@ public float score(Object source, Variant target, Resource resource) { } @Override - public float score(Representation source, Class target, - Resource resource) { + public float score(Representation source, Class target, Resource resource) { return -1.0f; } @Override - public T toObject(Representation source, Class target, - Resource resource) throws IOException { + public T toObject(Representation source, Class target, Resource resource) { return null; } @Override - public Representation toRepresentation(Object source, Variant target, - Resource resource) throws IOException { + public Representation toRepresentation(Object source, Variant target, Resource resource) { - if (source instanceof Template) { - return new TemplateRepresentation((Template) source, - new ResolverHashModel(Resolver.createResolver( - resource.getRequest(), resource.getResponse())), + if (source instanceof Template template) { + return new TemplateRepresentation( + template, + new ResolverHashModel( + Resolver.createResolver(resource.getRequest(), resource.getResponse())), target.getMediaType()); - } return null; } - } diff --git a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/TemplateFilter.java b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/TemplateFilter.java index 149ea4d527..8c5e7b4c56 100644 --- a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/TemplateFilter.java +++ b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/TemplateFilter.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.freemarker; +import freemarker.template.Configuration; +import freemarker.template.TemplateHashModel; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -18,20 +19,16 @@ import org.restlet.routing.Filter; import org.restlet.util.Resolver; -import freemarker.template.Configuration; -import freemarker.template.TemplateHashModel; - /** - * Filter response's entity and wrap it with a FreeMarker's template - * representation. By default, the template representation provides a data model - * based on the request and response objects. In order for the wrapping to - * happen, the representations must have the {@link Encoding#FREEMARKER} + * Filter the response's entity and wrap it with a FreeMarker's template representation. By default, + * the template representation provides a data model based on the request and response objects. In + * order for the wrapping to happen, the representations must have the {@link Encoding#FREEMARKER} * encoding set.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Thierry Boileau */ public class TemplateFilter extends Filter { @@ -42,9 +39,7 @@ public class TemplateFilter extends Filter { /** The template's data model. */ private volatile Object dataModel; - /** - * Constructor. - */ + /** Constructor. */ public TemplateFilter() { super(); this.configuration = new Configuration(); @@ -52,9 +47,8 @@ public TemplateFilter() { /** * Constructor. - * - * @param context - * The context. + * + * @param context The context. */ public TemplateFilter(Context context) { super(context); @@ -63,11 +57,9 @@ public TemplateFilter(Context context) { /** * Constructor. - * - * @param context - * The context. - * @param next - * The next Restlet. + * + * @param context The context. + * @param next The next Restlet. */ public TemplateFilter(Context context, Restlet next) { super(context, next); @@ -76,13 +68,10 @@ public TemplateFilter(Context context, Restlet next) { /** * Constructor. - * - * @param context - * The context. - * @param next - * The next Restlet. - * @param dataModel - * The filter's data model. + * + * @param context The context. + * @param next The next Restlet. + * @param dataModel The filter's data model. */ public TemplateFilter(Context context, Restlet next, Object dataModel) { this(context, next); @@ -91,16 +80,12 @@ public TemplateFilter(Context context, Restlet next, Object dataModel) { /** * Constructor. - * - * @param context - * The context. - * @param next - * The next Restlet. - * @param dataModel - * The filter's data model. + * + * @param context The context. + * @param next The next Restlet. + * @param dataModel The filter's data model. */ - public TemplateFilter(Context context, Restlet next, - Resolver dataModel) { + public TemplateFilter(Context context, Restlet next, Resolver dataModel) { this(context, next); this.dataModel = dataModel; } @@ -108,34 +93,31 @@ public TemplateFilter(Context context, Restlet next, @Override protected void afterHandle(Request request, Response response) { if (response.isEntityAvailable() - && response.getEntity().getEncodings() - .contains(Encoding.FREEMARKER)) { - TemplateRepresentation representation = new TemplateRepresentation( - response.getEntity(), this.configuration, response - .getEntity().getMediaType()); + && response.getEntity().getEncodings().contains(Encoding.FREEMARKER)) { + TemplateRepresentation representation = + new TemplateRepresentation( + response.getEntity(), + this.configuration, + response.getEntity().getMediaType()); representation.setDataModel(createDataModel(request, response)); response.setEntity(representation); } } /** - * Creates the FreeMarker data model for a given call. By default, it will - * create a {@link TemplateHashModel} based on the result of - * {@link Resolver#createResolver(Request, Response)}. If the - * {@link #getDataModel()} method has a non null value, it will be used. - * - * @param request - * The handled request. - * @param response - * The handled response. + * Creates the FreeMarker data model for a given call. By default, it will create a {@link + * TemplateHashModel} based on the result of {@link Resolver#createResolver(Request, Response)}. + * If the {@link #getDataModel()} method has a non-null value, it will be used. + * + * @param request The handled request. + * @param response The handled response. * @return The FreeMarker data model for the given call. */ protected Object createDataModel(Request request, Response response) { Object result = null; if (this.dataModel == null) { - result = new ResolverHashModel(Resolver.createResolver(request, - response)); + result = new ResolverHashModel(Resolver.createResolver(request, response)); } else { result = this.dataModel; } @@ -145,7 +127,7 @@ protected Object createDataModel(Request request, Response response) { /** * Returns the FreeMarker configuration. - * + * * @return The FreeMarker configuration. */ public Configuration getConfiguration() { @@ -153,9 +135,9 @@ public Configuration getConfiguration() { } /** - * Returns the template data model common to all calls. If each call should - * have a specific model, you should set this property to null. - * + * Returns the template data model common to all calls. If each call should have a specific + * model, you should set this property to null. + * * @return The template data model common to all calls. */ public Object getDataModel() { @@ -164,23 +146,20 @@ public Object getDataModel() { /** * Sets the FreeMarker configuration. - * - * @param config - * FreeMarker configuration. + * + * @param config FreeMarker configuration. */ public void setConfiguration(Configuration config) { this.configuration = config; } /** - * Sets the template data model common to all calls. If each call should - * have a specific model, you should set this property to null. - * - * @param dataModel - * The template data model common to all calls. + * Sets the template data model common to all calls. If each call should have a specific model, + * you should set this property to null. + * + * @param dataModel The template data model common to all calls. */ public void setDataModel(Object dataModel) { this.dataModel = dataModel; } - } diff --git a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/TemplateRepresentation.java b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/TemplateRepresentation.java index 822899c020..0d02d10535 100644 --- a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/TemplateRepresentation.java +++ b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/TemplateRepresentation.java @@ -1,17 +1,20 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.freemarker; +import static freemarker.template.Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; import java.io.IOException; import java.io.Writer; - import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -22,14 +25,9 @@ import org.restlet.representation.WriterRepresentation; import org.restlet.util.Resolver; -import freemarker.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; - /** - * FreeMarker template representation. Useful for dynamic string-based - * representations. - * + * FreeMarker template representation. Useful for dynamic string-based representations. + * * @see FreeMarker home page * @author Jerome Louvel */ @@ -37,51 +35,57 @@ public class TemplateRepresentation extends WriterRepresentation { /** * Returns a FreeMarker template from a representation and a configuration. - * - * @param config - * The FreeMarker configuration. - * @param templateRepresentation - * The template representation. + * + * @param config The FreeMarker configuration. + * @param templateRepresentation The template representation. * @return The template or null if not found. */ - public static Template getTemplate(Configuration config, - Representation templateRepresentation) { + public static Template getTemplate( + Configuration config, Representation templateRepresentation) { try { // Instantiate the template with the character set of the template // representation if it has been set, otherwise use UTF-8. if (templateRepresentation.getCharacterSet() != null) { - return new Template("template", - templateRepresentation.getReader(), config, + return new Template( + "template", + templateRepresentation.getReader(), + config, templateRepresentation.getCharacterSet().getName()); } - return new Template("template", templateRepresentation.getReader(), - config, CharacterSet.UTF_8.getName()); + return new Template( + "template", + templateRepresentation.getReader(), + config, + CharacterSet.UTF_8.getName()); } catch (IOException e) { - Context.getCurrentLogger().warning( - "Unable to get the template from the representation " - + templateRepresentation.getLocationRef() - + ". Error message: " + e.getMessage()); + Context.getCurrentLogger() + .warning( + "Unable to get the template from the representation " + + templateRepresentation.getLocationRef() + + ". Error message: " + + e.getMessage()); return null; } } /** * Returns a FreeMarker template from its name and a configuration. - * - * @param config - * The FreeMarker configuration. - * @param templateName - * The template name. + * + * @param config The FreeMarker configuration. + * @param templateName The template name. * @return The template or null if not found. */ public static Template getTemplate(Configuration config, String templateName) { try { return config.getTemplate(templateName); } catch (IOException e) { - Context.getCurrentLogger().warning( - "Unable to get the template " + templateName - + ". Error message: " + e.getMessage()); + Context.getCurrentLogger() + .warning( + "Unable to get the template " + + templateName + + ". Error message: " + + e.getMessage()); return null; } } @@ -94,105 +98,92 @@ public static Template getTemplate(Configuration config, String templateName) { /** * Constructor. - * - * @param templateRepresentation - * The FreeMarker template provided via a representation. - * @param config - * The FreeMarker configuration. - * @param mediaType - * The representation's media type. + * + * @param templateRepresentation The FreeMarker template provided via a representation. + * @param config The FreeMarker configuration. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(Representation templateRepresentation, - Configuration config, MediaType mediaType) { + public TemplateRepresentation( + Representation templateRepresentation, Configuration config, MediaType mediaType) { this(getTemplate(config, templateRepresentation), mediaType); } /** * Constructor. - * - * @param templateRepresentation - * The FreeMarker template provided via a representation. - * @param config - * The FreeMarker configuration. - * @param dataModel - * The template's data model. - * @param mediaType - * The representation's media type. + * + * @param templateRepresentation The FreeMarker template provided via a representation. + * @param config The FreeMarker configuration. + * @param dataModel The template's data model. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(Representation templateRepresentation, - Configuration config, Object dataModel, MediaType mediaType) { + public TemplateRepresentation( + Representation templateRepresentation, + Configuration config, + Object dataModel, + MediaType mediaType) { this(getTemplate(config, templateRepresentation), dataModel, mediaType); } /** * Constructor. - * - * @param templateRepresentation - * The FreeMarker template provided via a representation. - * @param mediaType - * The representation's media type. + * + * @param templateRepresentation The FreeMarker template provided via a representation. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(Representation templateRepresentation, - MediaType mediaType) { - this(templateRepresentation, new Configuration(), mediaType); + public TemplateRepresentation(Representation templateRepresentation, MediaType mediaType) { + this( + templateRepresentation, + new Configuration(DEFAULT_INCOMPATIBLE_IMPROVEMENTS), + mediaType); } /** * Constructor. Uses a default FreeMarker configuration. - * - * @param templateRepresentation - * The FreeMarker template provided via a representation. - * @param dataModel - * The template's data model. - * @param mediaType - * The representation's media type. + * + * @param templateRepresentation The FreeMarker template provided via a representation. + * @param dataModel The template's data model. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(Representation templateRepresentation, - Object dataModel, MediaType mediaType) { - this(templateRepresentation, new Configuration(), dataModel, mediaType); + public TemplateRepresentation( + Representation templateRepresentation, Object dataModel, MediaType mediaType) { + this( + templateRepresentation, + new Configuration(DEFAULT_INCOMPATIBLE_IMPROVEMENTS), + dataModel, + mediaType); } /** * Constructor. - * - * @param templateName - * The FreeMarker template's name. The full path is resolved by - * the configuration. - * @param config - * The FreeMarker configuration. - * @param mediaType - * The representation's media type. + * + * @param templateName The FreeMarker template's name. The full path is resolved by the + * configuration. + * @param config The FreeMarker configuration. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(String templateName, Configuration config, - MediaType mediaType) { + public TemplateRepresentation(String templateName, Configuration config, MediaType mediaType) { this(getTemplate(config, templateName), mediaType); } /** * Constructor. - * - * @param templateName - * The FreeMarker template's name. The full path is resolved by - * the configuration. - * @param config - * The FreeMarker configuration. - * @param dataModel - * The template's data model. - * @param mediaType - * The representation's media type. + * + * @param templateName The FreeMarker template's name. The full path is resolved by the + * configuration. + * @param config The FreeMarker configuration. + * @param dataModel The template's data model. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(String templateName, Configuration config, - Object dataModel, MediaType mediaType) { + public TemplateRepresentation( + String templateName, Configuration config, Object dataModel, MediaType mediaType) { this(getTemplate(config, templateName), dataModel, mediaType); } /** * Constructor. - * - * @param template - * The FreeMarker template. - * @param mediaType - * The representation's media type. + * + * @param template The FreeMarker template. + * @param mediaType The representation's media-type. */ public TemplateRepresentation(Template template, MediaType mediaType) { super(mediaType); @@ -201,24 +192,25 @@ public TemplateRepresentation(Template template, MediaType mediaType) { /** * Constructor. - * - * @param template - * The FreeMarker template. - * @param dataModel - * The template's data model. - * @param mediaType - * The representation's media type. + * + * @param template The FreeMarker template. + * @param dataModel The template's data model. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(Template template, Object dataModel, - MediaType mediaType) { + public TemplateRepresentation(Template template, Object dataModel, MediaType mediaType) { super(mediaType); this.template = template; this.dataModel = dataModel; } + @Override + public boolean equals(final Object obj) { + return super.equals(obj); + } + /** * Returns the template's data model. - * + * * @return The template's data model. */ public Object getDataModel() { @@ -227,18 +219,22 @@ public Object getDataModel() { /** * Returns the FreeMarker template. - * + * * @return The FreeMarker template. */ public Template getTemplate() { return template; } + @Override + public int hashCode() { + return super.hashCode(); + } + /** * Sets the template's data model. - * - * @param dataModel - * The template's data model. + * + * @param dataModel The template's data model. * @return The template's data model. */ public Object setDataModel(Object dataModel) { @@ -247,29 +243,24 @@ public Object setDataModel(Object dataModel) { } /** - * Sets the template's data model from a request/response pair. This default - * implementation uses a Resolver. - * + * Sets the template's data model from a request/response pair. This default implementation uses + * a Resolver. + * * @see Resolver * @see Resolver#createResolver(Request, Response) - * - * @param request - * The request where data are located. - * @param response - * The response where data are located. + * @param request The request where data are located. + * @param response The response where data are located. * @return The template's data model. */ public Object setDataModel(Request request, Response response) { - this.dataModel = new ResolverHashModel(Resolver.createResolver(request, - response)); + this.dataModel = new ResolverHashModel(Resolver.createResolver(request, response)); return this.dataModel; } /** * Sets the template's data model from a resolver. - * - * @param resolver - * The resolver. + * + * @param resolver The resolver. * @return The template's data model. */ public Object setDataModel(Resolver resolver) { @@ -279,9 +270,8 @@ public Object setDataModel(Resolver resolver) { /** * Sets the FreeMarker template. - * - * @param template - * The FreeMarker template. + * + * @param template The FreeMarker template. */ public void setTemplate(Template template) { this.template = template; @@ -289,9 +279,8 @@ public void setTemplate(Template template) { /** * Writes the datum as a stream of characters. - * - * @param writer - * The writer to use when writing. + * + * @param writer The writer to use when writing. */ @Override public void write(Writer writer) throws IOException { @@ -299,14 +288,11 @@ public void write(Writer writer) throws IOException { try { this.template.process(getDataModel(), writer); } catch (TemplateException te) { - throw new IOException("Template processing error " - + te.getMessage()); + throw new IOException("Template processing error " + te.getMessage()); } } else { Context.getCurrentLogger() - .warning( - "Unable to write the template representation. No template found."); + .warning("Unable to write the template representation. No template found."); } } - } diff --git a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/internal/ResolverHashModel.java b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/internal/ResolverHashModel.java index 2a2459e533..abcba8319f 100644 --- a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/internal/ResolverHashModel.java +++ b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/internal/ResolverHashModel.java @@ -1,23 +1,21 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.freemarker.internal; -import org.restlet.util.Resolver; - import freemarker.template.TemplateHashModel; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; +import org.restlet.util.Resolver; /** * Template Hash Model based on a Resolver instance. - * + * * @author Jerome Louvel */ public class ResolverHashModel implements TemplateHashModel { @@ -26,35 +24,27 @@ public class ResolverHashModel implements TemplateHashModel { /** * Constructor. - * - * @param resolver - * The inner resolver. + * + * @param resolver The inner resolver. */ public ResolverHashModel(Resolver resolver) { super(); this.resolver = resolver; } - /** - * Returns a scalar model based on the value returned by the resolver - * according to the key. - */ + /** Returns a scalar model based on the value returned by the resolver according to the key. */ public TemplateModel get(String key) throws TemplateModelException { Object value = this.resolver.resolve(key); if (value == null) { return null; - } else if (value instanceof TemplateModel) { - return (TemplateModel) value; + } else if (value instanceof TemplateModel templateModel) { + return templateModel; } return new ScalarModel(value); } - /** - * Returns false. - * - * @Return False. - */ + /** Returns false. @Return False. */ public boolean isEmpty() throws TemplateModelException { return false; } diff --git a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/internal/ScalarModel.java b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/internal/ScalarModel.java index 01b5b67c17..ee44773146 100644 --- a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/internal/ScalarModel.java +++ b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/internal/ScalarModel.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.freemarker.internal; import freemarker.template.TemplateModelException; @@ -14,7 +13,7 @@ /** * Data model that gives access to a Object value. - * + * * @author Jerome Louvel */ class ScalarModel implements TemplateScalarModel { @@ -23,9 +22,8 @@ class ScalarModel implements TemplateScalarModel { /** * Constructor. - * - * @param value - * the provided value of this scalar model. + * + * @param value the provided value of this scalar model. */ public ScalarModel(Object value) { super(); diff --git a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/package-info.java b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/package-info.java new file mode 100644 index 0000000000..9b95514138 --- /dev/null +++ b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/package-info.java @@ -0,0 +1,11 @@ +/** + * Integration with Apache FreeMarker 2.3 template library. FreeMarker is a template engine; a + * generic tool to generate text output (anything from HTML to autogenerated source code) based on + * templates. + * + * @since Restlet 1.0 + * @see FreeMarker template engine + * @see User + * Guide - FreeMarker extension + */ +package org.restlet.ext.freemarker; diff --git a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/package.html b/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/package.html deleted file mode 100644 index df1a980930..0000000000 --- a/org.restlet.ext.freemarker/src/main/java/org/restlet/ext/freemarker/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - -Integration with Apache FreeMarker 2.3 template library. FreeMarker is a template engine; a generic tool to generate text output (anything from HTML to autogenerated source code) based on templates. - -@since Restlet 1.0 -@see FreeMarker template engine -@see User Guide - FreeMarker extension - - \ No newline at end of file diff --git a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java index 013cc4ed5d..7afafb2b70 100644 --- a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java +++ b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java @@ -1,35 +1,33 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.freemarker; -import freemarker.template.Configuration; -import org.junit.jupiter.api.Test; -import org.restlet.data.MediaType; -import org.restlet.engine.io.IoUtils; +import static org.junit.jupiter.api.Assertions.assertEquals; +import freemarker.template.Configuration; import java.io.File; import java.io.FileWriter; import java.nio.file.Files; import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; /** * Unit test for the FreeMarker extension. - * + * * @author Jerome Louvel */ -public class FreeMarkerTestCase { +class FreeMarkerTestCase { @Test - public void testTemplate() throws Exception { + void testTemplate() throws Exception { // Create a temporary directory for the tests final File testDir = Files.createTempDirectory("FreeMarkerTestCase").toFile(); @@ -43,11 +41,12 @@ public void testTemplate() throws Exception { fmc.setDirectoryForTemplateLoading(testDir); final Map map = Map.of("value", "myValue"); - final String result = new TemplateRepresentation(testFile.getName(), fmc, map, MediaType.TEXT_PLAIN).getText(); + final String result = + new TemplateRepresentation(testFile.getName(), fmc, map, MediaType.TEXT_PLAIN) + .getText(); assertEquals("Value=myValue", result); // Clean-up IoUtils.delete(testDir, true); } - } diff --git a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/TemplateFilterTestCase.java b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/TemplateFilterTestCase.java index c64de2613c..dec470bd1f 100644 --- a/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/TemplateFilterTestCase.java +++ b/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/TemplateFilterTestCase.java @@ -1,41 +1,45 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.freemarker; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.restlet.*; +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.data.LocalReference; import org.restlet.data.Method; import org.restlet.data.Protocol; import org.restlet.engine.Engine; import org.restlet.resource.Directory; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test case for template filters. * * @author Thierry Boileau */ -public class TemplateFilterTestCase { +class TemplateFilterTestCase { @Test - public void representationShouldBeUsedAsTemplate() throws Exception { - Request request = new Request(Method.GET,"/template.txt.fmt"); + void representationShouldBeUsedAsTemplate() throws Exception { + Request request = new Request(Method.GET, "/template.txt.fmt"); Response response = testApplication.handle(request); assertEquals("Method=GET/Path=/template.txt.fmt", response.getEntity().getText()); } @Test - public void representationShouldNotBeUsedAsTemplate() throws Exception { + void representationShouldNotBeUsedAsTemplate() throws Exception { Request request = new Request(Method.GET, "/notATemplate.txt"); Response response = testApplication.handle(request); @@ -66,9 +70,12 @@ private static class MyFreemakerApplication extends Application { @Override public Restlet createInboundRoot() { - final Directory directory = new Directory(getContext(), LocalReference.createClapReference(TemplateFilterTestCase.class.getPackage())); + final Directory directory = + new Directory( + getContext(), + LocalReference.createClapReference( + TemplateFilterTestCase.class.getPackage())); return new org.restlet.ext.freemarker.TemplateFilter(getContext(), directory); } } - } diff --git a/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/GsonConverter.java b/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/GsonConverter.java index 6190c764df..7a53c4b848 100644 --- a/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/GsonConverter.java +++ b/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/GsonConverter.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.gson; import java.io.IOException; import java.util.List; - import org.restlet.data.MediaType; import org.restlet.data.Preference; import org.restlet.engine.converter.ConverterHelper; @@ -21,41 +19,36 @@ import org.restlet.resource.Resource; /** - * Converter between the JSON and Representation classe based on Gson library. - * + * Converter between the JSON and Representation classes based on the Gson library. + * * @author Neal Mi */ public class GsonConverter extends ConverterHelper { /** Variant with media type application/json. */ - private static final VariantInfo VARIANT_JSON = new VariantInfo( - MediaType.APPLICATION_JSON); + private static final VariantInfo VARIANT_JSON = new VariantInfo(MediaType.APPLICATION_JSON); /** * Creates the unmarshaling {@link GsonRepresentation}. - * + * * @param - * @param source - * The source representation to unmarshal. - * @param objectClass - * The object class to instantiate. + * @param source The source representation to unmarshal. + * @param objectClass The object class to instantiate. * @return The unmarshaling {@link GsonRepresentation}. */ - protected GsonRepresentation create(Representation source, - Class objectClass) { - return new GsonRepresentation(source, objectClass); + protected GsonRepresentation create(Representation source, Class objectClass) { + return new GsonRepresentation<>(source, objectClass); } /** * Creates the marshaling {@link GsonRepresentation}. - * + * * @param - * @param source - * The source object to marshal. + * @param source The source object to marshal. * @return The marshaling {@link GsonRepresentation}. */ protected GsonRepresentation create(T source) { - return new GsonRepresentation(source); + return new GsonRepresentation<>(source); } @Override @@ -83,7 +76,7 @@ public List getVariants(Class source) { @Override public float score(Object source, Variant target, Resource resource) { - float result = -1.0F; + final float result; if (source instanceof GsonRepresentation) { result = 1.0F; @@ -101,14 +94,12 @@ public float score(Object source, Variant target, Resource resource) { } @Override - public float score(Representation source, Class target, - Resource resource) { + public float score(Representation source, Class target, Resource resource) { float result = -1.0F; if (source instanceof GsonRepresentation) { result = 1.0F; - } else if ((target != null) - && GsonRepresentation.class.isAssignableFrom(target)) { + } else if ((target != null) && GsonRepresentation.class.isAssignableFrom(target)) { result = 1.0F; } else if (VARIANT_JSON.isCompatible(source)) { result = 0.8F; @@ -119,8 +110,8 @@ public float score(Representation source, Class target, @SuppressWarnings("unchecked") @Override - public T toObject(Representation source, Class target, - Resource resource) throws IOException { + public T toObject(Representation source, Class target, Resource resource) + throws IOException { Object result = null; // The source for the gson conversion @@ -134,8 +125,7 @@ public T toObject(Representation source, Class target, if (gsonSource != null) { // Handle the conversion - if ((target != null) - && GsonRepresentation.class.isAssignableFrom(target)) { + if ((target != null) && GsonRepresentation.class.isAssignableFrom(target)) { result = gsonSource; } else { result = gsonSource.getObject(); @@ -146,8 +136,7 @@ public T toObject(Representation source, Class target, } @Override - public Representation toRepresentation(Object source, Variant target, - Resource resource) throws IOException { + public Representation toRepresentation(Object source, Variant target, Resource resource) { Representation result = null; if (source instanceof GsonRepresentation) { @@ -158,8 +147,7 @@ public Representation toRepresentation(Object source, Variant target, } if (VARIANT_JSON.isCompatible(target)) { - GsonRepresentation gsonRepresentation = create(source); - result = gsonRepresentation; + result = create(source); } } @@ -167,9 +155,7 @@ public Representation toRepresentation(Object source, Variant target, } @Override - public void updatePreferences(List> preferences, - Class entity) { + public void updatePreferences(List> preferences, Class entity) { updatePreferences(preferences, MediaType.APPLICATION_JSON, 1.0F); } - } diff --git a/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/GsonRepresentation.java b/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/GsonRepresentation.java index ecbda03833..9ba5dae9d3 100644 --- a/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/GsonRepresentation.java +++ b/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/GsonRepresentation.java @@ -1,25 +1,13 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.gson; -import java.io.IOException; -import java.io.Writer; -import java.lang.reflect.Type; -import java.text.DateFormat; -import java.util.Date; - -import org.joda.time.DateTime; -import org.restlet.data.MediaType; -import org.restlet.representation.Representation; -import org.restlet.representation.WriterRepresentation; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; @@ -31,11 +19,20 @@ import com.google.gson.JsonSerializer; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.util.Date; +import org.joda.time.DateTime; +import org.restlet.data.MediaType; +import org.restlet.representation.Representation; +import org.restlet.representation.WriterRepresentation; /** - * Representation based on a JSON document. JSON stands for JavaScript Object - * Notation and is a lightweight data-interchange format. - * + * Representation based on a JSON document. JSON stands for JavaScript Object Notation and is a + * lightweight data-interchange format. + * * @author Neal Mi * @see Gson project */ @@ -43,25 +40,23 @@ public class GsonRepresentation extends WriterRepresentation { /** * Custom deserializer for {@link Date} instances. - * + * * @author Neal Mi. */ private static class ISODateDeserializer implements JsonDeserializer { - public Date deserialize(JsonElement json, Type typeOfT, - JsonDeserializationContext context) throws JsonParseException { - return new DateTime(json.getAsJsonPrimitive().getAsString()) - .toDate(); + public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return new DateTime(json.getAsJsonPrimitive().getAsString()).toDate(); } } /** * Custom serializer for {@link Date} instances. - * + * * @author Neal Mi. */ private static class ISODateSerializer implements JsonSerializer { - public JsonElement serialize(Date src, Type typeOfSrc, - JsonSerializationContext context) { + public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { DateTime dt = new DateTime(src); // DateTime dtz = dt.withZone(DateTimeZone.forOffsetHours(-8)); return new JsonPrimitive(dt.toString()); @@ -72,7 +67,7 @@ public JsonElement serialize(Date src, Type typeOfSrc, private GsonBuilder builder; /** The JSON representation to parse. */ - private Representation jsonRepresentation; + private final Representation jsonRepresentation; /** The (parsed) object to format. */ private T object; @@ -82,14 +77,11 @@ public JsonElement serialize(Date src, Type typeOfSrc, /** * Constructor. - * - * @param representation - * The representation to parse. - * @param objectClass - * The object class to instantiate. + * + * @param representation The representation to parse. + * @param objectClass The object class to instantiate. */ - public GsonRepresentation(Representation representation, - Class objectClass) { + public GsonRepresentation(Representation representation, Class objectClass) { super(representation.getMediaType()); this.object = null; this.objectClass = objectClass; @@ -99,23 +91,21 @@ public GsonRepresentation(Representation representation, /** * Constructor for the JSON media type. - * - * @param object - * The object to format. + * + * @param object The object to format. */ @SuppressWarnings("unchecked") public GsonRepresentation(T object) { super(MediaType.APPLICATION_JSON); this.object = object; - this.objectClass = ((Class) ((object == null) ? null : object - .getClass())); + this.objectClass = ((Class) ((object == null) ? null : object.getClass())); this.jsonRepresentation = null; this.builder = null; } /** * Returns a new instance of the builder for Gson instances. - * + * * @return a new instance of builder for Gson instances. */ protected GsonBuilder createBuilder() { @@ -126,22 +116,22 @@ protected GsonBuilder createBuilder() { /** * Returns the builder for Gson instances. - * + * * @return The builder for Gson instances. */ public GsonBuilder getBuilder() { if (builder == null) { - builder = createBuilder().registerTypeAdapter(Date.class, - new ISODateSerializer()).registerTypeAdapter(Date.class, - new ISODateDeserializer()); + builder = + createBuilder() + .registerTypeAdapter(Date.class, new ISODateSerializer()) + .registerTypeAdapter(Date.class, new ISODateDeserializer()); } return builder; } /** - * Returns the wrapped object, deserializing the representation with Gson if - * necessary. - * + * Returns the wrapped object, deserializing the representation with Gson if necessary. + * * @return The wrapped object. * @throws IOException */ @@ -152,9 +142,8 @@ public T getObject() throws IOException { result = this.object; } else if (this.jsonRepresentation != null) { Gson gson = getBuilder().create(); - result = gson.fromJson( - new JsonReader(jsonRepresentation.getReader()), - this.objectClass); + result = + gson.fromJson(new JsonReader(jsonRepresentation.getReader()), this.objectClass); } return result; @@ -162,7 +151,7 @@ public T getObject() throws IOException { /** * Returns the object class to instantiate. - * + * * @return The object class to instantiate. */ public Class getObjectClass() { @@ -171,9 +160,8 @@ public Class getObjectClass() { /** * Sets the Gson builder. - * - * @param builder - * The Gson builder. + * + * @param builder The Gson builder. */ public void setBuilder(GsonBuilder builder) { this.builder = builder; @@ -181,9 +169,8 @@ public void setBuilder(GsonBuilder builder) { /** * Sets the object to format. - * - * @param object - * The object to format. + * + * @param object The object to format. */ public void setObject(T object) { this.object = object; @@ -191,9 +178,8 @@ public void setObject(T object) { /** * Sets the object class to instantiate. - * - * @param objectClass - * The object class to instantiate. + * + * @param objectClass The object class to instantiate. */ public void setObjectClass(Class objectClass) { this.objectClass = objectClass; @@ -208,5 +194,4 @@ public void write(Writer writer) throws IOException { gson.toJson(object, objectClass, new JsonWriter(writer)); } } - } diff --git a/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/package-info.java b/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/package-info.java new file mode 100644 index 0000000000..05203004b7 --- /dev/null +++ b/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/package-info.java @@ -0,0 +1,9 @@ +/** + * Integration with Gson 2.8. + * + * @since Restlet 2.2 + * @see Gson Web site + * @see User Guide + * - Gson extension + */ +package org.restlet.ext.gson; diff --git a/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/package.html b/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/package.html deleted file mode 100644 index 242aeed0c9..0000000000 --- a/org.restlet.ext.gson/src/main/java/org/restlet/ext/gson/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - -Integration with Gson 2.8. - -@since Restlet 2.2 -@see Gson Web site -@see User Guide - Gson extension - - diff --git a/org.restlet.ext.gson/src/test/java/org/restlet/ext/gson/GsonTestCase.java b/org.restlet.ext.gson/src/test/java/org/restlet/ext/gson/GsonTestCase.java index 9399532611..a07d1938a8 100644 --- a/org.restlet.ext.gson/src/test/java/org/restlet/ext/gson/GsonTestCase.java +++ b/org.restlet.ext.gson/src/test/java/org/restlet/ext/gson/GsonTestCase.java @@ -1,16 +1,22 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.gson; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.gson.annotations.Since; +import java.io.IOException; +import java.util.Date; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -20,17 +26,12 @@ import org.restlet.representation.StringRepresentation; import org.restlet.representation.Variant; -import java.io.IOException; -import java.util.Date; - -import static org.junit.jupiter.api.Assertions.*; - /** * Unit test for the Gson extension. * * @author Neal Mi */ -public class GsonTestCase { +class GsonTestCase { private static class User { private final boolean active; @@ -46,8 +47,13 @@ private static class User { private final int rate; - public User(String loginId, String password, int rate, boolean active, - Date createAt, Date lastLogin) { + public User( + String loginId, + String password, + int rate, + boolean active, + Date createAt, + Date lastLogin) { super(); this.loginId = loginId; this.password = password; @@ -87,21 +93,21 @@ public boolean isActive() { private User user; @BeforeEach - public void setUpEach() throws Exception { + void setUpEach() { user = new User("hello", "secret", 1, true, new Date(), new Date()); gsonConverter = new GsonConverter(); } @Test - public final void testCreateMediaTypeT() { + final void testCreateMediaTypeT() { Representation rep = new GsonRepresentation<>(user); assertNotNull(rep); - assertEquals(rep.getMediaType(), MediaType.APPLICATION_JSON); + assertEquals(MediaType.APPLICATION_JSON, rep.getMediaType()); } @Test - public final void testCreateRepresentationClassOfT() { + final void testCreateRepresentationClassOfT() { Representation rep = new GsonRepresentation<>(user); Representation rep1 = new GsonRepresentation<>(rep, User.class); @@ -110,21 +116,23 @@ public final void testCreateRepresentationClassOfT() { } @Test - public final void testGsonRepresentationRead() throws IOException { - final String userAsJsonString = "{\"loginId\":\"hello\",\"password\":\"secret\",\"rate\":1,\"active\":true,\"createAt\":\"2012-05-20T15:41:01.489+08:00\",\"lastLogin\":\"2012-05-20T15:41:01.489+08:00\"}"; - Representation source = new StringRepresentation(userAsJsonString, MediaType.APPLICATION_JSON); + final void testGsonRepresentationRead() throws IOException { + final String userAsJsonString = + "{\"loginId\":\"hello\",\"password\":\"secret\",\"rate\":1,\"active\":true,\"createAt\":\"2012-05-20T15:41:01.489+08:00\",\"lastLogin\":\"2012-05-20T15:41:01.489+08:00\"}"; + Representation source = + new StringRepresentation(userAsJsonString, MediaType.APPLICATION_JSON); GsonRepresentation gsonRep = new GsonRepresentation<>(source, User.class); - User user = gsonRep.getObject(); + User parsedUser = gsonRep.getObject(); - assertNotNull(user); - assertEquals("hello", user.getLoginId()); - assertEquals("secret", user.getPassword()); - assertEquals(1, user.getRate()); - assertTrue(user.isActive()); + assertNotNull(parsedUser); + assertEquals("hello", parsedUser.getLoginId()); + assertEquals("secret", parsedUser.getPassword()); + assertEquals(1, parsedUser.getRate()); + assertTrue(parsedUser.isActive()); DateTime time = new DateTime("2012-05-20T15:41:01.489+08:00"); - assertEquals(time.getMillis(), user.getCreateAt().getTime()); - assertEquals(time.getMillis(), user.getLastLogin().getTime()); + assertEquals(time.getMillis(), parsedUser.getCreateAt().getTime()); + assertEquals(time.getMillis(), parsedUser.getLastLogin().getTime()); GsonRepresentation gsonRep1 = new GsonRepresentation<>(source, User.class); gsonRep1.getBuilder().setVersion(1.0); @@ -139,7 +147,7 @@ public final void testGsonRepresentationRead() throws IOException { } @Test - public final void testGsonRepresentationWrite() throws IOException { + final void testGsonRepresentationWrite() throws IOException { GsonRepresentation source = new GsonRepresentation<>(user); assertEquals(User.class, source.getObjectClass()); @@ -158,7 +166,7 @@ public final void testGsonRepresentationWrite() throws IOException { } @Test - public final void testScoreObjectVariantResource() { + final void testScoreObjectVariantResource() { Variant v = new Variant(MediaType.APPLICATION_JSON); Representation source = new GsonRepresentation<>(user); @@ -166,11 +174,11 @@ public final void testScoreObjectVariantResource() { assertEquals(0.8F, score); float score1 = gsonConverter.score(source, v, null); - assertEquals(1.0F , score1); + assertEquals(1.0F, score1); } @Test - public final void testScoreRepresentationClassOfTResource() { + final void testScoreRepresentationClassOfTResource() { Representation source = new GsonRepresentation(user); float score = gsonConverter.score(source, User.class, null); @@ -183,8 +191,7 @@ public final void testScoreRepresentationClassOfTResource() { } @Test() - public final void testToObjectRepresentationClassOfTResource() - throws IOException { + final void testToObjectRepresentationClassOfTResource() throws IOException { Representation source = new GsonRepresentation<>(user); User u = gsonConverter.toObject(source, User.class, null); @@ -202,16 +209,14 @@ public final void testToObjectRepresentationClassOfTResource() } @Test - public final void testToRepresentationObjectVariantResource() - throws IOException { + final void testToRepresentationObjectVariantResource() { Variant v = new Variant(MediaType.APPLICATION_JSON); Representation rep = gsonConverter.toRepresentation(user, v, null); assertNotNull(rep); - assertEquals(rep.getMediaType(), MediaType.APPLICATION_JSON); + assertEquals(MediaType.APPLICATION_JSON, rep.getMediaType()); Variant v1 = new Variant(MediaType.APPLICATION_XML); Representation rep1 = gsonConverter.toRepresentation(user, v1, null); assertNull(rep1); } - } diff --git a/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/ChallengeCallbackHandler.java b/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/ChallengeCallbackHandler.java index 6a6deb4e79..06d662429c 100644 --- a/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/ChallengeCallbackHandler.java +++ b/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/ChallengeCallbackHandler.java @@ -1,28 +1,25 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.jaas; -import java.io.IOException; - import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; - import org.restlet.Request; import org.restlet.Response; /** - * JAAS callback handler that automatically provides the identifier and secret - * when asked by login modules. - * + * JAAS callback handler that automatically provides the identifier and secret when asked by login + * modules. + * * @author Jerome Louvel */ public class ChallengeCallbackHandler implements CallbackHandler { @@ -35,11 +32,9 @@ public class ChallengeCallbackHandler implements CallbackHandler { /** * Constructor. - * - * @param request - * The handled request. - * @param response - * The handled response. + * + * @param request The handled request. + * @param response The handled response. */ public ChallengeCallbackHandler(Request request, Response response) { this.request = request; @@ -48,7 +43,7 @@ public ChallengeCallbackHandler(Request request, Response response) { /** * Returns the handled request. - * + * * @return The handled request. */ public Request getRequest() { @@ -57,7 +52,7 @@ public Request getRequest() { /** * Returns the handled response. - * + * * @return The handled response. */ public Response getResponse() { @@ -65,43 +60,30 @@ public Response getResponse() { } /** - * Handles a callback. The default implementation automatically sets the - * identifier on {@link javax.security.auth.callback.NameCallback} instances - * and the secret on {@link PasswordCallback}. - * - * @param callback - * The callback to handle. + * Handles a callback. The default implementation automatically sets the identifier on {@link + * NameCallback} instances and the secret on {@link PasswordCallback}. + * + * @param callback The callback to handle. * @throws UnsupportedCallbackException */ - protected void handle(Callback callback) - throws UnsupportedCallbackException { - if (callback instanceof javax.security.auth.callback.NameCallback) { - javax.security.auth.callback.NameCallback nc = (javax.security.auth.callback.NameCallback) callback; - - if (getRequest().getChallengeResponse() != null) { - nc.setName(getRequest().getChallengeResponse().getIdentifier()); - } - } else if (callback instanceof PasswordCallback) { - PasswordCallback pc = (PasswordCallback) callback; - - if (getRequest().getChallengeResponse() != null) { - pc.setPassword(getRequest().getChallengeResponse().getSecret()); - } - } else { - throw new UnsupportedCallbackException(callback, - "Unrecognized Callback"); + protected void handle(Callback callback) throws UnsupportedCallbackException { + switch (callback) { + case NameCallback nameCallback when getRequest().getChallengeResponse() != null -> + nameCallback.setName(getRequest().getChallengeResponse().getIdentifier()); + case PasswordCallback passwordCallback + when getRequest().getChallengeResponse() != null -> + passwordCallback.setPassword(getRequest().getChallengeResponse().getSecret()); + default -> throw new UnsupportedCallbackException(callback, "Unrecognized Callback"); } } /** - * Handles the callbacks. The default implementation delegates the handling - * to the {@link #handle(Callback)} method. - * - * @param callbacks - * The callbacks to handle. + * Handles the callbacks. The default implementation delegates the handling to the {@link + * #handle(Callback)} method. + * + * @param callbacks The callbacks to handle. */ - public void handle(Callback[] callbacks) throws IOException, - UnsupportedCallbackException { + public void handle(Callback[] callbacks) throws UnsupportedCallbackException { if (callbacks != null) { for (Callback callback : callbacks) { @@ -112,9 +94,8 @@ public void handle(Callback[] callbacks) throws IOException, /** * Sets the handled request. - * - * @param request - * The handled request. + * + * @param request The handled request. */ public void setRequest(Request request) { this.request = request; @@ -122,12 +103,10 @@ public void setRequest(Request request) { /** * Sets the handled response. - * - * @param response - * The handled response. + * + * @param response The handled response. */ public void setResponse(Response response) { this.response = response; } - } diff --git a/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/JaasUtils.java b/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/JaasUtils.java index 578a240681..fe0daa4dcf 100644 --- a/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/JaasUtils.java +++ b/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/JaasUtils.java @@ -1,38 +1,33 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.jaas; import java.security.AccessControlContext; import java.security.Principal; import java.security.PrivilegedAction; - import javax.security.auth.Subject; - import org.restlet.data.ClientInfo; import org.restlet.security.Role; /** * Utility class to facilitate integration between the Restlet and JAAS APIs. - * + * * @author Jerome Louvel */ public final class JaasUtils { /** - * Creates a JAAS subject based on a given {@link ClientInfo}. It adds a - * {@link ClientInfo#getUser()}, all the entries in - * {@link ClientInfo#getRoles()} and all other principals in - * {@link ClientInfo#getPrincipals()}. - * - * @param clientInfo - * The client info to expose as a subject. + * Creates a JAAS subject based on a given {@link ClientInfo}. It adds a {@link + * ClientInfo#getUser()}, all the entries in {@link ClientInfo#getRoles()} and all other + * principals in {@link ClientInfo#getPrincipals()}. + * + * @param clientInfo The client info to expose as a subject. * @return The populated JAAS subject. */ public static Subject createSubject(ClientInfo clientInfo) { @@ -56,45 +51,32 @@ public static Subject createSubject(ClientInfo clientInfo) { } /** - * Creates a JAAS subject on the {@link ClientInfo} and uses it to run the - * action, using - * {@link Subject#doAsPrivileged(Subject, PrivilegedAction, AccessControlContext)} - * . This uses a null {@link AccessControlContext}. - * - * @param - * the return type of the action. - * @param clientInfo - * the client info from which to build as a subject. - * @param action - * the code to be run as the specified Subject. + * Creates a JAAS subject on the {@link ClientInfo} and uses it to run the action, using {@link + * Subject#doAsPrivileged(Subject, PrivilegedAction, AccessControlContext)} . This uses a null + * {@link AccessControlContext}. + * + * @param the return type of the action. + * @param clientInfo the client info from which to build as a subject. + * @param action the code to be run as the specified Subject. * @return the value returned by the action. */ - public static T doAsPriviledged(ClientInfo clientInfo, - PrivilegedAction action) { + public static T doAsPriviledged(ClientInfo clientInfo, PrivilegedAction action) { return doAsPriviledged(clientInfo, action, null); } /** - * Creates a JAAS subject on the {@link ClientInfo} and uses it to run the - * action, using - * {@link Subject#doAsPrivileged(Subject, PrivilegedAction, AccessControlContext)} - * . - * - * @param - * the return type of the action. - * @param clientInfo - * the client info from which to build a subject. - * @param action - * the code to be run as the specified Subject. - * @param acc - * the AccessControlContext to be tied to the specified subject - * and action. + * Creates a JAAS subject on the {@link ClientInfo} and uses it to run the action, using {@link + * Subject#doAsPrivileged(Subject, PrivilegedAction, AccessControlContext)} . + * + * @param the return type of the action. + * @param clientInfo the client info from which to build a subject. + * @param action the code to be run as the specified Subject. + * @param acc the AccessControlContext to be tied to the specified subject and action. * @return the value returned by the action. */ - public static T doAsPriviledged(ClientInfo clientInfo, - PrivilegedAction action, AccessControlContext acc) { + public static T doAsPriviledged( + ClientInfo clientInfo, PrivilegedAction action, AccessControlContext acc) { Subject subject = JaasUtils.createSubject(clientInfo); - T result = Subject.doAsPrivileged(subject, action, acc); - return result; + return Subject.doAsPrivileged(subject, action, acc); } } diff --git a/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/JaasVerifier.java b/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/JaasVerifier.java index e32b4bf13c..f3909a2137 100644 --- a/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/JaasVerifier.java +++ b/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/JaasVerifier.java @@ -1,22 +1,19 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.jaas; import java.security.Principal; - import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; - import org.restlet.Request; import org.restlet.Response; import org.restlet.data.ClientInfo; @@ -25,12 +22,14 @@ /** * Verifier that leverages the JAAS pluggable authentication mechanism. - * + * * @author Jerome Louvel - * @see JAAS - * Tutorials - * @see JAAS Reference - * Guide + * @see JAAS + * Tutorials + * @see JAAS + * Reference Guide */ public class JaasVerifier implements Verifier { @@ -45,28 +44,26 @@ public class JaasVerifier implements Verifier { /** * Constructor. - * - * @param name - * The JAAS login context name. + * + * @param name The JAAS login context name. */ public JaasVerifier(String name) { this.name = name; } /** - * Creates a callback handler for the given parameters. By default it - * returns one handler that handles name and password JAAS callbacks. - * + * Creates a callback handler for the given parameters. By default it returns one handler that + * handles name and password JAAS callbacks. + * * @return The callback handler created. */ - protected CallbackHandler createCallbackHandler(Request request, - Response response) { + protected CallbackHandler createCallbackHandler(Request request, Response response) { return new ChallengeCallbackHandler(request, response); } /** * Returns the optional JAAS login configuration. - * + * * @return The optional JAAS login configuration. */ public Configuration getConfiguration() { @@ -75,7 +72,7 @@ public Configuration getConfiguration() { /** * Returns the JAAS login context name. - * + * * @return The JAAS login context name. */ public String getName() { @@ -84,7 +81,7 @@ public String getName() { /** * Gets the user principal class name. - * + * * @return the user principal class name. */ public String getUserPrincipalClassName() { @@ -93,9 +90,8 @@ public String getUserPrincipalClassName() { /** * Sets the optional JAAS login configuration. - * - * @param configuration - * The optional JAAS login configuration. + * + * @param configuration The optional JAAS login configuration. */ public void setConfiguration(Configuration configuration) { this.configuration = configuration; @@ -103,38 +99,33 @@ public void setConfiguration(Configuration configuration) { /** * Sets the JAAS login context name. - * - * @param contextName - * The JAAS login context name. + * + * @param contextName The JAAS login context name. */ public void setName(String contextName) { this.name = contextName; } /** - * Sets the user principal class name. If a {@link User} is not associated - * with the {@link Request}'s {@link ClientInfo} and if one of the - * principals returned after the JAAS login is of this type, a new {@link User} will be associated with the - * {@link ClientInfo} using its + * Sets the user principal class name. If a {@link User} is not associated with the {@link + * Request}'s {@link ClientInfo} and if one of the principals returned after the JAAS login is + * of this type, a new {@link User} will be associated with the {@link ClientInfo} using its * name. - * - * @param userPrincipalClassName - * the user principal class name. + * + * @param userPrincipalClassName the user principal class name. */ public void setUserPrincipalClassName(String userPrincipalClassName) { this.userPrincipalClassName = userPrincipalClassName; } /** - * Verifies that the proposed secret is correct for the specified - * identifier. By default, it creates a JAAS login context with the callback - * handler obtained by {@link #createCallbackHandler(Request, Response)} and - * calls the {@link LoginContext#login()} method on it. - * - * @param request - * The request sent. - * @param response - * The response to update. + * Verifies that the proposed secret is correct for the specified identifier. By default, it + * creates a JAAS login context with the callback handler obtained by {@link + * #createCallbackHandler(Request, Response)} and calls the {@link LoginContext#login()} method + * on it. + * + * @param request The request sent. + * @param response The response to update. * @return Result of the verification based on the RESULT_* constants. */ public int verify(Request request, Response response) { @@ -146,15 +137,16 @@ public int verify(Request request, Response response) { subject.getPrincipals().add(request.getClientInfo().getUser()); } if (request.getClientInfo().getRoles() != null) { - subject.getPrincipals().addAll( - request.getClientInfo().getRoles()); + subject.getPrincipals().addAll(request.getClientInfo().getRoles()); } - subject.getPrincipals().addAll( - request.getClientInfo().getPrincipals()); - - LoginContext loginContext = new LoginContext(getName(), subject, - createCallbackHandler(request, response), - getConfiguration()); + subject.getPrincipals().addAll(request.getClientInfo().getPrincipals()); + + LoginContext loginContext = + new LoginContext( + getName(), + subject, + createCallbackHandler(request, response), + getConfiguration()); loginContext.login(); /* @@ -168,7 +160,7 @@ public int verify(Request request, Response response) { request.getClientInfo().getPrincipals().add(principal); } /* - * If no user has been set yet and if this principal if of the + * If no user has been set yet and if this principal is of the * type we expect for a user, we create a new User based on the * principal's name. */ diff --git a/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/package-info.java b/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/package-info.java new file mode 100644 index 0000000000..bf13f47f47 --- /dev/null +++ b/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/package-info.java @@ -0,0 +1,8 @@ +/** + * Support for JAAS authentication and authorization framework. + * + * @since Restlet 2.0 + * @see User Guide + * - JAAS extension + */ +package org.restlet.ext.jaas; diff --git a/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/package.html b/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/package.html deleted file mode 100644 index f7b4727bd0..0000000000 --- a/org.restlet.ext.jaas/src/main/java/org/restlet/ext/jaas/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - -Support for JAAS authentication and authorization framework. - -@since Restlet 2.0 -@see User Guide - JAAS extension - - \ No newline at end of file diff --git a/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/JacksonConverter.java b/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/JacksonConverter.java index 5a89cfde64..5feae97ece 100644 --- a/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/JacksonConverter.java +++ b/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/JacksonConverter.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.jackson; import java.io.IOException; import java.util.List; - import org.restlet.data.MediaType; import org.restlet.data.Preference; import org.restlet.engine.converter.ConverterHelper; @@ -21,71 +19,60 @@ import org.restlet.resource.Resource; /** - * Converter between the JSON, JSON Smile, CSV, XML, YAML and Representation - * classes based on Jackson. - * + * Converter between the JSON, JSON Smile, CSV, XML, YAML and Representation classes based on + * Jackson. + * * @author Jerome Louvel * @author Thierry Boileau */ public class JacksonConverter extends ConverterHelper { /** Variant with media type application/xml. */ - private static final VariantInfo VARIANT_APPLICATION_XML = new VariantInfo( - MediaType.APPLICATION_XML); + private static final VariantInfo VARIANT_APPLICATION_XML = + new VariantInfo(MediaType.APPLICATION_XML); /** Variant with media type application/yaml. */ - private static final VariantInfo VARIANT_APPLICATION_YAML = new VariantInfo( - MediaType.APPLICATION_YAML); + private static final VariantInfo VARIANT_APPLICATION_YAML = + new VariantInfo(MediaType.APPLICATION_YAML); /** Variant with media type application/json. */ - private static final VariantInfo VARIANT_JSON = new VariantInfo( - MediaType.APPLICATION_JSON); + private static final VariantInfo VARIANT_JSON = new VariantInfo(MediaType.APPLICATION_JSON); /** Variant with media type application/x-json-smile. */ - private static final VariantInfo VARIANT_JSON_SMILE = new VariantInfo( - MediaType.APPLICATION_JSON_SMILE); + private static final VariantInfo VARIANT_JSON_SMILE = + new VariantInfo(MediaType.APPLICATION_JSON_SMILE); /** Variant with media type text/csv. */ - private static final VariantInfo VARIANT_TEXT_CSV = new VariantInfo( - MediaType.TEXT_CSV); + private static final VariantInfo VARIANT_TEXT_CSV = new VariantInfo(MediaType.TEXT_CSV); /** Variant with media type text/xml. */ - private static final VariantInfo VARIANT_TEXT_XML = new VariantInfo( - MediaType.TEXT_XML); + private static final VariantInfo VARIANT_TEXT_XML = new VariantInfo(MediaType.TEXT_XML); /** Variant with media type text/yaml. */ - private static final VariantInfo VARIANT_TEXT_YAML = new VariantInfo( - MediaType.TEXT_YAML); + private static final VariantInfo VARIANT_TEXT_YAML = new VariantInfo(MediaType.TEXT_YAML); /** * Creates the marshaling {@link JacksonRepresentation}. - * - * @param - * The expected class of the representation Java object. - * @param mediaType - * The target media type. - * @param source - * The source object to marshal. + * + * @param The expected class of the representation Java object. + * @param mediaType The target media type. + * @param source The source object to marshal. * @return The marshaling {@link JacksonRepresentation}. */ protected JacksonRepresentation create(MediaType mediaType, T source) { - return new JacksonRepresentation(mediaType, source); + return new JacksonRepresentation<>(mediaType, source); } /** * Creates the unmarshaling {@link JacksonRepresentation}. - * - * @param - * The expected class of the representation Java object. - * @param source - * The source representation to unmarshal. - * @param objectClass - * The object class to instantiate. + * + * @param The expected class of the representation Java object. + * @param source The source representation to unmarshal. + * @param objectClass The object class to instantiate. * @return The unmarshaling {@link JacksonRepresentation}. */ - protected JacksonRepresentation create(Representation source, - Class objectClass) { - return new JacksonRepresentation(source, objectClass); + protected JacksonRepresentation create(Representation source, Class objectClass) { + return new JacksonRepresentation<>(source, objectClass); } @Override @@ -118,13 +105,12 @@ public List getVariants(Class source) { } /** - * Indicates if the given variant is compatible with the media types - * supported by this converter. - * - * @param variant - * The variant. - * @return True if the given variant is compatible with the media types - * supported by this converter. + * Indicates if the given variant is compatible with the media types supported by this + * converter. + * + * @param variant The variant. + * @return True if the given variant is compatible with the media types supported by this + * converter. */ protected boolean isCompatible(Variant variant) { return (variant != null) @@ -133,41 +119,39 @@ protected boolean isCompatible(Variant variant) { || (VARIANT_APPLICATION_XML.isCompatible(variant)) || (VARIANT_TEXT_XML.isCompatible(variant)) || VARIANT_APPLICATION_YAML.isCompatible(variant) - || VARIANT_TEXT_YAML.isCompatible(variant) || VARIANT_TEXT_CSV - .isCompatible(variant)); + || VARIANT_TEXT_YAML.isCompatible(variant) + || VARIANT_TEXT_CSV.isCompatible(variant)); } @Override public float score(Object source, Variant target, Resource resource) { - float result = -1.0F; + final float result; if (source instanceof JacksonRepresentation) { result = 1.0F; + } else if (target == null) { + result = 0.5F; + } else if (isCompatible(target)) { + result = 0.8F; } else { - if (target == null) { - result = 0.5F; - } else if (isCompatible(target)) { - result = 0.8F; - } else { - result = 0.5F; - } + result = 0.5F; } return result; } @Override - public float score(Representation source, Class target, - Resource resource) { - float result = -1.0F; + public float score(Representation source, Class target, Resource resource) { + final float result; if (source instanceof JacksonRepresentation) { result = 1.0F; - } else if ((target != null) - && JacksonRepresentation.class.isAssignableFrom(target)) { + } else if ((target != null) && JacksonRepresentation.class.isAssignableFrom(target)) { result = 1.0F; } else if (isCompatible(source)) { result = 0.8F; + } else { + result = -1.0F; } return result; @@ -175,9 +159,9 @@ public float score(Representation source, Class target, @SuppressWarnings("unchecked") @Override - public T toObject(Representation source, Class target, - Resource resource) throws IOException { - Object result = null; + public T toObject(Representation source, Class target, Resource resource) + throws IOException { + final Object result; // The source for the Jackson conversion JacksonRepresentation jacksonSource = null; @@ -187,26 +171,25 @@ public T toObject(Representation source, Class target, jacksonSource = create(source, target); } - if (jacksonSource != null) { - // Handle the conversion - if ((target != null) - && JacksonRepresentation.class.isAssignableFrom(target)) { + if (jacksonSource != null) { // Handle the conversion + if ((target != null) && JacksonRepresentation.class.isAssignableFrom(target)) { result = jacksonSource; } else { result = jacksonSource.getObject(); } + } else { + result = null; } return (T) result; } @Override - public Representation toRepresentation(Object source, Variant target, - Resource resource) { + public Representation toRepresentation(Object source, Variant target, Resource resource) { Representation result = null; - if (source instanceof JacksonRepresentation) { - result = (JacksonRepresentation) source; + if (source instanceof JacksonRepresentation jacksonRepresentation) { + result = jacksonRepresentation; } else { if (target.getMediaType() == null) { target.setMediaType(MediaType.APPLICATION_JSON); @@ -220,8 +203,7 @@ public Representation toRepresentation(Object source, Variant target, } @Override - public void updatePreferences(List> preferences, - Class entity) { + public void updatePreferences(List> preferences, Class entity) { updatePreferences(preferences, MediaType.APPLICATION_JSON, 1.0F); updatePreferences(preferences, MediaType.APPLICATION_JSON_SMILE, 1.0F); updatePreferences(preferences, MediaType.APPLICATION_XML, 1.0F); @@ -230,5 +212,4 @@ public void updatePreferences(List> preferences, updatePreferences(preferences, MediaType.TEXT_YAML, 1.0F); updatePreferences(preferences, MediaType.TEXT_CSV, 1.0F); } - } diff --git a/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/JacksonRepresentation.java b/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/JacksonRepresentation.java index aa930b3931..bdb7c5f311 100644 --- a/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/JacksonRepresentation.java +++ b/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/JacksonRepresentation.java @@ -1,23 +1,13 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.jackson; -import java.io.IOException; -import java.io.OutputStream; - -import org.restlet.data.MediaType; -import org.restlet.engine.Edition; -import org.restlet.ext.jackson.internal.XmlFactoryProvider; -import org.restlet.representation.OutputRepresentation; -import org.restlet.representation.Representation; - import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator.Feature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -30,47 +20,49 @@ import com.fasterxml.jackson.dataformat.xml.XmlFactory; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.io.IOException; +import java.io.OutputStream; +import org.restlet.data.MediaType; +import org.restlet.ext.jackson.internal.XmlFactoryProvider; +import org.restlet.representation.OutputRepresentation; +import org.restlet.representation.Representation; /** - * Representation based on the Jackson library. It can serialize and deserialize - * automatically in JSON, JSON binary (Smile), XML, YAML and CSV.
+ * Representation based on the Jackson library. It can serialize and deserialize automatically in + * JSON, JSON binary (Smile), XML, YAML, and CSV.
*
- * SECURITY WARNING: Using XML parsers configured to not prevent nor limit - * document type definition (DTD) entity resolution can expose the parser to an - * XML Entity Expansion injection attack. - * + * SECURITY WARNING: Using XML parsers configured to not prevent nor limit document type definition + * (DTD) entity resolution can expose the parser to an XML Entity Expansion injection attack. + * * @see Jackson project * @see XML - * Entity Expansion injection attack + * href="https://github.com/restlet/restlet-framework-java/wiki/XEE-security-enhancements">XML + * Entity Expansion injection attack * @author Jerome Louvel - * @param - * The type to wrap. + * @param The type to wrap. */ public class JacksonRepresentation extends OutputRepresentation { /** - * True for expanding entity references when parsing XML representations. - * Default value provided by system property - * "org.restlet.ext.xml.expandingEntityRefs", false by default. + * True for expanding entity references when parsing XML representations. Default value provided + * by system property "org.restlet.ext.xml.expandingEntityRefs", false by default. */ - public final static boolean XML_EXPANDING_ENTITY_REFS = Boolean - .getBoolean("org.restlet.ext.xml.expandingEntityRefs"); + public static final boolean XML_EXPANDING_ENTITY_REFS = + Boolean.getBoolean("org.restlet.ext.xml.expandingEntityRefs"); /** - * True for validating DTD documents when parsing XML representations. - * Default value provided by system property - * "org.restlet.ext.xml.validatingDtd", false by default. + * True for validating DTD documents when parsing XML representations. Default value provided by + * system property "org.restlet.ext.xml.validatingDtd", false by default. */ - public final static boolean XML_VALIDATING_DTD = Boolean - .getBoolean("org.restlet.ext.xml.validatingDtd"); + public static final boolean XML_VALIDATING_DTD = + Boolean.getBoolean("org.restlet.ext.xml.validatingDtd"); /** The modifiable Jackson CSV schema. */ private CsvSchema csvSchema; /** - * Specifies that the parser will expand entity reference nodes. By default - * the value of this is set to false. + * Specifies that the parser will expand entity reference nodes. By default the value of this is + * set to false. */ private volatile boolean expandingEntityRefs; @@ -93,28 +85,24 @@ public class JacksonRepresentation extends OutputRepresentation { private volatile Representation representation; /** - * Indicates the desire for validating this type of XML representations - * against a DTD. Note that for XML schema or Relax NG validation, use the - * "schema" property instead. - * + * Indicates the desire for validating this type of XML representations against a DTD. Note that + * for XML schema or Relax NG validation, use the "schema" property instead. + * * @see javax.xml.parsers.DocumentBuilderFactory#setValidating(boolean) */ private volatile boolean validatingDtd; /** * Constructor. - * - * @param mediaType - * The target media type. - * @param object - * The object to format. + * + * @param mediaType The target media type. + * @param object The object to format. */ @SuppressWarnings("unchecked") public JacksonRepresentation(MediaType mediaType, T object) { super(mediaType); this.object = object; - this.objectClass = (Class) ((object == null) ? null : object - .getClass()); + this.objectClass = (Class) ((object == null) ? null : object.getClass()); this.representation = null; this.objectMapper = null; this.objectReader = null; @@ -126,14 +114,11 @@ public JacksonRepresentation(MediaType mediaType, T object) { /** * Constructor. - * - * @param representation - * The representation to parse. - * @param objectClass - * The object class to instantiate. + * + * @param representation The representation to parse. + * @param objectClass The object class to instantiate. */ - public JacksonRepresentation(Representation representation, - Class objectClass) { + public JacksonRepresentation(Representation representation, Class objectClass) { super(representation.getMediaType()); this.object = null; this.objectClass = objectClass; @@ -143,25 +128,22 @@ public JacksonRepresentation(Representation representation, this.objectWriter = null; this.csvSchema = null; this.expandingEntityRefs = XML_EXPANDING_ENTITY_REFS; - this.validatingDtd = XML_VALIDATING_DTD; + this.validatingDtd = XML_VALIDATING_DTD; } /** * Constructor for the JSON media type. - * - * @param object - * The object to format. + * + * @param object The object to format. */ public JacksonRepresentation(T object) { this(MediaType.APPLICATION_JSON, object); } /** - * Creates a Jackson CSV schema based on a mapper and the current object - * class. - * - * @param csvMapper - * The source CSV mapper. + * Creates a Jackson CSV schema based on a mapper and the current object class. + * + * @param csvMapper The source CSV mapper. * @return A Jackson CSV schema */ protected CsvSchema createCsvSchema(CsvMapper csvMapper) { @@ -169,13 +151,13 @@ protected CsvSchema createCsvSchema(CsvMapper csvMapper) { } /** - * Creates a Jackson object mapper based on a media type. It supports JSON, - * JSON Smile, XML, YAML and CSV. - * + * Creates a Jackson object mapper based on a media type. It supports JSON, JSON Smile, XML, + * YAML, and CSV. + * * @return The Jackson object mapper. */ protected ObjectMapper createObjectMapper() { - ObjectMapper result = null; + final ObjectMapper result; if (MediaType.APPLICATION_JSON.isCompatible(getMediaType())) { JsonFactory jsonFactory = new JsonFactory(); @@ -188,21 +170,12 @@ protected ObjectMapper createObjectMapper() { } else if (MediaType.APPLICATION_XML.isCompatible(getMediaType()) || MediaType.TEXT_XML.isCompatible(getMediaType())) { - if (Edition.ANDROID.isCurrentEdition() && XmlFactoryProvider.inputFactoryProvider == null) { - XmlFactoryProvider.inputFactoryProvider = new com.ctc.wstx.osgi.InputFactoryProviderImpl(); - } - if (Edition.ANDROID.isCurrentEdition() && XmlFactoryProvider.outputFactoryProvider == null) { - XmlFactoryProvider.outputFactoryProvider = new com.ctc.wstx.osgi.OutputFactoryProviderImpl(); - } - javax.xml.stream.XMLInputFactory xif = XmlFactoryProvider.newInputFactory(); xif.setProperty( javax.xml.stream.XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, isExpandingEntityRefs()); - xif.setProperty(javax.xml.stream.XMLInputFactory.SUPPORT_DTD, - isExpandingEntityRefs()); - xif.setProperty(javax.xml.stream.XMLInputFactory.IS_VALIDATING, - isValidatingDtd()); + xif.setProperty(javax.xml.stream.XMLInputFactory.SUPPORT_DTD, isExpandingEntityRefs()); + xif.setProperty(javax.xml.stream.XMLInputFactory.IS_VALIDATING, isValidatingDtd()); javax.xml.stream.XMLOutputFactory xof = XmlFactoryProvider.newOutputFactory(); XmlFactory xmlFactory = new XmlFactory(xif, xof); xmlFactory.configure(Feature.AUTO_CLOSE_TARGET, false); @@ -227,13 +200,13 @@ protected ObjectMapper createObjectMapper() { } /** - * Creates a Jackson object reader based on a mapper. Has a special handling - * for CSV media types. - * + * Creates a Jackson object reader based on a mapper. Has a special handling for CSV media + * types. + * * @return The Jackson object reader. */ protected ObjectReader createObjectReader() { - ObjectReader result = null; + final ObjectReader result; if (MediaType.TEXT_CSV.isCompatible(getMediaType())) { CsvMapper csvMapper = (CsvMapper) getObjectMapper(); @@ -247,13 +220,13 @@ protected ObjectReader createObjectReader() { } /** - * Creates a Jackson object writer based on a mapper. Has a special handling - * for CSV media types. - * + * Creates a Jackson object writer based on a mapper. Has a special handling for CSV media + * types. + * * @return The Jackson object writer. */ protected ObjectWriter createObjectWriter() { - ObjectWriter result = null; + final ObjectWriter result; if (MediaType.TEXT_CSV.isCompatible(getMediaType())) { CsvMapper csvMapper = (CsvMapper) getObjectMapper(); @@ -268,7 +241,7 @@ protected ObjectWriter createObjectWriter() { /** * Returns the modifiable Jackson CSV schema. - * + * * @return The modifiable Jackson CSV schema. */ public CsvSchema getCsvSchema() { @@ -280,9 +253,8 @@ public CsvSchema getCsvSchema() { } /** - * Returns the wrapped object, deserializing the representation with Jackson - * if necessary. - * + * Returns the wrapped object, deserializing the representation with Jackson if necessary. + * * @return The wrapped object. * @throws IOException */ @@ -292,8 +264,7 @@ public T getObject() throws IOException { if (this.object != null) { result = this.object; } else if (this.representation != null) { - result = getObjectReader().readValue( - this.representation.getStream()); + result = getObjectReader().readValue(this.representation.getStream()); } return result; @@ -301,7 +272,7 @@ public T getObject() throws IOException { /** * Returns the object class to instantiate. - * + * * @return The object class to instantiate. */ public Class getObjectClass() { @@ -309,9 +280,8 @@ public Class getObjectClass() { } /** - * Returns the modifiable Jackson object mapper. Useful to customize - * mappings. - * + * Returns the modifiable Jackson object mapper. Useful to customize mappings. + * * @return The modifiable Jackson object mapper. */ public ObjectMapper getObjectMapper() { @@ -323,9 +293,8 @@ public ObjectMapper getObjectMapper() { } /** - * Returns the modifiable Jackson object reader. Useful to customize - * deserialization. - * + * Returns the modifiable Jackson object reader. Useful to customize deserialization. + * * @return The modifiable Jackson object reader. */ public ObjectReader getObjectReader() { @@ -337,9 +306,8 @@ public ObjectReader getObjectReader() { } /** - * Returns the modifiable Jackson object writer. Useful to customize - * serialization. - * + * Returns the modifiable Jackson object writer. Useful to customize serialization. + * * @return The modifiable Jackson object writer. */ public ObjectWriter getObjectWriter() { @@ -351,19 +319,19 @@ public ObjectWriter getObjectWriter() { } /** - * Indicates if the parser expands entity reference nodes. - * By default, the value of this is set to true. - * + * Indicates if the parser expands entity reference nodes. By default, the value of this is set + * to true. + * * @return True if the parser expands entity reference nodes. */ public boolean isExpandingEntityRefs() { - return expandingEntityRefs; + return expandingEntityRefs; } /** - * Indicates the desire for validating this type of XML representations - * against an XML schema if one is referenced within the contents. - * + * Indicates the desire for validating this type of XML representations against an XML schema if + * one is referenced within the contents. + * * @return True if the schema-based validation is enabled. */ public boolean isValidatingDtd() { @@ -372,20 +340,18 @@ public boolean isValidatingDtd() { /** * Sets the Jackson CSV schema. - * - * @param csvSchema - * The Jackson CSV schema. + * + * @param csvSchema The Jackson CSV schema. */ public void setCsvSchema(CsvSchema csvSchema) { this.csvSchema = csvSchema; } /** - * Indicates if the parser expands entity reference nodes. - * By default, the value of this is set to true. - * - * @param expandEntityRefs - * True if the parser expands entity reference nodes. + * Indicates if the parser expands entity reference nodes. By default, the value of this is set + * to true. + * + * @param expandEntityRefs True if the parser expands entity reference nodes. */ public void setExpandingEntityRefs(boolean expandEntityRefs) { this.expandingEntityRefs = expandEntityRefs; @@ -393,9 +359,8 @@ public void setExpandingEntityRefs(boolean expandEntityRefs) { /** * Sets the object to format. - * - * @param object - * The object to format. + * + * @param object The object to format. */ public void setObject(T object) { this.object = object; @@ -403,9 +368,8 @@ public void setObject(T object) { /** * Sets the object class to instantiate. - * - * @param objectClass - * The object class to instantiate. + * + * @param objectClass The object class to instantiate. */ public void setObjectClass(Class objectClass) { this.objectClass = objectClass; @@ -413,9 +377,8 @@ public void setObjectClass(Class objectClass) { /** * Sets the Jackson object mapper. - * - * @param objectMapper - * The Jackson object mapper. + * + * @param objectMapper The Jackson object mapper. */ public void setObjectMapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; @@ -423,9 +386,8 @@ public void setObjectMapper(ObjectMapper objectMapper) { /** * Sets the Jackson object reader. - * - * @param objectReader - * The Jackson object reader. + * + * @param objectReader The Jackson object reader. */ public void setObjectReader(ObjectReader objectReader) { this.objectReader = objectReader; @@ -433,20 +395,18 @@ public void setObjectReader(ObjectReader objectReader) { /** * Sets the Jackson object writer. - * - * @param objectWriter - * The Jackson object writer. + * + * @param objectWriter The Jackson object writer. */ public void setObjectWriter(ObjectWriter objectWriter) { this.objectWriter = objectWriter; } /** - * Indicates the desire for validating this type of XML representations - * against an XML schema if one is referenced within the contents. - * - * @param validating - * The new validation flag to set. + * Indicates the desire for validating this type of XML representations against an XML schema if + * one is referenced within the contents. + * + * @param validating The new validation flag to set. */ public void setValidatingDtd(boolean validating) { this.validatingDtd = validating; diff --git a/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/internal/XmlFactoryProvider.java b/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/internal/XmlFactoryProvider.java index 0f1c43a43f..374dada276 100644 --- a/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/internal/XmlFactoryProvider.java +++ b/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/internal/XmlFactoryProvider.java @@ -1,71 +1,73 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.jackson.internal; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; - import org.codehaus.stax2.osgi.Stax2InputFactoryProvider; import org.codehaus.stax2.osgi.Stax2OutputFactoryProvider; +import org.restlet.engine.Edition; /** - * Provides {@link javax.xml.stream.XMLInputFactory} - * and {@link javax.xml.stream.XMLOutputFactory} + * Provides {@link javax.xml.stream.XMLInputFactory} and {@link javax.xml.stream.XMLOutputFactory} * in an OSGI context. * - * In a no-OSGI context, the factories are retrieved with java service loader. + *

In a no-OSGI context, the factories are retrieved with java service loader. * * @author Manuel Boillod */ public class XmlFactoryProvider { /** - * Allow to explicitly set the Stax2InputFactory instance in OSGI context. - * In a no-OSGI context, the factory is retrieved with java service loader. - * - *

Note: Stax2 implementation is provided by woodstox library which - * is a dependency of Jackson.

+ * Allow explicitly setting the Stax2InputFactory instance in OSGI context. In a no-OSGI + * context, the factory is retrieved with a java service loader. * - * @see org.restlet.ext.jackson.internal.Activator + *

Note: Stax2 implementation is provided by woodstox library, which is a dependency of + * Jackson. */ public static Stax2InputFactoryProvider inputFactoryProvider = null; /** - * Allow to explicitly set the Stax2OutputFactoryProvider instance in OSGI context. - * In a no-OSGI context, the factory is retrieved with java service loader. + * Allow explicitly setting the Stax2OutputFactoryProvider instance in OSGI context. In a + * no-OSGI context, the factory is retrieved with a java service loader. * - *

Note: Stax2 implementation is provided by woodstox library which - * is a dependency of Jackson.

- * - * @see org.restlet.ext.jackson.internal.Activator + *

Note: Stax2 implementation is provided by woodstox library, which is a dependency of + * Jackson. */ public static Stax2OutputFactoryProvider outputFactoryProvider = null; - /** - * Returns an instance of {@link javax.xml.stream.XMLInputFactory} - * according to the classpath. + * Returns an instance of {@link javax.xml.stream.XMLInputFactory} according to the classpath. */ public static XMLInputFactory newInputFactory() { - return inputFactoryProvider != null ? - inputFactoryProvider.createInputFactory() : - XMLInputFactory.newFactory(); + if (inputFactoryProvider == null) { + if (Edition.ANDROID.isCurrentEdition()) { + inputFactoryProvider = new com.ctc.wstx.osgi.InputFactoryProviderImpl(); + } else { + return XMLInputFactory.newFactory(); + } + } + return inputFactoryProvider.createInputFactory(); } /** - * Returns an instance of {@link javax.xml.stream.XMLInputFactory} - * according to the classpath. + * Returns an instance of {@link javax.xml.stream.XMLInputFactory} according to the classpath. */ public static XMLOutputFactory newOutputFactory() { - return outputFactoryProvider != null ? - outputFactoryProvider.createOutputFactory() : - XMLOutputFactory.newFactory(); + if (outputFactoryProvider == null) { + if (Edition.ANDROID.isCurrentEdition()) { + outputFactoryProvider = new com.ctc.wstx.osgi.OutputFactoryProviderImpl(); + } else { + return XMLOutputFactory.newFactory(); + } + } + + return outputFactoryProvider.createOutputFactory(); } } diff --git a/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/package-info.java b/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/package-info.java new file mode 100644 index 0000000000..25c3b0b400 --- /dev/null +++ b/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/package-info.java @@ -0,0 +1,10 @@ +/** + * Integration with Jackson 2.10. Jackson is a high-performance JSON processor able to serialize + * objects to JSON and back again. + * + * @since Restlet 2.0 + * @see Jackson Web site + * @see User + * Guide - Jackson extension + */ +package org.restlet.ext.jackson; diff --git a/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/package.html b/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/package.html deleted file mode 100644 index b3ca770c1e..0000000000 --- a/org.restlet.ext.jackson/src/main/java/org/restlet/ext/jackson/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - -Integration with Jackson 2.10. Jackson is a high-performance JSON processor able to serialize objects to JSON and back again. - -@since Restlet 2.0 -@see Jackson Web site -@see User Guide - Jackson extension - - diff --git a/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/Customer.java b/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/Customer.java index c7d1e90011..4c7873fb7b 100644 --- a/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/Customer.java +++ b/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/Customer.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.jackson; import java.util.List; @@ -39,5 +38,4 @@ public void setLastName(String lastName) { public List getInvoices() { return invoices; } - } diff --git a/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/Invoice.java b/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/Invoice.java index 1151102489..b1da7abd0a 100644 --- a/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/Invoice.java +++ b/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/Invoice.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.jackson; import java.util.Date; @@ -42,5 +41,4 @@ public void setDate(Date date) { public void setPaid(boolean paid) { this.paid = paid; } - } diff --git a/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonTestCase.java b/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonTestCase.java index 1ec6a46c68..a66b26ed85 100644 --- a/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonTestCase.java +++ b/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonTestCase.java @@ -1,15 +1,17 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.jackson; +import static org.junit.jupiter.api.Assertions.assertThrows; + import com.fasterxml.jackson.core.JsonParseException; +import java.util.Date; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; @@ -17,16 +19,12 @@ import org.restlet.representation.StringRepresentation; import org.restlet.resource.ClientResource; -import java.util.Date; - -import static org.junit.jupiter.api.Assertions.assertThrows; - /** * Unit test for the Jackson extension. * * @author Jerome Louvel */ -public class JacksonTestCase { +class JacksonTestCase { protected Customer createCustomer() { Date date = new Date(1356533333882L); @@ -60,17 +58,20 @@ protected Invoice createInvoice() { } @Test - public void testCsv() throws Exception { + void testCsv() throws Exception { Invoice invoice = createInvoice(); - JacksonRepresentation rep = new JacksonRepresentation<>(MediaType.TEXT_CSV, invoice); + JacksonRepresentation rep = + new JacksonRepresentation<>(MediaType.TEXT_CSV, invoice); String text = rep.getText(); Assertions.assertEquals("12456,1356533333882,false\n", text); - rep = new JacksonRepresentation<>(new StringRepresentation(text, rep.getMediaType()), Invoice.class); + rep = + new JacksonRepresentation<>( + new StringRepresentation(text, rep.getMediaType()), Invoice.class); assertEquals(invoice, rep.getObject()); } @Test - public void testException() throws Exception { + void testException() throws Exception { Customer customer = createCustomer(); MyException me = new MyException(customer, "CUST-1234"); @@ -78,56 +79,71 @@ public void testException() throws Exception { // Unless we are in debug mode, hide those properties me.setStackTrace(new StackTraceElement[0]); - JacksonRepresentation rep = new JacksonRepresentation<>(MediaType.APPLICATION_JSON, me); + JacksonRepresentation rep = + new JacksonRepresentation<>(MediaType.APPLICATION_JSON, me); - rep = new JacksonRepresentation<>(new StringRepresentation(rep.getText(), rep.getMediaType()), MyException.class); + rep = + new JacksonRepresentation<>( + new StringRepresentation(rep.getText(), rep.getMediaType()), + MyException.class); assertEquals(me, rep.getObject()); } @Test - public void testJson() throws Exception { + void testJson() throws Exception { Customer customer = createCustomer(); - JacksonRepresentation rep = new JacksonRepresentation<>(MediaType.APPLICATION_JSON, customer); + JacksonRepresentation rep = + new JacksonRepresentation<>(MediaType.APPLICATION_JSON, customer); String text = rep.getText(); Assertions.assertEquals( "{\"firstName\":\"Foo\",\"lastName\":\"Bar\",\"invoices\":[{\"date\":1356533333882,\"amount\":12456,\"paid\":false},{\"date\":1356533333882,\"amount\":7890,\"paid\":true}]}", text); - rep = new JacksonRepresentation<>(new StringRepresentation(text, rep.getMediaType()), Customer.class); + rep = + new JacksonRepresentation<>( + new StringRepresentation(text, rep.getMediaType()), Customer.class); assertEquals(customer, rep.getObject()); } @Test - public void testSmile() throws Exception { + void testSmile() throws Exception { Customer customer = createCustomer(); - JacksonRepresentation rep = new JacksonRepresentation<>(MediaType.APPLICATION_JSON_SMILE, customer); + JacksonRepresentation rep = + new JacksonRepresentation<>(MediaType.APPLICATION_JSON_SMILE, customer); rep = new JacksonRepresentation<>(rep, Customer.class); assertEquals(customer, rep.getObject()); } @Test - public void testXml() throws Exception { + void testXml() throws Exception { Customer customer = createCustomer(); - JacksonRepresentation rep = new JacksonRepresentation<>(MediaType.APPLICATION_XML, customer); + JacksonRepresentation rep = + new JacksonRepresentation<>(MediaType.APPLICATION_XML, customer); String text = rep.getText(); Assertions.assertEquals( "FooBar135653333388212456false13565333338827890true", text); - rep = new JacksonRepresentation<>(new StringRepresentation(text, rep.getMediaType()), Customer.class); + rep = + new JacksonRepresentation<>( + new StringRepresentation(text, rep.getMediaType()), Customer.class); assertEquals(customer, rep.getObject()); } @Test - public void testXmlBomb() { - ClientResource cr = new ClientResource("clap://class/org/restlet/ext/jackson/jacksonBomb.xml"); + void testXmlBomb() { + ClientResource cr = + new ClientResource("clap://class/org/restlet/ext/jackson/jacksonBomb.xml"); Representation xmlRep = cr.get(); xmlRep.setMediaType(MediaType.APPLICATION_XML); - Exception exception = assertThrows(JsonParseException.class, - () -> new JacksonRepresentation<>(xmlRep, Customer.class).getObject()); - String expected = """ + Exception exception = + assertThrows( + JsonParseException.class, + () -> new JacksonRepresentation<>(xmlRep, Customer.class).getObject()); + String expected = + """ Undeclared general entity "lol10" at [row,col {unknown-source}]: [14,31] at [Source: (BufferedInputStream); line: 14, column: 32]"""; @@ -135,12 +151,14 @@ public void testXmlBomb() { } @Test - public void testYaml() throws Exception { + void testYaml() throws Exception { Customer customer = createCustomer(); - JacksonRepresentation rep = new JacksonRepresentation<>(MediaType.APPLICATION_YAML, customer); + JacksonRepresentation rep = + new JacksonRepresentation<>(MediaType.APPLICATION_YAML, customer); String text = rep.getText(); - Assertions.assertEquals(""" + Assertions.assertEquals( + """ --- firstName: "Foo" lastName: "Bar" @@ -151,9 +169,12 @@ public void testYaml() throws Exception { - date: 1356533333882 amount: 7890 paid: true - """, text); + """, + text); - rep = new JacksonRepresentation<>(new StringRepresentation(text, rep.getMediaType()), Customer.class); + rep = + new JacksonRepresentation<>( + new StringRepresentation(text, rep.getMediaType()), Customer.class); assertEquals(customer, rep.getObject()); } @@ -161,14 +182,16 @@ protected void assertEquals(Customer customer1, Customer customer2) { Assertions.assertEquals(customer1.getFirstName(), customer2.getFirstName()); Assertions.assertEquals(customer1.getLastName(), customer2.getLastName()); Assertions.assertEquals(customer1.getInvoices().size(), customer2.getInvoices().size()); - Assertions.assertEquals(customer1.getInvoices().get(0).getAmount(), customer2 - .getInvoices().get(0).getAmount()); - Assertions.assertEquals(customer1.getInvoices().get(1).getAmount(), customer2 - .getInvoices().get(1).getAmount()); - Assertions.assertEquals(customer1.getInvoices().get(0).getDate(), customer2 - .getInvoices().get(0).getDate()); - Assertions.assertEquals(customer1.getInvoices().get(1).getDate(), customer2 - .getInvoices().get(1).getDate()); + Assertions.assertEquals( + customer1.getInvoices().get(0).getAmount(), + customer2.getInvoices().get(0).getAmount()); + Assertions.assertEquals( + customer1.getInvoices().get(1).getAmount(), + customer2.getInvoices().get(1).getAmount()); + Assertions.assertEquals( + customer1.getInvoices().get(0).getDate(), customer2.getInvoices().get(0).getDate()); + Assertions.assertEquals( + customer1.getInvoices().get(1).getDate(), customer2.getInvoices().get(1).getDate()); } protected void assertEquals(Invoice invoice1, Invoice invoice2) { diff --git a/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/MyException.java b/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/MyException.java index f900aa764d..2519fae704 100644 --- a/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/MyException.java +++ b/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/MyException.java @@ -1,17 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.jackson; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -@JsonIgnoreProperties({ "cause", "localizedMessage", "suppressed", "message" }) +@JsonIgnoreProperties({"cause", "localizedMessage", "suppressed", "message"}) public class MyException extends Exception { private static final long serialVersionUID = 1L; @@ -20,16 +19,18 @@ public class MyException extends Exception { private String errorCode; - public MyException() { - } + public MyException() {} public MyException(Customer customer, String errorCode) { - this(customer, errorCode, "Customer exception detected", null, true, - true); + this(customer, errorCode, "Customer exception detected", null, true, true); } - public MyException(Customer customer, String errorCode, String message, - Throwable cause, boolean enableSuppression, + public MyException( + Customer customer, + String errorCode, + String message, + Throwable cause, + boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); this.customer = customer; @@ -51,5 +52,4 @@ public void setCustomer(Customer customer) { public void setErrorCode(String errorCode) { this.errorCode = errorCode; } - } diff --git a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonConverter.java b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonConverter.java index fc9016babe..e123956c3e 100644 --- a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonConverter.java +++ b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonConverter.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.json; import java.io.IOException; import java.util.List; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -25,16 +23,14 @@ import org.restlet.resource.Resource; /** - * Converter between the JSON API (more precisely {@link JSONArray}, - * {@link JSONObject} and {@link JSONTokener} instances) and Representation - * classes. - * + * Converter between the JSON API (more precisely {@link JSONArray}, {@link JSONObject} and {@link + * JSONTokener} instances) and Representation classes. + * * @author Jerome Louvel */ public class JsonConverter extends ConverterHelper { - private static final VariantInfo VARIANT_JSON = new VariantInfo( - MediaType.APPLICATION_JSON); + private static final VariantInfo VARIANT_JSON = new VariantInfo(MediaType.APPLICATION_JSON); @Override public List> getObjectClasses(Variant source) { @@ -53,11 +49,9 @@ public List> getObjectClasses(Variant source) { public List getVariants(Class source) { List result = null; - if (JSONArray.class.isAssignableFrom(source)) { - result = addVariant(result, VARIANT_JSON); - } else if (JSONObject.class.isAssignableFrom(source)) { - result = addVariant(result, VARIANT_JSON); - } else if (JSONTokener.class.isAssignableFrom(source)) { + if (JSONArray.class.isAssignableFrom(source) + || JSONObject.class.isAssignableFrom(source) + || JSONTokener.class.isAssignableFrom(source)) { result = addVariant(result, VARIANT_JSON); } @@ -68,12 +62,12 @@ public List getVariants(Class source) { public float score(Object source, Variant target, Resource resource) { float result = -1.0F; - if ((source instanceof JSONArray) || (source instanceof JSONObject) + if ((source instanceof JSONArray) + || (source instanceof JSONObject) || (source instanceof JSONTokener)) { if (target == null) { result = 0.5F; - } else if (MediaType.APPLICATION_JSON.isCompatible(target - .getMediaType())) { + } else if (MediaType.APPLICATION_JSON.isCompatible(target.getMediaType())) { result = 1.0F; } else { result = 0.5F; @@ -84,30 +78,26 @@ public float score(Object source, Variant target, Resource resource) { } @Override - public float score(Representation source, Class target, - Resource resource) { + public float score(Representation source, Class target, Resource resource) { float result = -1.0F; if (target != null) { if (JsonRepresentation.class.isAssignableFrom(target)) { result = 1.0F; } else if (JSONArray.class.isAssignableFrom(target)) { - if (MediaType.APPLICATION_JSON.isCompatible(source - .getMediaType())) { + if (MediaType.APPLICATION_JSON.isCompatible(source.getMediaType())) { result = 1.0F; } else { result = 0.5F; } } else if (JSONObject.class.isAssignableFrom(target)) { - if (MediaType.APPLICATION_JSON.isCompatible(source - .getMediaType())) { + if (MediaType.APPLICATION_JSON.isCompatible(source.getMediaType())) { result = 1.0F; } else { result = 0.5F; } } else if (JSONTokener.class.isAssignableFrom(target)) { - if (MediaType.APPLICATION_JSON.isCompatible(source - .getMediaType())) { + if (MediaType.APPLICATION_JSON.isCompatible(source.getMediaType())) { result = 1.0F; } else { result = 0.5F; @@ -119,11 +109,11 @@ public float score(Representation source, Class target, } @Override - public T toObject(Representation source, Class target, - Resource resource) throws IOException { - JsonRepresentation jsonSource = null; - if (source instanceof JsonRepresentation) { - jsonSource = (JsonRepresentation) source; + public T toObject(Representation source, Class target, Resource resource) + throws IOException { + final JsonRepresentation jsonSource; + if (source instanceof JsonRepresentation jsonRepresentation) { + jsonSource = jsonRepresentation; } else { jsonSource = new JsonRepresentation(source); } @@ -134,16 +124,15 @@ public T toObject(Representation source, Class target, try { result = target.cast(jsonSource.getJsonArray()); } catch (JSONException e) { - IOException ioe = new IOException( - "Unable to convert to JSON array"); + IOException ioe = new IOException("Unable to convert to JSON array"); ioe.initCause(e); + throw ioe; } } else if (JSONObject.class.isAssignableFrom(target)) { try { result = target.cast(jsonSource.getJsonObject()); } catch (JSONException e) { - IOException ioe = new IOException( - "Unable to convert to JSON object"); + IOException ioe = new IOException("Unable to convert to JSON object"); ioe.initCause(e); throw ioe; } @@ -151,8 +140,7 @@ public T toObject(Representation source, Class target, try { result = target.cast(jsonSource.getJsonTokener()); } catch (JSONException e) { - IOException ioe = new IOException( - "Unable to convert to JSON tokener"); + IOException ioe = new IOException("Unable to convert to JSON tokener"); ioe.initCause(e); throw ioe; } @@ -165,30 +153,26 @@ public T toObject(Representation source, Class target, } @Override - public Representation toRepresentation(Object source, Variant target, - Resource resource) { + public Representation toRepresentation(Object source, Variant target, Resource resource) { Representation result = null; - if (source instanceof JSONArray) { - result = new JsonRepresentation((JSONArray) source); - } else if (source instanceof JSONObject) { - result = new JsonRepresentation((JSONObject) source); - } else if (source instanceof JSONTokener) { - result = new JsonRepresentation((JSONTokener) source); + if (source instanceof JSONArray jsonArray) { + result = new JsonRepresentation(jsonArray); + } else if (source instanceof JSONObject jsonObject) { + result = new JsonRepresentation(jsonObject); + } else if (source instanceof JSONTokener jsonTokener) { + result = new JsonRepresentation(jsonTokener); } return result; - } @Override - public void updatePreferences(List> preferences, - Class entity) { + public void updatePreferences(List> preferences, Class entity) { if (JSONArray.class.isAssignableFrom(entity) || JSONObject.class.isAssignableFrom(entity) || JSONTokener.class.isAssignableFrom(entity)) { updatePreferences(preferences, MediaType.APPLICATION_JSON, 1.0F); } } - } diff --git a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonRepresentation.java b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonRepresentation.java index f7e95a68ee..d3eea56b13 100644 --- a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonRepresentation.java +++ b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonRepresentation.java @@ -1,18 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.json; import java.io.IOException; import java.io.Writer; import java.util.Map; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -25,9 +23,9 @@ import org.restlet.representation.WriterRepresentation; /** - * Representation based on a JSON document. JSON stands for JavaScript Object - * Notation and is a lightweight data-interchange format. - * + * Representation based on a JSON document. JSON stands for JavaScript Object Notation and is a + * lightweight data-interchange format. + * * @author Jerome Louvel * @see JSON home */ @@ -47,9 +45,8 @@ public class JsonRepresentation extends WriterRepresentation { /** * Constructor from a JSON array. - * - * @param jsonArray - * The JSON array. + * + * @param jsonArray The JSON array. */ public JsonRepresentation(JSONArray jsonArray) { super(MediaType.APPLICATION_JSON); @@ -58,9 +55,8 @@ public JsonRepresentation(JSONArray jsonArray) { /** * Constructor from a JSON object. - * - * @param jsonObject - * The JSON object. + * + * @param jsonObject The JSON object. */ public JsonRepresentation(JSONObject jsonObject) { super(MediaType.APPLICATION_JSON); @@ -69,9 +65,8 @@ public JsonRepresentation(JSONObject jsonObject) { /** * Constructor from a JSON stringer. - * - * @param jsonStringer - * The JSON stringer. + * + * @param jsonStringer The JSON stringer. */ public JsonRepresentation(JSONStringer jsonStringer) { super(MediaType.APPLICATION_JSON); @@ -80,9 +75,8 @@ public JsonRepresentation(JSONStringer jsonStringer) { /** * Constructor from a JSON tokener. - * - * @param jsonTokener - * The JSON tokener. + * + * @param jsonTokener The JSON tokener. */ public JsonRepresentation(JSONTokener jsonTokener) { super(MediaType.APPLICATION_JSON); @@ -91,9 +85,8 @@ public JsonRepresentation(JSONTokener jsonTokener) { /** * Constructor from a map object. - * - * @param map - * The map to convert to JSON. + * + * @param map The map to convert to JSON. * @see org.json.JSONObject#JSONObject(Map) */ public JsonRepresentation(Map map) { @@ -102,9 +95,8 @@ public JsonRepresentation(Map map) { /** * Constructor from a bean using reflection to generate JSON names. - * - * @param bean - * The bean to convert to JSON. + * + * @param bean The bean to convert to JSON. * @see org.json.JSONObject#JSONObject(Object) */ public JsonRepresentation(Object bean) { @@ -113,21 +105,18 @@ public JsonRepresentation(Object bean) { /** * Constructor. - * - * @param jsonRepresentation - * A source JSON representation to parse. + * + * @param jsonRepresentation A source JSON representation to parse. */ public JsonRepresentation(Representation jsonRepresentation) { - super((jsonRepresentation == null) ? null : jsonRepresentation - .getMediaType()); + super((jsonRepresentation == null) ? null : jsonRepresentation.getMediaType()); this.jsonRepresentation = jsonRepresentation; } /** * Constructor from a JSON string. - * - * @param jsonString - * The JSON string. + * + * @param jsonString The JSON string. */ public JsonRepresentation(String jsonString) { super(MediaType.APPLICATION_JSON); @@ -137,7 +126,7 @@ public JsonRepresentation(String jsonString) { /** * Returns the number of spaces to use for indentation. - * + * * @return The number of spaces to use for indentation. */ public int getIndentingSize() { @@ -145,9 +134,8 @@ public int getIndentingSize() { } /** - * Gets the wrapped JSON array or converts the wrapped representation if - * needed. - * + * Gets the wrapped JSON array or converts the wrapped representation if needed. + * * @return The converted JSON array. * @throws JSONException */ @@ -160,9 +148,8 @@ public JSONArray getJsonArray() throws JSONException { } /** - * Gets the wrapped JSON object or converts the wrapped representation if - * needed. - * + * Gets the wrapped JSON object or converts the wrapped representation if needed. + * * @return The converted JSON object. * @throws JSONException */ @@ -176,7 +163,7 @@ public JSONObject getJsonObject() throws JSONException { /** * Returns the JSON text for the wrapped JSON object or representation. - * + * * @return The JSON text. * @throws JSONException */ @@ -184,27 +171,23 @@ private String getJsonText() throws JSONException { String result = null; if (this.jsonValue != null) { - if (this.jsonValue instanceof JSONArray) { - JSONArray jsonArray = (JSONArray) this.jsonValue; + if (this.jsonValue instanceof final JSONArray jsonArray) { if (isIndenting()) { result = jsonArray.toString(getIndentingSize()); } else { result = jsonArray.toString(); } - } else if (this.jsonValue instanceof JSONObject) { - JSONObject jsonObject = (JSONObject) this.jsonValue; + } else if (this.jsonValue instanceof final JSONObject jsonObject) { if (isIndenting()) { result = jsonObject.toString(getIndentingSize()); } else { result = jsonObject.toString(); } - } else if (this.jsonValue instanceof JSONStringer) { - JSONStringer jsonStringer = (JSONStringer) this.jsonValue; + } else if (this.jsonValue instanceof final JSONStringer jsonStringer) { result = jsonStringer.toString(); - } else if (this.jsonValue instanceof JSONTokener) { - JSONTokener jsonTokener = (JSONTokener) this.jsonValue; + } else if (this.jsonValue instanceof final JSONTokener jsonTokener) { result = jsonTokener.toString(); } } else if (this.jsonRepresentation != null) { @@ -219,9 +202,8 @@ private String getJsonText() throws JSONException { } /** - * Gets the wrapped JSON tokener or converts the wrapped representation if - * needed. - * + * Gets the wrapped JSON tokener or converts the wrapped representation if needed. + * * @return The converted JSON tokener. * @throws JSONException */ @@ -242,7 +224,6 @@ public long getSize() { } /** - * * @param jsonObject */ private void init(Object jsonObject) { @@ -254,7 +235,7 @@ private void init(Object jsonObject) { /** * Indicates if JSON objects and arrays should be indented. - * + * * @return True if JSON objects and arrays should be indented. */ public boolean isIndenting() { @@ -263,9 +244,8 @@ public boolean isIndenting() { /** * Indicates if JSON objects and arrays should be indented. - * - * @param indenting - * True if JSON objects and arrays should be indented. + * + * @param indenting True if JSON objects and arrays should be indented. */ public void setIndenting(boolean indenting) { this.indenting = indenting; @@ -273,9 +253,8 @@ public void setIndenting(boolean indenting) { /** * Sets the number of spaces to use for indentation. - * - * @param indentFactor - * The number of spaces to use for indentation. + * + * @param indentFactor The number of spaces to use for indentation. */ public void setIndentingSize(int indentFactor) { this.indentingSize = indentFactor; diff --git a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonpFilter.java b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonpFilter.java index 81b209678a..b6f428a7de 100644 --- a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonpFilter.java +++ b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonpFilter.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.json; import org.restlet.Context; @@ -18,49 +17,45 @@ import org.restlet.routing.Filter; /** - * Filter that converts response entity of the JSON media type into a JSONP - * callback document. Make sure that you properly pass a "callback" query - * parameter in the URI query string with the name of your JavaScrip callback - * method. - * - * See {@link JsonpRepresentation} for the actual wrapper representation used - * internally. - * + * Filter that converts the response entity of the JSON media type into a JSONP callback document. + * Make sure that you properly pass a "callback" query parameter in the URI query string with the + * name of your JavaScript callback method. + * + *

See {@link JsonpRepresentation} for the actual wrapper representation used internally. + * * @author Mark Kharitonov */ public class JsonpFilter extends Filter { /** * Constructor. - * - * @param context - * The context. + * + * @param context The context. */ public JsonpFilter(Context context) { super(context); } /** - * Assumes that there is a "callback" query parameter available in the URI - * query string, containing the name of the JavaScript callback method. + * Assumes that there is a "callback" query parameter available in the URI query string, + * containing the name of the JavaScript callback method. */ @Override public void afterHandle(Request request, Response response) { // Check the presence of the callback parameter - String callback = request.getResourceRef().getQueryAsForm() - .getFirstValue("callback"); + String callback = request.getResourceRef().getQueryAsForm().getFirstValue("callback"); if (callback != null) { Representation entity = response.getEntity(); if (entity != null - && ("text".equals(entity.getMediaType().getMainType()) || MediaType.APPLICATION_JSON - .equals(entity.getMediaType()))) { - response.setEntity(new JsonpRepresentation(callback, response - .getStatus(), response.getEntity())); + && ("text".equals(entity.getMediaType().getMainType()) + || MediaType.APPLICATION_JSON.equals(entity.getMediaType()))) { + response.setEntity( + new JsonpRepresentation( + callback, response.getStatus(), response.getEntity())); response.setStatus(Status.SUCCESS_OK); } } } - -} \ No newline at end of file +} diff --git a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonpRepresentation.java b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonpRepresentation.java index 9e78a938bb..8b50db8aae 100644 --- a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonpRepresentation.java +++ b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/JsonpRepresentation.java @@ -1,16 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.json; import java.io.IOException; - import org.restlet.data.MediaType; import org.restlet.data.Status; import org.restlet.engine.io.IoUtils; @@ -18,10 +16,9 @@ import org.restlet.representation.WriterRepresentation; /** - * Wrappers that adds a JSONP header and footer to JSON representations. The - * goal is to make them accessible to web browser without restriction from - * single origin policies. - * + * Wrappers that adds a JSONP header and footer to JSON representations. The goal is to make them + * accessible to web browser without restriction from single origin policies. + * * @author Mark Kharitonov * @author Jerome Louvel */ @@ -37,15 +34,13 @@ public class JsonpRepresentation extends WriterRepresentation { /** * Constructor. - * - * @param callback - * The name of the JavaScript callback method. - * @param status - * The actual status code. + * + * @param callback The name of the JavaScript callback method. + * @param status The actual status code. * @param wrappedRepresentation */ - public JsonpRepresentation(String callback, Status status, - Representation wrappedRepresentation) { + public JsonpRepresentation( + String callback, Status status, Representation wrappedRepresentation) { super(MediaType.APPLICATION_JAVASCRIPT); this.callback = callback; this.status = status; @@ -54,7 +49,7 @@ public JsonpRepresentation(String callback, Status status, /** * Returns the name of the JavaScript callback method. - * + * * @return The name of the JavaScript callback method. */ public String getCallback() { @@ -65,9 +60,7 @@ public String getCallback() { public long getSize() { long result = wrappedRepresentation.getSize(); - if (result > 0 - && MediaType.APPLICATION_JSON.equals(wrappedRepresentation - .getMediaType())) { + if (result > 0 && MediaType.APPLICATION_JSON.equals(wrappedRepresentation.getMediaType())) { try { java.io.StringWriter sw = new java.io.StringWriter(); write(sw); @@ -83,7 +76,7 @@ public long getSize() { /** * Returns the actual status code. - * + * * @return The actual status code. */ public Status getStatus() { @@ -97,8 +90,7 @@ public void write(java.io.Writer writer) throws IOException { writer.write(Integer.toString(getStatus().getCode())); writer.write(",\"body\":"); - if (MediaType.APPLICATION_JSON.equals(wrappedRepresentation - .getMediaType())) { + if (MediaType.APPLICATION_JSON.equals(wrappedRepresentation.getMediaType())) { IoUtils.copy(wrappedRepresentation.getReader(), writer); } else { writer.write("\""); @@ -114,5 +106,4 @@ public void write(java.io.Writer writer) throws IOException { writer.write("});"); } - } diff --git a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/package-info.java b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/package-info.java new file mode 100644 index 0000000000..39795d857e --- /dev/null +++ b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/package-info.java @@ -0,0 +1,13 @@ +/** + * Support for JSON representations. JSON stands for JavaScript Object Notation and is a lightweight + * data-interchange format. This extension is based on the JSON library from Java provided by the + * official JSON.org website. This library is useful in simple cases but not tuned for performance + * or extensive serialization support of POJOs. If you need such features, you can also consider the + * XStream extension which supports both XML and JSON serialization. + * + * @since Restlet 1.0 + * @see JSON project + * @see User Guide + * - JSON extension + */ +package org.restlet.ext.json; diff --git a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/package.html b/org.restlet.ext.json/src/main/java/org/restlet/ext/json/package.html deleted file mode 100644 index 969c61b085..0000000000 --- a/org.restlet.ext.json/src/main/java/org/restlet/ext/json/package.html +++ /dev/null @@ -1,14 +0,0 @@ - - -Support for JSON representations. JSON stands for JavaScript Object Notation and is a lightweight -data-interchange format. This extension is based on the JSON library from Java providing -by the official JSON.org Web site. This library is useful in simple cases, but not tuned -for performance or extensive serialization support of POJOs. If you need such features, -you can also consider the XStream extension which supports both XML and JSON serialization. -

- -@since Restlet 1.0 -@see JSON project -@see User Guide - JSON extension - - \ No newline at end of file diff --git a/org.restlet.ext.json/src/test/java/org/restlet/ext/json/JsonpFilterTestCase.java b/org.restlet.ext.json/src/test/java/org/restlet/ext/json/JsonpFilterTestCase.java index 3b7c19f718..44435ac59b 100644 --- a/org.restlet.ext.json/src/test/java/org/restlet/ext/json/JsonpFilterTestCase.java +++ b/org.restlet.ext.json/src/test/java/org/restlet/ext/json/JsonpFilterTestCase.java @@ -1,14 +1,17 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.json; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.restlet.data.Status.SUCCESS_OK; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.restlet.Request; @@ -19,19 +22,15 @@ import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.restlet.data.Status.SUCCESS_OK; - /** * Test case for the {@link JsonpFilter} class. * * @author Cyril Lakech */ -public class JsonpFilterTestCase { +class JsonpFilterTestCase { @Test - public void testAfterHandle() { + void testAfterHandle() { JsonpFilter filter = new JsonpFilter(null); @@ -46,8 +45,8 @@ public void testAfterHandle() { filter.afterHandle(request, response); Representation actual = response.getEntity(); - Representation expected = new JsonpRepresentation(callback, SUCCESS_OK, - new JsonRepresentation(jsonString)); + Representation expected = + new JsonpRepresentation(callback, SUCCESS_OK, new JsonRepresentation(jsonString)); assertInstanceOf(JsonpRepresentation.class, actual); assertEquals(expected, actual); @@ -55,7 +54,7 @@ public void testAfterHandle() { } @Test - public void testAfterHandleText() { + void testAfterHandleText() { JsonpFilter filter = new JsonpFilter(null); @@ -70,8 +69,11 @@ public void testAfterHandleText() { filter.afterHandle(request, response); Representation actual = response.getEntity(); - Representation expected = new JsonpRepresentation(callback, SUCCESS_OK, - new StringRepresentation(jsonString, MediaType.TEXT_HTML)); + Representation expected = + new JsonpRepresentation( + callback, + SUCCESS_OK, + new StringRepresentation(jsonString, MediaType.TEXT_HTML)); assertInstanceOf(JsonpRepresentation.class, actual); assertEquals(expected, actual); @@ -79,7 +81,7 @@ public void testAfterHandleText() { } @Test - public void testAfterHandle_without_callback_should_return_entity_unchanged() { + void testAfterHandle_without_callback_should_return_entity_unchanged() { JsonpFilter filter = new JsonpFilter(null); @@ -98,8 +100,7 @@ public void testAfterHandle_without_callback_should_return_entity_unchanged() { } @Test - public void testAfterHandle_with_other_mediatype_should_return_entity_unchanged() - throws Exception { + void testAfterHandle_with_other_mediatype_should_return_entity_unchanged() { JsonpFilter filter = new JsonpFilter(null); @@ -108,8 +109,8 @@ public void testAfterHandle_with_other_mediatype_should_return_entity_unchanged( ref.addQueryParameter(callback, "test"); Request request = new Request(Method.GET, ref); Response response = new Response(request); - final StringRepresentation expected = new StringRepresentation("", - MediaType.APPLICATION_XML); + final StringRepresentation expected = + new StringRepresentation("", MediaType.APPLICATION_XML); response.setEntity(expected); filter.afterHandle(request, response); diff --git a/org.restlet.ext.json/src/test/java/org/restlet/ext/json/JsonpRepresentationTestCase.java b/org.restlet.ext.json/src/test/java/org/restlet/ext/json/JsonpRepresentationTestCase.java index b2c966baa7..6aa0876138 100644 --- a/org.restlet.ext.json/src/test/java/org/restlet/ext/json/JsonpRepresentationTestCase.java +++ b/org.restlet.ext.json/src/test/java/org/restlet/ext/json/JsonpRepresentationTestCase.java @@ -1,30 +1,28 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.json; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.restlet.data.Status.SUCCESS_OK; + +import java.io.ByteArrayOutputStream; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; -import java.io.ByteArrayOutputStream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.restlet.data.Status.SUCCESS_OK; - /** * Test case for the {@link JsonpRepresentation} class. - * + * * @author Cyril Lakech */ -public class JsonpRepresentationTestCase { +class JsonpRepresentationTestCase { public static final String CALLBACK = "callback"; @@ -33,24 +31,28 @@ public class JsonpRepresentationTestCase { public static final String JSONP_STATUS_BODY = "({\"status\":,\"body\":});"; @Test - public void testGetSizeJson() { - JsonpRepresentation jsonpRepresentation = new JsonpRepresentation( - CALLBACK, SUCCESS_OK, new JsonRepresentation(JSON_SAMPLE)); + void testGetSizeJson() { + JsonpRepresentation jsonpRepresentation = + new JsonpRepresentation(CALLBACK, SUCCESS_OK, new JsonRepresentation(JSON_SAMPLE)); long actual = jsonpRepresentation.getSize(); - long expected = JSON_SAMPLE.length() - + Integer.toString(SUCCESS_OK.getCode()).length() - + CALLBACK.length() + JSONP_STATUS_BODY.length(); + long expected = + JSON_SAMPLE.length() + + Integer.toString(SUCCESS_OK.getCode()).length() + + CALLBACK.length() + + JSONP_STATUS_BODY.length(); assertEquals(expected, actual); } @Test - public void testGetSize_with_text_is_UNKNOWN_SIZE() { - JsonpRepresentation jsonpRepresentation = new JsonpRepresentation( - CALLBACK, SUCCESS_OK, new StringRepresentation(JSON_SAMPLE, - MediaType.TEXT_HTML)); + void testGetSize_with_text_is_UNKNOWN_SIZE() { + JsonpRepresentation jsonpRepresentation = + new JsonpRepresentation( + CALLBACK, + SUCCESS_OK, + new StringRepresentation(JSON_SAMPLE, MediaType.TEXT_HTML)); long actual = jsonpRepresentation.getSize(); @@ -60,9 +62,9 @@ CALLBACK, SUCCESS_OK, new StringRepresentation(JSON_SAMPLE, } @Test - public void testWrite() throws Exception { - JsonpRepresentation jsonpRepresentation = new JsonpRepresentation( - CALLBACK, SUCCESS_OK, new JsonRepresentation(JSON_SAMPLE)); + void testWrite() throws Exception { + JsonpRepresentation jsonpRepresentation = + new JsonpRepresentation(CALLBACK, SUCCESS_OK, new JsonRepresentation(JSON_SAMPLE)); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -76,11 +78,13 @@ public void testWrite() throws Exception { // with a text representation, apostrophes are escaped and the text is embedded // between 2 apostrophes @Test - public void testWrite_with_text_then_apostrophe_are_escaped() - throws Exception { - JsonpRepresentation jsonpRepresentation = new JsonpRepresentation( - CALLBACK, SUCCESS_OK, new StringRepresentation( - "whatever\"with\"apostrophe", MediaType.TEXT_HTML)); + void testWrite_with_text_then_apostrophe_are_escaped() throws Exception { + JsonpRepresentation jsonpRepresentation = + new JsonpRepresentation( + CALLBACK, + SUCCESS_OK, + new StringRepresentation( + "whatever\"with\"apostrophe", MediaType.TEXT_HTML)); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -90,5 +94,4 @@ CALLBACK, SUCCESS_OK, new StringRepresentation( assertEquals(expected, out.toString()); } - } diff --git a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/OpenApiApplication.java b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/OpenApiApplication.java index 5e1713cfdd..90c2a58553 100644 --- a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/OpenApiApplication.java +++ b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/OpenApiApplication.java @@ -1,12 +1,11 @@ /** * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open source license available at - * http://www.opensource.org/licenses/apache-2.0 - * + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi; import org.restlet.Application; @@ -16,14 +15,12 @@ public class OpenApiApplication extends Application { /** - * Default path for the OpenAPI specification. Can be overridden by overriding the - * {@link #getOpenApiSpecificationPath()} method. + * Default path for the OpenAPI specification. Can be overridden by overriding the {@link + * #getOpenApiSpecificationPath()} method. */ static final String OPENAPI_SPECIFICATION_DEFAULT_PATH = "/openapi"; - /** - * Indicates if this application has already been documented or not. - */ + /** Indicates if this application has already been documented or not. */ private volatile boolean documented; @Override @@ -39,7 +36,6 @@ public Restlet getInboundRoot() { attachOpenApiSpecificationRestlet(rootRouter); documented = true; } - } } } @@ -47,9 +43,7 @@ public Restlet getInboundRoot() { return inboundRoot; } - /** - * Path where the OpenAPI specification will be available. By default, it is "/openapi". - */ + /** Path where the OpenAPI specification will be available. By default, it is "/openapi". */ protected String getOpenApiSpecificationPath() { return OPENAPI_SPECIFICATION_DEFAULT_PATH; } @@ -82,9 +76,7 @@ private void attachOpenApiSpecificationRestlet(Router router) { * * @return The {@link Restlet} able to generate the Swagger specification formats. */ - OpenApiSpecificationRestlet getOpenApiSpecificationRestlet( - Router router - ) { + OpenApiSpecificationRestlet getOpenApiSpecificationRestlet(Router router) { return new OpenApiSpecificationRestlet(router); } } diff --git a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/OpenApiSpecificationRestlet.java b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/OpenApiSpecificationRestlet.java index da171b7c86..bf3039f0c7 100644 --- a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/OpenApiSpecificationRestlet.java +++ b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/OpenApiSpecificationRestlet.java @@ -1,12 +1,11 @@ /** * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open source license available at - * http://www.opensource.org/licenses/apache-2.0 - * + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi; import com.fasterxml.jackson.core.JsonProcessingException; @@ -14,6 +13,7 @@ import io.swagger.v3.oas.integration.OpenApiConfigurationException; import io.swagger.v3.oas.integration.SwaggerConfiguration; import io.swagger.v3.oas.integration.api.OpenAPIConfiguration; +import java.util.List; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; @@ -28,16 +28,11 @@ import org.restlet.representation.Variant; import org.restlet.routing.Router; -import java.util.List; - public class OpenApiSpecificationRestlet extends Restlet { - private static final VariantInfo VARIANT_JSON = new VariantInfo( - MediaType.APPLICATION_JSON - ); + private static final VariantInfo VARIANT_JSON = new VariantInfo(MediaType.APPLICATION_JSON); - private static final VariantInfo VARIANT_APPLICATION_YAML = new VariantInfo( - MediaType.APPLICATION_YAML - ); + private static final VariantInfo VARIANT_APPLICATION_YAML = + new VariantInfo(MediaType.APPLICATION_YAML); private final Router router; @@ -60,35 +55,35 @@ public void handle(Request request, Response response) { private Representation getOpenApiDefinitionAsRepresentation(Request request) { List allowedVariants = List.of(VARIANT_APPLICATION_YAML, VARIANT_JSON); - Variant preferredVariant = getApplication() - .getConnegService() - .getPreferredVariant( - allowedVariants, - request, - getApplication().getMetadataService() - ); + Variant preferredVariant = + getApplication() + .getConnegService() + .getPreferredVariant( + allowedVariants, request, getApplication().getMetadataService()); - OpenAPIConfiguration oasConfig = new SwaggerConfiguration() - .prettyPrint(true); + OpenAPIConfiguration oasConfig = new SwaggerConfiguration().prettyPrint(true); try { - var context = new RestletOpenApiContextBuilder() - .router(router) - .openApiConfiguration(oasConfig) - .buildContext(true); + var context = + new RestletOpenApiContextBuilder() + .router(router) + .openApiConfiguration(oasConfig) + .buildContext(true); var openApiRead = context.read(); if (VARIANT_JSON.isCompatible(preferredVariant)) { - var openApiAsJson = context.getOutputJsonMapper() - .writer(new DefaultPrettyPrinter()) - .writeValueAsString(openApiRead); + var openApiAsJson = + context.getOutputJsonMapper() + .writer(new DefaultPrettyPrinter()) + .writeValueAsString(openApiRead); return new StringRepresentation(openApiAsJson, MediaType.APPLICATION_JSON); } else { - var openApiAsYaml = context.getOutputYamlMapper() - .writer(new DefaultPrettyPrinter()) - .writeValueAsString(openApiRead); + var openApiAsYaml = + context.getOutputYamlMapper() + .writer(new DefaultPrettyPrinter()) + .writeValueAsString(openApiRead); return new StringRepresentation(openApiAsYaml, MediaType.APPLICATION_YAML); } diff --git a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/OpenApiAnnotationProcessor.java b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/OpenApiAnnotationProcessor.java index 21070c20a7..ff5bc45f67 100644 --- a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/OpenApiAnnotationProcessor.java +++ b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/OpenApiAnnotationProcessor.java @@ -1,12 +1,11 @@ -/* - * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * - * Restlet is a registered trademark of QlikTech International AB. +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi.internal; import static org.restlet.engine.util.StringUtils.isNullOrEmpty; @@ -18,39 +17,36 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.parameters.Parameter; - import java.util.Optional; public class OpenApiAnnotationProcessor { - private OpenApiAnnotationProcessor () {} // utility class + private OpenApiAnnotationProcessor() {} // utility class public static void documentOpenApiDefinition( - OpenAPI openAPIDefinition, - OpenAPIDefinition openAPIDefinitionAnnotation - ) { + OpenAPI openAPIDefinition, OpenAPIDefinition openAPIDefinitionAnnotation) { if (openAPIDefinitionAnnotation.info() != null) { AnnotationsUtils.getInfo(openAPIDefinitionAnnotation.info()) - .ifPresent(openAPIDefinition::setInfo); + .ifPresent(openAPIDefinition::setInfo); } } public static void documentOperation( - Operation operation, - io.swagger.v3.oas.annotations.Operation operationAnnotation - ) { + Operation operation, io.swagger.v3.oas.annotations.Operation operationAnnotation) { if (operationAnnotation.summary() != null && !operationAnnotation.summary().isEmpty()) { operation.setSummary(operationAnnotation.summary()); } - if (operationAnnotation.description() != null && !operationAnnotation.description().isEmpty()) { + if (operationAnnotation.description() != null + && !operationAnnotation.description().isEmpty()) { operation.setDescription(operationAnnotation.description()); } if (operationAnnotation.parameters() != null) { - for (io.swagger.v3.oas.annotations.Parameter parameterAnnotation : operationAnnotation.parameters()) { + for (io.swagger.v3.oas.annotations.Parameter parameterAnnotation : + operationAnnotation.parameters()) { resolveParameterFromAnnotation(parameterAnnotation) - .ifPresent(operation::addParametersItem); + .ifPresent(operation::addParametersItem); } } @@ -62,50 +58,45 @@ public static void documentOperation( } public static void documentOperationResponse( - Operation operation, - ApiResponse apiResponseAnnotation - ) { + Operation operation, ApiResponse apiResponseAnnotation) { var defaultDescription = apiResponseAnnotation.responseCode() + " response"; io.swagger.v3.oas.models.responses.ApiResponse apiResponse = - new io.swagger.v3.oas.models.responses.ApiResponse() - .description(isNullOrEmpty(apiResponseAnnotation.description()) - ? defaultDescription - : apiResponseAnnotation.description() - ); + new io.swagger.v3.oas.models.responses.ApiResponse() + .description( + isNullOrEmpty(apiResponseAnnotation.description()) + ? defaultDescription + : apiResponseAnnotation.description()); if (!isContentEmpty(apiResponseAnnotation)) { AnnotationsUtils.getContent( - apiResponseAnnotation.content(), - null, - null, - null, - null, - null - ).ifPresent(apiResponse::content); + apiResponseAnnotation.content(), null, null, null, null, null) + .ifPresent(apiResponse::content); } AnnotationsUtils.getHeaders(apiResponseAnnotation.headers(), null) - .ifPresent(apiResponse::headers); + .ifPresent(apiResponse::headers); Operations.addApiResponse(operation, apiResponseAnnotation.responseCode(), apiResponse); } - private static Optional resolveParameterFromAnnotation(io.swagger.v3.oas.annotations.Parameter parameterAnnotation) { + private static Optional resolveParameterFromAnnotation( + io.swagger.v3.oas.annotations.Parameter parameterAnnotation) { return switch (parameterAnnotation.in()) { case DEFAULT, PATH -> Optional.empty(); case COOKIE, HEADER, QUERY -> { - Parameter parameter = new Parameter() - .name(parameterAnnotation.name()) - .description(isNullOrEmpty(parameterAnnotation.description()) - ? null - : parameterAnnotation.description() - ) - .in(parameterAnnotation.in().name().toLowerCase()) - .required(parameterAnnotation.required()); + Parameter parameter = + new Parameter() + .name(parameterAnnotation.name()) + .description( + isNullOrEmpty(parameterAnnotation.description()) + ? null + : parameterAnnotation.description()) + .in(parameterAnnotation.in().name().toLowerCase()) + .required(parameterAnnotation.required()); AnnotationsUtils.getSchemaFromAnnotation(parameterAnnotation.schema(), null) - .ifPresent(parameter::schema); + .ifPresent(parameter::schema); yield Optional.of(parameter); } @@ -113,14 +104,15 @@ private static Optional resolveParameterFromAnnotation(io.swagger.v3. } private static boolean isContentEmpty(ApiResponse apiResponseAnnotation) { - if (apiResponseAnnotation.content() == null || apiResponseAnnotation.content().length == 0) { + if (apiResponseAnnotation.content() == null + || apiResponseAnnotation.content().length == 0) { return true; } Content content = apiResponseAnnotation.content()[0]; - return content.mediaType().isEmpty() && - content.schema().implementation() == Void.class && - content.schema().ref().isEmpty(); + return content.mediaType().isEmpty() + && content.schema().implementation() == Void.class + && content.schema().ref().isEmpty(); } } diff --git a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/OpenApiApplicationException.java b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/OpenApiApplicationException.java index e73243409e..99cb35c65c 100644 --- a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/OpenApiApplicationException.java +++ b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/OpenApiApplicationException.java @@ -1,12 +1,11 @@ /** * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open source license available at - * http://www.opensource.org/licenses/apache-2.0 - * + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi.internal; import org.restlet.resource.Status; diff --git a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/Operations.java b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/Operations.java index 2f3fcadc44..15ab1ddd72 100644 --- a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/Operations.java +++ b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/Operations.java @@ -1,12 +1,11 @@ -/* - * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * - * Restlet is a registered trademark of QlikTech International AB. +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi.internal; import io.swagger.v3.oas.models.Operation; @@ -14,14 +13,12 @@ import io.swagger.v3.oas.models.responses.ApiResponses; public class Operations { - private Operations() { - } + private Operations() {} - public static void addApiResponse(Operation operation, String apiResponseName, ApiResponse apiResponse) { + public static void addApiResponse( + Operation operation, String apiResponseName, ApiResponse apiResponse) { if (operation.getResponses() == null) { - operation.responses( - new ApiResponses().addApiResponse(apiResponseName, apiResponse) - ); + operation.responses(new ApiResponses().addApiResponse(apiResponseName, apiResponse)); } else { operation.getResponses().addApiResponse(apiResponseName, apiResponse); } diff --git a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/PathItems.java b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/PathItems.java index cfe7048c47..2288afe302 100644 --- a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/PathItems.java +++ b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/PathItems.java @@ -1,12 +1,11 @@ -/* - * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * - * Restlet is a registered trademark of QlikTech International AB. +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi.internal; import io.swagger.v3.oas.models.Operation; @@ -14,8 +13,7 @@ import org.restlet.data.Method; public class PathItems { - private PathItems() { - } + private PathItems() {} public static void setOperation(PathItem pathItem, Method restletMethod, Operation operation) { if (restletMethod.equals(Method.POST)) { @@ -31,7 +29,8 @@ public static void setOperation(PathItem pathItem, Method restletMethod, Operati } else if (restletMethod.equals(Method.OPTIONS)) { pathItem.setOptions(operation); } else { - throw new IllegalArgumentException("Unsupported Restlet Method: " + restletMethod.getName()); + throw new IllegalArgumentException( + "Unsupported Restlet Method: " + restletMethod.getName()); } } } diff --git a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiContext.java b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiContext.java index 0e58dae99f..8391f48230 100644 --- a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiContext.java +++ b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiContext.java @@ -1,12 +1,11 @@ -/* - * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * - * Restlet is a registered trademark of QlikTech International AB. +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi.internal; import io.swagger.v3.oas.integration.GenericOpenApiContext; @@ -16,7 +15,8 @@ import org.apache.commons.lang3.StringUtils; import org.restlet.routing.Router; -public class RestletOpenApiContext extends GenericOpenApiContext implements OpenApiContext { +public class RestletOpenApiContext extends GenericOpenApiContext + implements OpenApiContext { private final Router router; public RestletOpenApiContext(Router router) { @@ -24,11 +24,13 @@ public RestletOpenApiContext(Router router) { } @Override - protected OpenApiReader buildReader(OpenAPIConfiguration openApiConfiguration) throws Exception { + protected OpenApiReader buildReader(OpenAPIConfiguration openApiConfiguration) + throws Exception { OpenApiReader reader; if (StringUtils.isNotBlank(openApiConfiguration.getReaderClass())) { - Class cls = getClass().getClassLoader().loadClass(openApiConfiguration.getReaderClass()); + Class cls = + getClass().getClassLoader().loadClass(openApiConfiguration.getReaderClass()); reader = (OpenApiReader) cls.getDeclaredConstructor().newInstance(); } else { reader = new RestletOpenApiReader(); diff --git a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiContextBuilder.java b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiContextBuilder.java index e48379bad6..1fbe5ca200 100644 --- a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiContextBuilder.java +++ b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiContextBuilder.java @@ -1,12 +1,11 @@ -/* - * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * - * Restlet is a registered trademark of QlikTech International AB. +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi.internal; import io.swagger.v3.oas.integration.GenericOpenApiContextBuilder; @@ -16,7 +15,8 @@ import org.apache.commons.lang3.StringUtils; import org.restlet.routing.Router; -public class RestletOpenApiContextBuilder extends GenericOpenApiContextBuilder { +public class RestletOpenApiContextBuilder + extends GenericOpenApiContextBuilder { private Router router; public RestletOpenApiContextBuilder router(Router router) { @@ -33,13 +33,15 @@ public OpenApiContext buildContext(boolean init) throws OpenApiConfigurationExce OpenApiContext ctx = OpenApiContextLocator.getInstance().getOpenApiContext(ctxId); if (ctx == null) { - OpenApiContext rootCtx = OpenApiContextLocator.getInstance() - .getOpenApiContext(OpenApiContext.OPENAPI_CONTEXT_ID_DEFAULT); - - ctx = new RestletOpenApiContext(router) - .id(ctxId) - .openApiConfiguration(openApiConfiguration) - .parent(rootCtx); + OpenApiContext rootCtx = + OpenApiContextLocator.getInstance() + .getOpenApiContext(OpenApiContext.OPENAPI_CONTEXT_ID_DEFAULT); + + ctx = + new RestletOpenApiContext(router) + .id(ctxId) + .openApiConfiguration(openApiConfiguration) + .parent(rootCtx); if (init) { ctx.init(); diff --git a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiReader.java b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiReader.java index c25b5d3619..5b0fdc875e 100644 --- a/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiReader.java +++ b/org.restlet.ext.openapi/src/main/java/org/restlet/ext/openapi/internal/RestletOpenApiReader.java @@ -1,12 +1,11 @@ -/* - * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * - * Restlet is a registered trademark of QlikTech International AB. +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi.internal; import io.swagger.v3.core.converter.AnnotatedType; @@ -27,27 +26,24 @@ import io.swagger.v3.oas.models.media.MediaType; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.responses.ApiResponse; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import org.restlet.Context; import org.restlet.engine.resource.AnnotationInfo; import org.restlet.engine.resource.AnnotationUtils; import org.restlet.engine.resource.MethodAnnotationInfo; import org.restlet.representation.Variant; import org.restlet.resource.Finder; -import org.restlet.resource.ResourceException; import org.restlet.resource.ServerResource; import org.restlet.routing.Route; import org.restlet.routing.Router; import org.restlet.routing.TemplateRoute; import org.restlet.service.MetadataService; -import java.io.IOException; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - public class RestletOpenApiReader implements OpenApiReader { private final MetadataService metadataService = new MetadataService(); @@ -74,24 +70,26 @@ public void setConfiguration(OpenAPIConfiguration openApiConfiguration) { @Override public OpenAPI read(Set> classes, Map resources) { if (config == null) { - throw new IllegalStateException("configuration must be set before processing OpenAPI definition"); + throw new IllegalStateException( + "configuration must be set before processing OpenAPI definition"); } if (router == null) { - throw new IllegalStateException("router must be set before processing OpenAPI definition"); + throw new IllegalStateException( + "router must be set before processing OpenAPI definition"); } return processRouter(router); } private OpenAPI processRouter(Router router) { - OpenAPIDefinition openAPIDefinitionAnnotation = ReflectionUtils.getAnnotation( - router.getApplication().getClass(), - OpenAPIDefinition.class - ); + OpenAPIDefinition openAPIDefinitionAnnotation = + ReflectionUtils.getAnnotation( + router.getApplication().getClass(), OpenAPIDefinition.class); if (openAPIDefinitionAnnotation != null) { - OpenApiAnnotationProcessor.documentOpenApiDefinition(openApi, openAPIDefinitionAnnotation); + OpenApiAnnotationProcessor.documentOpenApiDefinition( + openApi, openAPIDefinitionAnnotation); } completeOpenApiInfo(router); @@ -131,13 +129,11 @@ private void processRoute(Route route) { } private void processServerResource( - ServerResource serverResource, - String operationPath, - List pathVariableNames - ) { - List annotations = serverResource.isAnnotated() - ? AnnotationUtils.getInstance().getAnnotations(serverResource.getClass()) - : null; + ServerResource serverResource, String operationPath, List pathVariableNames) { + List annotations = + serverResource.isAnnotated() + ? AnnotationUtils.getInstance().getAnnotations(serverResource.getClass()) + : null; if (annotations == null) { return; @@ -151,11 +147,13 @@ private void processServerResource( completePathParameters(operation, pathVariableNames); completeOperation(serverResource, operation, methodAnnotationInfo); - PathItem pathItem = Optional.ofNullable(openApi.getPaths()) - .map(openApiPaths -> openApiPaths.get(operationPath)) - .orElseGet(PathItem::new); + PathItem pathItem = + Optional.ofNullable(openApi.getPaths()) + .map(openApiPaths -> openApiPaths.get(operationPath)) + .orElseGet(PathItem::new); - PathItems.setOperation(pathItem, methodAnnotationInfo.getRestletMethod(), operation); + PathItems.setOperation( + pathItem, methodAnnotationInfo.getRestletMethod(), operation); paths.addPathItem(operationPath, pathItem); if (openApi.getPaths() != null) { @@ -170,17 +168,16 @@ private void processServerResource( private void completeOpenApiInfo(Router router) { var applicationClassName = router.getApplication().getClass().getSimpleName(); - var applicationName = applicationClassName.endsWith("Application") - ? applicationClassName.substring(0, applicationClassName.length() - "Application".length()) - : applicationClassName; + var applicationName = + applicationClassName.endsWith("Application") + ? applicationClassName.substring( + 0, applicationClassName.length() - "Application".length()) + : applicationClassName; var defaultTitle = applicationName + " REST API"; if (openApi.getInfo() == null) { - openApi.setInfo(new Info() - .title(defaultTitle) - .version("1.0.0") - ); + openApi.setInfo(new Info().title(defaultTitle).version("1.0.0")); } else if (openApi.getInfo().getTitle() == null) { openApi.getInfo().setTitle(defaultTitle); } else if (openApi.getInfo().getVersion() == null) { @@ -188,59 +185,47 @@ private void completeOpenApiInfo(Router router) { } } - private void completePathParameters( - Operation operation, - List pathVariableNames - ) { + private void completePathParameters(Operation operation, List pathVariableNames) { if (pathVariableNames != null) { for (String pathVariableName : pathVariableNames) { operation.addParametersItem( - new io.swagger.v3.oas.models.parameters.Parameter() - .name(pathVariableName) - .in("path") - .required(true) - .schema(new Schema<>().type("string")) - ); + new io.swagger.v3.oas.models.parameters.Parameter() + .name(pathVariableName) + .in("path") + .required(true) + .schema(new Schema<>().type("string"))); } } } private Operation buildOperationFromRestletMethod( - final MethodAnnotationInfo methodAnnotationInfo - ) { + final MethodAnnotationInfo methodAnnotationInfo) { Operation operation = new Operation(); operation.setOperationId(methodAnnotationInfo.getJavaMethod().getName()); return operation; } private void completeOperation( - final ServerResource serverResource, - final Operation operation, - MethodAnnotationInfo methodAnnotationInfo - ) { - try { - var methodOperationAnnotation = ReflectionUtils.getAnnotation( - methodAnnotationInfo.getJavaMethod(), - io.swagger.v3.oas.annotations.Operation.class - ); - - if (methodOperationAnnotation != null) { - OpenApiAnnotationProcessor.documentOperation(operation, methodOperationAnnotation); - } - - completeOperationInput(serverResource, operation, methodAnnotationInfo); - completeOperationSuccessfulOutput(serverResource, operation, methodAnnotationInfo); - - } catch (IOException e) { - throw new ResourceException(e); + final ServerResource serverResource, + final Operation operation, + MethodAnnotationInfo methodAnnotationInfo) { + var methodOperationAnnotation = + ReflectionUtils.getAnnotation( + methodAnnotationInfo.getJavaMethod(), + io.swagger.v3.oas.annotations.Operation.class); + + if (methodOperationAnnotation != null) { + OpenApiAnnotationProcessor.documentOperation(operation, methodOperationAnnotation); } + + completeOperationInput(serverResource, operation, methodAnnotationInfo); + completeOperationSuccessfulOutput(serverResource, operation, methodAnnotationInfo); } private void completeOperationInput( - ServerResource serverResource, - Operation operation, - MethodAnnotationInfo methodAnnotationInfo - ) throws IOException { + ServerResource serverResource, + Operation operation, + MethodAnnotationInfo methodAnnotationInfo) { Type[] parameterTypes = methodAnnotationInfo.getJavaMethod().getGenericParameterTypes(); if (parameterTypes.length == 0) { return; @@ -248,10 +233,9 @@ private void completeOperationInput( Type firstParameterType = parameterTypes[0]; - List requestVariants = methodAnnotationInfo.getRequestVariants( - metadataService, - serverResource.getConverterService() - ); + List requestVariants = + methodAnnotationInfo.getRequestVariants( + metadataService, serverResource.getConverterService()); if (requestVariants == null || requestVariants.isEmpty()) { return; @@ -260,29 +244,30 @@ private void completeOperationInput( Variant firstVariant = requestVariants.getFirst(); processTypeToContent(firstParameterType, List.of(firstVariant)) - .ifPresent(content -> operation.requestBody( - new io.swagger.v3.oas.models.parameters.RequestBody() - .content(content) - )); + .ifPresent( + content -> + operation.requestBody( + new io.swagger.v3.oas.models.parameters.RequestBody() + .content(content))); } private void completeOperationSuccessfulOutput( - ServerResource serverResource, - Operation operation, - MethodAnnotationInfo methodAnnotationInfo - ) throws IOException { - List responseVariants = methodAnnotationInfo.getResponseVariants( - metadataService, - serverResource.getConverterService() - ); + ServerResource serverResource, + Operation operation, + MethodAnnotationInfo methodAnnotationInfo) { + List responseVariants = + methodAnnotationInfo.getResponseVariants( + metadataService, serverResource.getConverterService()); Type javaMethodReturnType = methodAnnotationInfo.getJavaMethod().getGenericReturnType(); if (responseVariants == null || responseVariants.isEmpty()) { - var hasResponsesDefined = operation.getResponses() != null && !operation.getResponses().isEmpty(); + var hasResponsesDefined = + operation.getResponses() != null && !operation.getResponses().isEmpty(); if (!hasResponsesDefined) { - Operations.addApiResponse(operation, "200", new ApiResponse().description("Success")); + Operations.addApiResponse( + operation, "200", new ApiResponse().description("Success")); } } else { processMethodReturnType(operation, javaMethodReturnType, responseVariants); @@ -290,29 +275,23 @@ private void completeOperationSuccessfulOutput( } private void processMethodReturnType( - Operation operation, - Type returnType, - List responseVariants - ) { + Operation operation, Type returnType, List responseVariants) { Variant firstVariant = responseVariants.getFirst(); processTypeToContent(returnType, List.of(firstVariant)) - .ifPresent(content -> Operations.addApiResponse( - operation, - "200", - new ApiResponse() - .content(content) - .description("Success") - )); + .ifPresent( + content -> + Operations.addApiResponse( + operation, + "200", + new ApiResponse().content(content).description("Success"))); } private Optional processTypeToContent(Type type, List variants) { - ResolvedSchema resolvedSchema = ModelConverters.getInstance(config.toConfiguration()) - .resolveAsResolvedSchema( - new AnnotatedType(type) - .resolveAsRef(true) - .components(components) - ); + ResolvedSchema resolvedSchema = + ModelConverters.getInstance(config.toConfiguration()) + .resolveAsResolvedSchema( + new AnnotatedType(type).resolveAsRef(true).components(components)); if (resolvedSchema.schema == null) { return Optional.empty(); diff --git a/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/LibraryExample.java b/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/LibraryExample.java index 2619cee4ab..c852ba075b 100644 --- a/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/LibraryExample.java +++ b/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/LibraryExample.java @@ -1,12 +1,11 @@ /** * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open source license available at - * http://www.opensource.org/licenses/apache-2.0 - * + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi; import io.swagger.v3.oas.annotations.Operation; @@ -15,6 +14,8 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import java.util.List; +import java.util.Optional; import org.restlet.data.Reference; import org.restlet.data.Status; import org.restlet.resource.Delete; @@ -23,33 +24,25 @@ import org.restlet.resource.ServerResource; import org.restlet.routing.Router; -import java.util.List; -import java.util.Optional; - @SuppressWarnings("unused") public class LibraryExample { - private static final List BOOKS = List.of( - new Book("1", "The Great Gatsby", "F. Scott Fitzgerald"), - new Book("2", "To Kill a Mockingbird", "Harper Lee") - ); + private static final List BOOKS = + List.of( + new Book("1", "The Great Gatsby", "F. Scott Fitzgerald"), + new Book("2", "To Kill a Mockingbird", "Harper Lee")); - public record Book(String id, String title, String author) { - } + public record Book(String id, String title, String author) {} public static class BookResource extends ServerResource { @Delete - @Operation( - summary = "Delete a book by ID" - ) + @Operation(summary = "Delete a book by ID") public void deleteBook() { Optional.ofNullable(getBook()) .ifPresent(BOOKS::remove); // Yes, this is an immutable list :) } @Get - @Operation( - summary = "Get a book by ID" - ) + @Operation(summary = "Get a book by ID") public Book getBook() { String bookId = getAttribute("bookId"); getContext().getLogger().info("Retrieving book with ID: " + bookId); @@ -60,34 +53,27 @@ public Book getBook() { public static class BooksResource extends ServerResource { @Get @Operation( - summary = "Get a list of all books", - parameters = { - @Parameter( - name = "filter", - description = "Filter books", - in = io.swagger.v3.oas.annotations.enums.ParameterIn.QUERY, - schema = @Schema(type = "string") - ) - } - ) + summary = "Get a list of all books", + parameters = { + @Parameter( + name = "filter", + description = "Filter books", + in = io.swagger.v3.oas.annotations.enums.ParameterIn.QUERY, + schema = @Schema(type = "string")) + }) public List getBooks() { return BOOKS; } @Post @Operation( - summary = "Add a new book", - responses = { - @ApiResponse( - responseCode = "201", - headers = @Header( - name = "Location", - schema = @Schema(type = "string") - ), - content = @Content() - ) - } - ) + summary = "Add a new book", + responses = { + @ApiResponse( + responseCode = "201", + headers = @Header(name = "Location", schema = @Schema(type = "string")), + content = @Content()) + }) public void addBook(Book book) { getResponse().setStatus(Status.SUCCESS_CREATED); Reference locationRef = getRequest().getResourceRef().addSegment(book.id); diff --git a/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/OpenApiGenerationTest.java b/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/OpenApiGenerationTest.java index b927cdff3d..155d9a4bda 100644 --- a/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/OpenApiGenerationTest.java +++ b/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/OpenApiGenerationTest.java @@ -1,12 +1,11 @@ /** * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open source license available at - * http://www.opensource.org/licenses/apache-2.0 - * + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -31,10 +30,12 @@ void testLibraryApplicationOpenApi() throws Exception { var application = new LibraryExample.LibraryApplication(); var component = new Component(); - var server = component.getServers().add( - Protocol.HTTP, - 0 // 0 = let the OS find an ephemeral port - ); + var server = + component + .getServers() + .add( + Protocol.HTTP, 0 // 0 = let the OS find an ephemeral port + ); component.getDefaultHost().attach(application); component.start(); @@ -43,9 +44,9 @@ void testLibraryApplicationOpenApi() throws Exception { System.out.println("Server started on: http://localhost:" + actualPort); - ClientResource clientResource = new ClientResource( - "http://localhost:" + actualPort + OPENAPI_SPECIFICATION_DEFAULT_PATH - ); + ClientResource clientResource = + new ClientResource( + "http://localhost:" + actualPort + OPENAPI_SPECIFICATION_DEFAULT_PATH); Representation representation = clientResource.get(); @@ -54,9 +55,9 @@ void testLibraryApplicationOpenApi() throws Exception { } String actualYamlResponse = parseAndFormatYaml(representation.getText()); - String expectedYamlResponse = parseAndFormatYaml( - OpenApiSpecifications.readFromClasspath("/library-openapi.yaml") - ); + String expectedYamlResponse = + parseAndFormatYaml( + OpenApiSpecifications.readFromClasspath("/library-openapi.yaml")); assertEquals(expectedYamlResponse, actualYamlResponse); diff --git a/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/OpenApiSpecifications.java b/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/OpenApiSpecifications.java index efed16b141..9bb450fbb8 100644 --- a/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/OpenApiSpecifications.java +++ b/org.restlet.ext.openapi/src/test/java/org/restlet/ext/openapi/OpenApiSpecifications.java @@ -1,17 +1,15 @@ /** * Copyright 2005-2026 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open source license available at - * http://www.opensource.org/licenses/apache-2.0 - * + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.openapi; import io.swagger.v3.parser.OpenAPIV3Parser; import io.swagger.v3.parser.core.models.SwaggerParseResult; - import java.util.List; class OpenApiSpecifications { @@ -20,14 +18,16 @@ class OpenApiSpecifications { static String readFromClasspath(String resourcePath) throws Exception { try (var inputStream = OpenApiSpecifications.class.getResourceAsStream(resourcePath)) { if (inputStream == null) { - throw new IllegalArgumentException("Resource not found in classpath: " + resourcePath); + throw new IllegalArgumentException( + "Resource not found in classpath: " + resourcePath); } return new String(inputStream.readAllBytes()); } } static ValidationResult validate(String yamlSpecification) { - SwaggerParseResult result = new OpenAPIV3Parser().readContents(yamlSpecification, null, null); + SwaggerParseResult result = + new OpenAPIV3Parser().readContents(yamlSpecification, null, null); if (result.getMessages().isEmpty()) { return new ValidationResult.Valid(); @@ -37,10 +37,8 @@ static ValidationResult validate(String yamlSpecification) { } sealed interface ValidationResult { - record Valid() implements ValidationResult { - } + record Valid() implements ValidationResult {} - record Invalid(List errors) implements ValidationResult { - } + record Invalid(List errors) implements ValidationResult {} } } diff --git a/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/Slf4jLogger.java b/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/Slf4jLogger.java index 063803e182..b918cc398a 100644 --- a/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/Slf4jLogger.java +++ b/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/Slf4jLogger.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.slf4j; import java.util.logging.Level; @@ -14,10 +13,9 @@ import java.util.logging.Logger; /** - * JULI logger that efficiently wraps a SLF4J logger. It prevents the creation - * of intermediary {@link LogRecord} objects in favor of direct calls to the - * SLF4J API. - * + * JULI logger that efficiently wraps a SLF4J logger. It prevents the creation of intermediary + * {@link LogRecord} objects in favor of direct calls to the SLF4J API. + * * @author Jerome Louvel */ public class Slf4jLogger extends Logger { @@ -27,9 +25,8 @@ public class Slf4jLogger extends Logger { /** * Constructor. - * - * @param slf4jLogger - * The SLF4J logger to wrap. + * + * @param slf4jLogger The SLF4J logger to wrap. */ public Slf4jLogger(org.slf4j.Logger slf4jLogger) { super(slf4jLogger.getName(), null); @@ -38,22 +35,18 @@ public Slf4jLogger(org.slf4j.Logger slf4jLogger) { /** * Constructor. - * - * @param name - * The logger name. - * @param resourceBundleName - * The optional resource bundle name. + * + * @param name The logger name. + * @param resourceBundleName The optional resource bundle name. */ protected Slf4jLogger(String name, String resourceBundleName) { super(name, resourceBundleName); } /** - * Logs a configuration message. By default, it invokes - * {@link org.slf4j.Logger#debug(String)}. - * - * @param msg - * The message to log. + * Logs a configuration message. By default, it invokes {@link org.slf4j.Logger#debug(String)}. + * + * @param msg The message to log. */ @Override public void config(String msg) { @@ -61,11 +54,9 @@ public void config(String msg) { } /** - * Logs a fine trace. By default, it invokes - * {@link org.slf4j.Logger#debug(String)}. - * - * @param msg - * The message to log. + * Logs a fine trace. By default, it invokes {@link org.slf4j.Logger#debug(String)}. + * + * @param msg The message to log. */ @Override public void fine(String msg) { @@ -73,11 +64,9 @@ public void fine(String msg) { } /** - * Logs a finer trace. By default, it invokes - * {@link org.slf4j.Logger#trace(String)}. - * - * @param msg - * The message to log. + * Logs a finer trace. By default, it invokes {@link org.slf4j.Logger#trace(String)}. + * + * @param msg The message to log. */ @Override public void finer(String msg) { @@ -85,11 +74,9 @@ public void finer(String msg) { } /** - * Logs a finest trace. By default, it invokes - * {@link org.slf4j.Logger#trace(String)}. - * - * @param msg - * The message to log. + * Logs a finest trace. By default, it invokes {@link org.slf4j.Logger#trace(String)}. + * + * @param msg The message to log. */ @Override public void finest(String msg) { @@ -98,7 +85,7 @@ public void finest(String msg) { /** * Returns the wrapped SLF4J logger. - * + * * @return The wrapped SLF4J logger. */ public org.slf4j.Logger getSlf4jLogger() { @@ -106,11 +93,9 @@ public org.slf4j.Logger getSlf4jLogger() { } /** - * Logs an info message. By default, it invokes - * {@link org.slf4j.Logger#info(String)}. - * - * @param msg - * The message to log. + * Logs an info message. By default, it invokes {@link org.slf4j.Logger#info(String)}. + * + * @param msg The message to log. */ @Override public void info(String msg) { @@ -219,11 +204,11 @@ public void log(Level level, String msg, Throwable thrown) { } @Override - public void log(LogRecord record) { - Level level = record.getLevel(); - String msg = record.getMessage(); - Object[] params = record.getParameters(); - Throwable thrown = record.getThrown(); + public void log(LogRecord logRecord) { + Level level = logRecord.getLevel(); + String msg = logRecord.getMessage(); + Object[] params = logRecord.getParameters(); + Throwable thrown = logRecord.getThrown(); if (thrown != null) { log(level, msg, thrown); @@ -236,20 +221,17 @@ public void log(LogRecord record) { /** * Sets the wrapped SLF4J logger. - * - * @param slf4jLogger - * The wrapped SLF4J logger. + * + * @param slf4jLogger The wrapped SLF4J logger. */ public void setSlf4jLogger(org.slf4j.Logger slf4jLogger) { this.slf4jLogger = slf4jLogger; } /** - * Logs a severe message. By default, it invokes - * {@link org.slf4j.Logger#error(String)}. - * - * @param msg - * The message to log. + * Logs a severe message. By default, it invokes {@link org.slf4j.Logger#error(String)}. + * + * @param msg The message to log. */ @Override public void severe(String msg) { @@ -257,15 +239,12 @@ public void severe(String msg) { } /** - * Logs a warning message. By default, it invokes - * {@link org.slf4j.Logger#warn(String)}. - * - * @param msg - * The message to log. + * Logs a warning message. By default, it invokes {@link org.slf4j.Logger#warn(String)}. + * + * @param msg The message to log. */ @Override public void warning(String msg) { getSlf4jLogger().warn(msg); } - } diff --git a/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/Slf4jLoggerFacade.java b/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/Slf4jLoggerFacade.java index e597b2ed23..31f8c61b99 100644 --- a/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/Slf4jLoggerFacade.java +++ b/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/Slf4jLoggerFacade.java @@ -1,34 +1,31 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.slf4j; import java.util.logging.Logger; - import org.restlet.engine.log.LoggerFacade; import org.slf4j.LoggerFactory; /** - * Restlet log facade for the SLF4J {@link LoggerFactory}. In order to use SLF4J - * as the logging facade for Restlet, you need to set the - * "org.restlet.engine.loggerFacadeClass" system property with the + * Restlet log facade for the SLF4J {@link LoggerFactory}. To use SLF4J as the logging facade for + * Restlet, you need to set the "org.restlet.engine.loggerFacadeClass" system property with the * "org.restlet.ext.slf4j.Slf4jLoggerFacade" value. - * + * * @see Slf4jLogger * @author Jerome Louvel */ public class Slf4jLoggerFacade extends LoggerFacade { /** - * Returns an instance of {@link Slf4jLogger}, wrapping the result of - * {@link LoggerFactory#getLogger(String)} where the logger name is "". - * + * Returns an instance of {@link Slf4jLogger}, wrapping the result of {@link + * LoggerFactory#getLogger(String)} where the logger name is "". + * * @return An anonymous logger. */ @Override @@ -37,16 +34,14 @@ public Logger getAnonymousLogger() { } /** - * Returns an instance of {@link Slf4jLogger}, wrapping the result of - * {@link LoggerFactory#getLogger(String)} with the logger name. - * - * @param loggerName - * The logger name. + * Returns an instance of {@link Slf4jLogger}, wrapping the result of {@link + * LoggerFactory#getLogger(String)} with the logger name. + * + * @param loggerName The logger name. * @return An anonymous logger. */ @Override public Logger getLogger(String loggerName) { return new Slf4jLogger(LoggerFactory.getLogger(loggerName)); } - } diff --git a/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/package-info.java b/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/package-info.java new file mode 100644 index 0000000000..573b79ac55 --- /dev/null +++ b/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/package-info.java @@ -0,0 +1,10 @@ +/** + * Integration with SLF4J 1.7. This extension provides a log facade for SLF4J for the Restlet + * engine, allowing bridges to alternate logging mechanisms such as Log4J or LogBack. + * + * @since Restlet 2.0 + * @see SLF4J home + * @see User + * Guide - SLF4J extension + */ +package org.restlet.ext.slf4j; diff --git a/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/package.html b/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/package.html deleted file mode 100644 index 2b05dfe633..0000000000 --- a/org.restlet.ext.slf4j/src/main/java/org/restlet/ext/slf4j/package.html +++ /dev/null @@ -1,11 +0,0 @@ - - -Integration with SLF4J 1.7. This extension provides a log facade for -SLF4J for the Restlet engine, allowing bridges to alternate logging mechanisms -such as Log4J or LogBack. - -@since Restlet 2.0 -@see SLF4J home -@see User Guide - SLF4J extension - - \ No newline at end of file diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringBeanFinder.java b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringBeanFinder.java index ef7d2ffcf9..7e542fb48f 100644 --- a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringBeanFinder.java +++ b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringBeanFinder.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; import org.restlet.Context; @@ -18,21 +17,20 @@ import org.springframework.context.ApplicationContextAware; /** - * An alternative to {@link SpringFinder} which uses Spring's BeanFactory - * mechanism to load a prototype bean by name. - * - * If both a {@link BeanFactory} and a {@link ApplicationContext} are provided, - * the bean will be looked up first in the application context and then in the - * bean factory. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * An alternative to {@link SpringFinder} which uses Spring's BeanFactory mechanism to load a + * prototype bean by name. + * + *

If both a {@link BeanFactory} and a {@link ApplicationContext} are provided, the bean will be + * looked up first in the application context and then in the bean factory. + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Rhett Sutphin */ -public class SpringBeanFinder extends SpringFinder implements BeanFactoryAware, - ApplicationContextAware { +public class SpringBeanFinder extends SpringFinder + implements BeanFactoryAware, ApplicationContextAware { /** The parent application context. */ private volatile ApplicationContext applicationContext; @@ -46,24 +44,17 @@ public class SpringBeanFinder extends SpringFinder implements BeanFactoryAware, /** The associated router. */ private volatile Router router; - /** - * Default constructor. - */ - public SpringBeanFinder() { - } + /** Default constructor. */ + public SpringBeanFinder() {} /** * Constructor. - * - * @param router - * The associated router used to retrieve the context. - * @param beanFactory - * The Spring bean factory. - * @param beanName - * The bean name. + * + * @param router The associated router used to retrieve the context. + * @param beanFactory The Spring bean factory. + * @param beanName The bean name. */ - public SpringBeanFinder(Router router, BeanFactory beanFactory, - String beanName) { + public SpringBeanFinder(Router router, BeanFactory beanFactory, String beanName) { this.router = router; setBeanFactory(beanFactory); setBeanName(beanName); @@ -74,9 +65,10 @@ public ServerResource create() { final Object resource = findBean(); if (!(resource instanceof ServerResource)) { - throw new ClassCastException(getBeanName() - + " does not resolve to an instance of " - + org.restlet.resource.ServerResource.class.getName()); + throw new ClassCastException( + getBeanName() + + " does not resolve to an instance of " + + org.restlet.resource.ServerResource.class.getName()); } return (org.restlet.resource.ServerResource) resource; @@ -89,18 +81,17 @@ private Object findBean() { } else if (getApplicationContext() != null && getApplicationContext().containsBean(getBeanName())) { return getApplicationContext().getBean(getBeanName()); - } else if (getBeanFactory() != null - && getBeanFactory().containsBean(getBeanName())) { + } else if (getBeanFactory() != null && getBeanFactory().containsBean(getBeanName())) { return getBeanFactory().getBean(getBeanName()); } else { - throw new IllegalStateException(String.format( - "No bean named %s present.", getBeanName())); + throw new IllegalStateException( + String.format("No bean named %s present.", getBeanName())); } } /** * Returns the parent application context. - * + * * @return The parent context. */ public ApplicationContext getApplicationContext() { @@ -109,7 +100,7 @@ public ApplicationContext getApplicationContext() { /** * Returns the parent bean factory. - * + * * @return The parent bean factory. */ public BeanFactory getBeanFactory() { @@ -118,7 +109,7 @@ public BeanFactory getBeanFactory() { /** * Returns the bean name. - * + * * @return The bean name. */ public String getBeanName() { @@ -127,13 +118,12 @@ public String getBeanName() { @Override public Context getContext() { - return (getRouter() == null) ? Context.getCurrent() : getRouter() - .getContext(); + return (getRouter() == null) ? Context.getCurrent() : getRouter().getContext(); } /** * Returns the associated router. - * + * * @return The associated router. */ public Router getRouter() { @@ -142,9 +132,8 @@ public Router getRouter() { /** * Sets the parent application context - * - * @param applicationContext - * The parent context. + * + * @param applicationContext The parent context. */ public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; @@ -152,9 +141,8 @@ public void setApplicationContext(ApplicationContext applicationContext) { /** * Sets the parent bean factory. - * - * @param beanFactory - * The parent bean factory. + * + * @param beanFactory The parent bean factory. */ public void setBeanFactory(BeanFactory beanFactory) { this.beanFactory = beanFactory; @@ -162,9 +150,8 @@ public void setBeanFactory(BeanFactory beanFactory) { /** * Sets the bean name. - * - * @param beanName - * The bean name. + * + * @param beanName The bean name. */ public void setBeanName(String beanName) { this.beanName = beanName; @@ -172,12 +159,10 @@ public void setBeanName(String beanName) { /** * Sets the associated router. - * - * @param router - * The associated router. + * + * @param router The associated router. */ public void setRouter(Router router) { this.router = router; } - } diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringBeanRouter.java b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringBeanRouter.java index a25f23c26d..f66f988f91 100644 --- a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringBeanRouter.java +++ b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringBeanRouter.java @@ -1,16 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; import java.util.Map; - import org.restlet.Context; import org.restlet.Restlet; import org.restlet.resource.Finder; @@ -26,48 +24,46 @@ import org.springframework.context.ApplicationContextAware; /** - * Restlet {@link Router} which behaves like Spring's - * {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}. It - * takes every bean of type {@link org.restlet.resource.ServerResource} or - * {@link Restlet} defined in a particular context and examines its aliases - * (generally speaking, its name and id). If one of the aliases begins with a - * forward slash, the resource will be attached to that URI. - *

- * Example: - * + * Restlet {@link Router} which behaves like Spring's {@link + * org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}. It takes every bean of type + * {@link org.restlet.resource.ServerResource} or {@link Restlet} defined in a particular context + * and examines its aliases (generally speaking, its name and id). If one of the aliases begins with + * a forward slash, the resource will be attached to that URI. + * + *

Example: + * *

  * <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" >
  *    <!-- Singleton instance of this class -->
  *    <bean name="router" class="org.restlet.ext.spring.SpringBeanRouter"/>
- * 
+ *
  *    <!-- Prototype beans for the resources -->
  *    <bean name="/studies" id="studiesResource" autowire="byName" scope="prototype" class="edu.northwestern.myapp.StudiesResource" >
  *       <property name="studyDao" ref="studyDao"/>
  *    </bean>
- * 
+ *
  *    <bean name="/studies/{study-identifier}/template" id="templateResource" autowire="byName" scope="prototype" class="edu.northwestern.myapp.TemplateResource" />
- * 
+ *
  *    <!-- Singleton bean for a restlet -->
  *    <bean name="/studies/{study-identifier}/files" id="filesResource" autowire="byName" class="edu.northwestern.myapp.MyDirectory" />
  * </beans>
  * 
- * - * This will route two resources and one restlet: "/studies", - * "/studies/{study-identifier}/template", and - * "/studies/{study-identifier}/files" to the corresponding beans. - * N.b.: Resources must be scoped prototype, since a new instance must be - * created for each request. Restlets may be singletons (this class will only - * ever load one instance for each). - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * + * This will route two resources and one restlet: "/studies", + * "/studies/{study-identifier}/template", and "/studies/{study-identifier}/files" + * to the corresponding beans. N.b.: Resources must be scoped prototype, since a new + * instance must be created for each request. Restlets may be singletons (this class will only ever + * load one instance for each). + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Rhett Sutphin * @author James Maki */ -public class SpringBeanRouter extends Router implements - ApplicationContextAware, BeanFactoryPostProcessor { +public class SpringBeanRouter extends Router + implements ApplicationContextAware, BeanFactoryPostProcessor { /** The Spring application context. */ private volatile ApplicationContext applicationContext; @@ -78,97 +74,76 @@ public class SpringBeanRouter extends Router implements /** If beans should be searched for higher up in the BeanFactory hierarchy. */ private volatile boolean findingInAncestors = true; - /** - * Constructor. - */ + /** Constructor. */ public SpringBeanRouter() { super(); } - /** - * Constructor with a parent context. - */ + /** Constructor with a parent context. */ public SpringBeanRouter(Context context) { super(context); } - /** - * Constructor with a parent Restlet. - */ + /** Constructor with a parent Restlet. */ public SpringBeanRouter(Restlet parent) { super(parent.getContext()); } /** * Attaches all the resources. - * - * @param beanFactory - * The Spring bean factory. + * + * @param beanFactory The Spring bean factory. */ private void attachAllResources(ListableBeanFactory beanFactory) { - for (String beanName : getBeanNamesByType( - org.restlet.resource.ServerResource.class, beanFactory)) { + for (String beanName : + getBeanNamesByType(org.restlet.resource.ServerResource.class, beanFactory)) { String uri = resolveUri(beanName, beanFactory); - if (uri != null) - attachResource(uri, beanName, beanFactory); + if (uri != null) attachResource(uri, beanName, beanFactory); } } /** * Attaches all the Restlet instances. - * - * @param beanFactory - * The Spring bean factory. + * + * @param beanFactory The Spring bean factory. */ private void attachAllRestlets(ListableBeanFactory beanFactory) { for (String beanName : getBeanNamesByType(Restlet.class, beanFactory)) { String uri = resolveUri(beanName, beanFactory); - if (uri != null) - attachRestlet(uri, beanName, beanFactory); + if (uri != null) attachRestlet(uri, beanName, beanFactory); } } /** - * Attaches the named resource bean at the given URI, creating a finder for - * it via {@link #createFinder(BeanFactory, String)}. - * - * @param uri - * The attachment URI. - * @param beanName - * The bean name. - * @param beanFactory - * The Spring bean factory. + * Attaches the named resource bean at the given URI, creating a finder for it via {@link + * #createFinder(BeanFactory, String)}. + * + * @param uri The attachment URI. + * @param beanName The bean name. + * @param beanFactory The Spring bean factory. */ - protected void attachResource(String uri, String beanName, - BeanFactory beanFactory) { + protected void attachResource(String uri, String beanName, BeanFactory beanFactory) { attach(uri, createFinder(beanFactory, beanName)); } /** * Attaches the named restlet bean directly at the given URI. - * - * @param uri - * The attachment URI. - * @param beanName - * The bean name. - * @param beanFactory - * The Spring bean factory. + * + * @param uri The attachment URI. + * @param beanName The bean name. + * @param beanFactory The Spring bean factory. */ - protected void attachRestlet(String uri, String beanName, - BeanFactory beanFactory) { + protected void attachRestlet(String uri, String beanName, BeanFactory beanFactory) { attach(uri, (Restlet) beanFactory.getBean(beanName)); } /** - * Creates an instance of {@link SpringBeanFinder}. This can be overridden - * if necessary. - * - * @param beanFactory - * The Spring bean factory. - * @param beanName - * The bean name. + * Creates an instance of {@link SpringBeanFinder}. This can be overridden if necessary. + * + * @param beanFactory The Spring bean factory. + * @param beanName The bean name. * @see #attachResource */ protected Finder createFinder(BeanFactory beanFactory, String beanName) { @@ -177,7 +152,7 @@ protected Finder createFinder(BeanFactory beanFactory, String beanName) { /** * Returns supplemental explicit mappings - * + * * @return Supplemental explicit mappings */ protected Map getAttachments() { @@ -186,72 +161,62 @@ protected Map getAttachments() { /** * Returns the list of bean name for the given type. - * - * @param beanClass - * The bean class to lookup. - * @param beanFactory - * The Spring bean factory. + * + * @param beanClass The bean class to lookup. + * @param beanFactory The Spring bean factory. * @return The array of bean names. */ - private String[] getBeanNamesByType(Class beanClass, - ListableBeanFactory beanFactory) { - return isFindingInAncestors() ? BeanFactoryUtils - .beanNamesForTypeIncludingAncestors(beanFactory, beanClass, - true, true) : beanFactory.getBeanNamesForType( - beanClass, true, true); + private String[] getBeanNamesByType(Class beanClass, ListableBeanFactory beanFactory) { + return isFindingInAncestors() + ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + beanFactory, beanClass, true, true) + : beanFactory.getBeanNamesForType(beanClass, true, true); } /** * Indicates if the attachments contain a mapping for the given URI. - * - * @param name - * The name to test. + * + * @param name The name to test. * @return True if the attachments contain a mapping for the given URI. */ private boolean isAvailableUri(String name) { return name.startsWith("/") - && (getAttachments() == null || !getAttachments().containsKey( - name)); + && (getAttachments() == null || !getAttachments().containsKey(name)); } /** - * Returns true if bean names will be searched for higher up in the - * BeanFactory hierarchy. Default is true. - * - * @return True if bean names will be searched for higher up in the - * BeanFactory hierarchy. + * Returns true if bean names are searched for higher up in the BeanFactory hierarchy. Default + * is true. + * + * @return True if bean names will be searched for higher up in the BeanFactory hierarchy. */ public boolean isFindingInAncestors() { return this.findingInAncestors; } /** - * Attaches all {@link ServerResource} and {@link Restlet} beans found in - * the surrounding bean factory for which {@link #resolveUri} finds a usable - * URI. Also attaches everything explicitly routed in the attachments - * property. - * - * @param beanFactory - * The Spring bean factory. + * Attaches all {@link ServerResource} and {@link Restlet} beans found in the surrounding bean + * factory for which {@link #resolveUri} finds a usable URI. Also attaches everything explicitly + * routed in the attachments' property. + * + * @param beanFactory The Spring bean factory. * @see #setAttachments */ - public void postProcessBeanFactory( - ConfigurableListableBeanFactory beanFactory) throws BeansException { + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { - ListableBeanFactory source = this.applicationContext == null ? beanFactory - : this.applicationContext; + ListableBeanFactory source = + this.applicationContext == null ? beanFactory : this.applicationContext; attachAllResources(source); attachAllRestlets(source); if (getAttachments() != null) { - for (Map.Entry attachment : getAttachments() - .entrySet()) { + for (Map.Entry attachment : getAttachments().entrySet()) { String uri = attachment.getKey(); String beanName = attachment.getValue(); Class beanType = source.getType(beanName); - if (org.restlet.resource.ServerResource.class - .isAssignableFrom(beanType)) { + if (org.restlet.resource.ServerResource.class.isAssignableFrom(beanType)) { attachResource(uri, beanName, source); } else if (Restlet.class.isAssignableFrom(beanType)) { attachRestlet(uri, beanName, source); @@ -265,16 +230,12 @@ public void postProcessBeanFactory( } /** - * Uses this first alias for this bean that starts with '/' and is not - * mapped in the explicit attachments to another bean. This mimics the - * behavior of - * {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping} - * . - * - * @param beanName - * The bean name to lookup in the bean factory aliases. - * @param beanFactory - * The Spring bean factory. + * Uses this first alias for this bean that starts with '/' and is not mapped in the explicit + * attachments to another bean. This mimics the behavior of {@link + * org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping} . + * + * @param beanName The bean name to lookup in the bean factory aliases. + * @param beanFactory The Spring bean factory. * @return The alias URI. */ protected String resolveUri(String beanName, ListableBeanFactory beanFactory) { @@ -293,23 +254,19 @@ protected String resolveUri(String beanName, ListableBeanFactory beanFactory) { /** * Sets the Spring application context. - * - * @param applicationContext - * The context. + * + * @param applicationContext The context. */ - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** - * Sets an explicit mapping of URI templates to bean IDs to use in addition - * to the usual bean name mapping behavior. If a URI template appears in - * both this mapping and as a bean name, the bean it is mapped to here is - * the one that will be used. - * - * @param attachments - * Supplemental explicit mappings. + * Sets an explicit mapping of URI templates to bean IDs to use in addition to the usual bean + * name mapping behavior. If a URI template appears in both this mapping and as a bean name, the + * bean it is mapped to here is the one that will be used. + * + * @param attachments Supplemental explicit mappings. * @see SpringRouter */ public void setAttachments(Map attachments) { @@ -317,14 +274,11 @@ public void setAttachments(Map attachments) { } /** - * Sets if bean names will be searched for higher up in the BeanFactory - * hierarchy. - * - * @param findingInAncestors - * Search for beans higher up in the BeanFactory hierarchy. + * Sets if bean names are searched for higher up in the BeanFactory hierarchy. + * + * @param findingInAncestors Search for beans higher up in the BeanFactory hierarchy. */ public void setFindingInAncestors(boolean findingInAncestors) { this.findingInAncestors = findingInAncestors; } - } diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringComponent.java b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringComponent.java index 241dfbd0e7..8aa58eb18e 100644 --- a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringComponent.java +++ b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringComponent.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; import java.util.ArrayList; import java.util.List; - import org.restlet.Client; import org.restlet.Restlet; import org.restlet.Server; @@ -19,7 +17,7 @@ /** * Component that is easily configurable from Spring. Here is a usage example: - * + * *

  * <bean id="component"
  *         class="org.restlet.ext.spring.SpringComponent">
@@ -36,10 +34,10 @@
  *                 </list>
  *         </property>
  * </bean>
- * 
+ *
  * <bean id="component.context"
  *         class="org.springframework.beans.factory.config.PropertyPathFactoryBean" />
- * 
+ *
  * <bean id="server" class="org.restlet.ext.spring.SpringServer">
  *         <constructor-arg value="http" />
  *         <constructor-arg value="8111" />
@@ -51,96 +49,84 @@
  *         </property>
  * </bean>
  * 
- * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @see Spring home page * @author Jerome Louvel */ public class SpringComponent extends org.restlet.Component { /** - * Adds a client to the list of connectors. The value can be either a - * protocol name, a Protocol instance or a Client instance. - * - * @param clientInfo - * The client info. + * Adds a client to the list of connectors. The value can be either a protocol name, a Protocol + * instance, or a Client instance. + * + * @param clientInfo The client info. */ public void setClient(Object clientInfo) { - final List clients = new ArrayList(); + final List clients = new ArrayList<>(); clients.add(clientInfo); setClientsList(clients); } /** - * Sets the list of clients, either as protocol names, Protocol instances or - * Client instances. - * - * @param clients - * The list of clients. + * Sets the list of clients, either as protocol names, Protocol instances, or Client instances. + * + * @param clients The list of clients. */ public synchronized void setClientsList(List clients) { for (final Object client : clients) { - if (client instanceof String) { - getClients().add(Protocol.valueOf((String) client)); - } else if (client instanceof Protocol) { - getClients().add((Protocol) client); - } else if (client instanceof Client) { - getClients().add((Client) client); - } else { - getLogger() - .warning( - "Unknown object found in the clients list. Only instances of String, org.restlet.data.Protocol and org.restlet.Client are allowed."); + switch (client) { + case String string -> getClients().add(Protocol.valueOf(string)); + case Protocol protocol -> getClients().add(protocol); + case Client client1 -> getClients().add(client1); + case null, default -> + getLogger() + .warning( + "Unknown object found in the clients list. Only instances of String, org.restlet.data.Protocol and org.restlet.Client are allowed."); } } } /** * Attaches a target Restlet to the default host. - * - * @param target - * The target Restlet. + * + * @param target The target Restlet. */ public void setDefaultTarget(Restlet target) { getDefaultHost().attach(target); } /** - * Adds a server to the list of connectors. The value can be either a - * protocol name, a Protocol instance or a Server instance. - * - * @param serverInfo - * The server info. + * Adds a server to the list of connectors. The value can be either a protocol name, a Protocol + * instance, or a Server instance. + * + * @param serverInfo The server info. */ public void setServer(Object serverInfo) { - final List servers = new ArrayList(); + final List servers = new ArrayList<>(); servers.add(serverInfo); setServersList(servers); } /** - * Sets the list of servers, either as protocol names, Protocol instances or - * Server instances. - * - * @param serversInfo - * The list of servers. + * Sets the list of servers, either as protocol names, Protocol instances, or Server instances. + * + * @param serversInfo The list of servers. */ public void setServersList(List serversInfo) { for (final Object serverInfo : serversInfo) { - if (serverInfo instanceof String) { - getServers().add(Protocol.valueOf((String) serverInfo)); - } else if (serverInfo instanceof Protocol) { - getServers().add((Protocol) serverInfo); - } else if (serverInfo instanceof Server) { - getServers().add((Server) serverInfo); - } else { - getLogger() - .warning( - "Unknown object found in the servers list. Only instances of String, org.restlet.data.Protocol and org.restlet.Server are allowed."); + switch (serverInfo) { + case String string -> getServers().add(Protocol.valueOf(string)); + case Protocol protocol -> getServers().add(protocol); + case Server server -> getServers().add(server); + case null, default -> + getLogger() + .warning( + "Unknown object found in the servers list. Only instances of String, org.restlet.data.Protocol and org.restlet.Server are allowed."); } } } - } diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringContext.java b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringContext.java index 97d95f77a3..3dd4a3abf9 100644 --- a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringContext.java +++ b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringContext.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; import java.util.ArrayList; import java.util.List; - import org.restlet.Context; import org.restlet.Request; import org.restlet.data.Method; @@ -21,9 +19,9 @@ import org.springframework.context.support.GenericApplicationContext; /** - * Spring application context based on a Restlet context. Here is an example - * illustrating the various ways to use this class: - * + * Spring application context based on a Restlet context. Here is an example illustrating the + * various ways to use this class: + * *

  * SpringContext springContext = new SpringContext(getContext());
  * springContext.getPropertyConfigRefs().add("war://config/database.properties");
@@ -33,7 +31,7 @@
  * springContext.getXmlConfigRefs().add(
  *         "clap://thread/config/applicationContext.xml");
  * 
- * + * * @author Jerome Louvel */ public class SpringContext extends GenericApplicationContext { @@ -41,25 +39,20 @@ public class SpringContext extends GenericApplicationContext { private volatile boolean loaded; /** - * The modifiable list of configuration URIs for beans definitions via - * property representations. + * The modifiable list of configuration URIs for beans definitions via property representations. */ private volatile List propertyConfigRefs; /** The parent Restlet context. */ private volatile Context restletContext; - /** - * The modifiable list of configuration URIs for beans definitions via XML - * representations. - */ + /** The modifiable list of configuration URIs for beans definitions via XML representations. */ private volatile List xmlConfigRefs; /** * Constructor. - * - * @param restletContext - * The parent Restlet context. + * + * @param restletContext The parent Restlet context. */ public SpringContext(Context restletContext) { this.restletContext = restletContext; @@ -69,9 +62,9 @@ public SpringContext(Context restletContext) { } /** - * Returns the modifiable list of configuration URIs for beans definitions - * via property representations. - * + * Returns the modifiable list of configuration URIs for beans definitions via property + * representations. + * * @return The modifiable list of configuration URIs. */ public List getPropertyConfigRefs() { @@ -81,7 +74,7 @@ public List getPropertyConfigRefs() { synchronized (this) { p = this.propertyConfigRefs; if (p == null) { - this.propertyConfigRefs = p = new ArrayList(); + this.propertyConfigRefs = p = new ArrayList<>(); } } } @@ -90,7 +83,7 @@ public List getPropertyConfigRefs() { /** * Returns the parent Restlet context. - * + * * @return The parent Restlet context. */ public Context getRestletContext() { @@ -98,9 +91,9 @@ public Context getRestletContext() { } /** - * Returns the modifiable list of configuration URIs for beans definitions - * via XML representations. - * + * Returns the modifiable list of configuration URIs for beans definitions via XML + * representations. + * * @return The modifiable list of configuration URIs. */ public List getXmlConfigRefs() { @@ -110,7 +103,7 @@ public List getXmlConfigRefs() { synchronized (this) { x = this.xmlConfigRefs; if (x == null) { - this.xmlConfigRefs = x = new ArrayList(); + this.xmlConfigRefs = x = new ArrayList<>(); } } } @@ -127,8 +120,11 @@ public void refresh() { // First, read the bean definitions from properties representations PropertiesBeanDefinitionReader propReader = null; for (final String ref : getPropertyConfigRefs()) { - config = getRestletContext().getClientDispatcher() - .handle(new Request(Method.GET, ref)).getEntity(); + config = + getRestletContext() + .getClientDispatcher() + .handle(new Request(Method.GET, ref)) + .getEntity(); if (config != null) { propReader = new PropertiesBeanDefinitionReader(this); @@ -139,13 +135,15 @@ public void refresh() { // Then, read the bean definitions from XML representations XmlBeanDefinitionReader xmlReader = null; for (final String ref : getXmlConfigRefs()) { - config = getRestletContext().getClientDispatcher() - .handle(new Request(Method.GET, ref)).getEntity(); + config = + getRestletContext() + .getClientDispatcher() + .handle(new Request(Method.GET, ref)) + .getEntity(); if (config != null) { xmlReader = new XmlBeanDefinitionReader(this); - xmlReader - .setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD); + xmlReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD); xmlReader.loadBeanDefinitions(new SpringResource(config)); } } @@ -154,5 +152,4 @@ public void refresh() { // Now load or refresh super.refresh(); } - } diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringFinder.java b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringFinder.java index 87c669c6c5..fef6481175 100644 --- a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringFinder.java +++ b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringFinder.java @@ -1,16 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; import java.util.logging.Level; - import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -19,49 +17,43 @@ import org.restlet.resource.ServerResource; /** - * Finder that is specialized for easier usage by Spring wiring services. The - * idea is to create a singleton Spring bean based on that SpringFinder and - * configure it using Spring's "lookup-method" element to return instances of a - * "prototype" bean for {@link #create()}. Finally, attach the SpringFinder to - * your Router. When the {@link #create()} method is invoked, a new instance of - * your prototype bean will be created and returned. A sample XML for - * "lookup-method": - * + * Finder that is specialized for easier usage by Spring wiring services. The idea is to create a + * singleton Spring bean based on that SpringFinder and configure it using Spring's "lookup-method" + * element to return instances of a "prototype" bean for {@link #create()}. Finally, attach the + * SpringFinder to your Router. When the {@link #create()} method is invoked, a new instance of your + * prototype bean will be created and returned. A sample XML for "lookup-method": + * *

- *      <bean id="myFinder" class="org.restlet.ext.spring.SpringFinder"> 
- *              <lookup-method name="create" bean="myResource"/> 
+ *      <bean id="myFinder" class="org.restlet.ext.spring.SpringFinder">
+ *              <lookup-method name="create" bean="myResource"/>
  *      </bean>
- *       
- *      <bean id="myResource" class="com.mycompany.rest.resource.MyResource" scope="prototype"> 
- *              <property name="aProperty" value="anotherOne"/> 
+ *
+ *      <bean id="myResource" class="com.mycompany.rest.resource.MyResource" scope="prototype">
+ *              <property name="aProperty" value="anotherOne"/>
  *              <property name="oneMore" value="true"/>
  *      </bean>
  * 
- * - * Note that the Code Generation - * Library (cglib) will be required in order to use the Spring's lookup - * method mechanism.
+ * + * Note that the Code Generation Library (cglib) will be + * required to use the Spring's lookup method mechanism.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class SpringFinder extends Finder { - /** - * Constructor. - */ + /** Constructor. */ public SpringFinder() { super(); } /** * Constructor. - * - * @param context - * The parent context. + * + * @param context The parent context. */ public SpringFinder(Context context) { super(context); @@ -69,32 +61,27 @@ public SpringFinder(Context context) { /** * Constructor. - * - * @param context - * The context. - * @param targetClass - * The target resource class. + * + * @param context The context. + * @param targetClass The target resource class. */ - public SpringFinder(Context context, - Class targetClass) { + public SpringFinder(Context context, Class targetClass) { super(context, targetClass); } /** * Constructor. - * - * @param restlet - * The parent Restlet. + * + * @param restlet The parent Restlet. */ public SpringFinder(Restlet restlet) { super(restlet.getContext()); } /** - * Creates a new instance of the {@link ServerResource} class designated by - * the "targetClass" property. This method is intended to be configured as a - * lookup method in Spring. - * + * Creates a new instance of the {@link ServerResource} class designated by the "targetClass" + * property. This method is intended to be configured as a lookup method in Spring. + * * @return The created resource or null. */ public ServerResource create() { @@ -106,7 +93,8 @@ public ServerResource create() { result = getTargetClass().getDeclaredConstructor().newInstance(); } catch (Exception e) { getLogger() - .log(Level.WARNING, + .log( + Level.WARNING, "Exception while instantiating the target server resource.", e); } @@ -116,20 +104,17 @@ public ServerResource create() { } /** - * Calls the {@link #create()} method that can be configured as a lookup - * method in Spring. Overriding this method was necessary for direct calls - * to it, for example by unit tests. + * Calls the {@link #create()} method that can be configured as a lookup method in Spring. + * Overriding this method was necessary for direct calls to it, for example, by unit tests. */ @Override - public ServerResource create(Class targetClass, - Request request, Response response) { + public ServerResource create( + Class targetClass, Request request, Response response) { return create(request, response); } @Override - public org.restlet.resource.ServerResource create(Request request, - Response response) { + public org.restlet.resource.ServerResource create(Request request, Response response) { return create(); } - } diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringHost.java b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringHost.java index 0fbfc20f02..436927da36 100644 --- a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringHost.java +++ b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringHost.java @@ -1,16 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; import java.util.Map; - import org.restlet.Component; import org.restlet.Context; import org.restlet.Restlet; @@ -18,9 +16,8 @@ import org.restlet.routing.VirtualHost; /** - * Virtual host that is easily configurable with Spring. Here is a usage - * example: - * + * Virtual host that is easily configurable with Spring. Here is a usage example: + * *

  *     <bean id="virtualHost" class="org.restlet.ext.spring.SpringHost">
  *         <constructor-arg ref="component" />
@@ -35,20 +32,19 @@
  *         </property>
  *     </bean>
  * 
- * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class SpringHost extends VirtualHost { /** * Constructor. - * - * @param component - * The parent component. + * + * @param component The parent component. */ public SpringHost(Component component) { super(component.getContext()); @@ -56,39 +52,35 @@ public SpringHost(Component component) { /** * Constructor. - * - * @param context - * The parent context. + * + * @param context The parent context. */ public SpringHost(Context context) { super(context); } /** - * Sets a route to attach. The keys is the URI template and the value can be - * either Restlet instance, {@link ServerResource} subclasse (as - * {@link Class} instances or as qualified class names). - * - * @param path - * The attachment URI path. - * @param route - * The route object to attach. + * Sets a route to attach. The keys are the URI template, and the value can be either Restlet + * instance, {@link ServerResource} subclasse (as {@link Class} instances or as qualified class + * names). + * + * @param path The attachment URI path. + * @param route The route object to attach. */ public void setAttachment(String path, Object route) { - if (route instanceof Restlet) { - checkContext((Restlet) route); + if (route instanceof Restlet restlet) { + checkContext(restlet); } SpringRouter.setAttachment(this, path, route); } /** - * Sets the map of routes to attach. The map keys are the URI templates and - * the values can be either Restlet instances, {@link ServerResource} - * subclasses (as {@link Class} instances or as qualified class names). - * - * @param routes - * The map of routes to attach. + * Sets the map of routes to attach. The map keys are the URI templates, and the values can be + * either Restlet instances, {@link ServerResource} subclasses (as {@link Class} instances or as + * qualified class names). + * + * @param routes The map of routes to attach. */ public void setAttachments(Map routes) { for (String key : routes.keySet()) { @@ -97,15 +89,12 @@ public void setAttachments(Map routes) { } /** - * Sets the default route to attach. The route can be either Restlet - * instances, {@link ServerResource} subclasses (as {@link Class} instances - * or as qualified class names). - * - * @param route - * The default route to attach. + * Sets the default route to attach. The route can be either Restlet instances, {@link + * ServerResource} subclasses (as {@link Class} instances or as qualified class names). + * + * @param route The default route to attach. */ public void setDefaultAttachment(Object route) { setAttachment("", route); } - } diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringResource.java b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringResource.java index 30ba70a282..b44ac07203 100644 --- a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringResource.java +++ b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringResource.java @@ -1,26 +1,24 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; import java.io.IOException; import java.io.InputStream; - +import java.util.Objects; import org.restlet.engine.util.SystemUtils; import org.restlet.representation.Representation; import org.springframework.core.io.AbstractResource; /** - * Spring Resource based on a Restlet Representation. DON'T GET CONFUSED, - * Spring's notion of Resource is different from Restlet's one, actually it's - * closer to Restlet's Representations. - * + * Spring Resource based on a Restlet Representation. DON'T GET CONFUSED, Spring's notion of + * Resource is different from Restlet's one, actually it's closer to Restlet's Representations. + * * @author Jerome Louvel */ public class SpringResource extends AbstractResource { @@ -35,9 +33,8 @@ public class SpringResource extends AbstractResource { /** * Constructor. - * - * @param representation - * The description. + * + * @param representation The description. */ public SpringResource(Representation representation) { this(representation, "Restlet Representation"); @@ -45,34 +42,32 @@ public SpringResource(Representation representation) { /** * Constructor. - * - * @param representation - * The description. - * @param description - * The description. + * + * @param representation The description. + * @param description The description. */ public SpringResource(Representation representation, String description) { if (representation == null) { - throw new IllegalArgumentException( - "Representation must not be null"); + throw new IllegalArgumentException("Representation must not be null"); } this.representation = representation; this.description = (description != null) ? description : ""; } - /** - * This implementation compares the underlying InputStream. - */ + /** {@inheritDoc} */ @Override public boolean equals(Object obj) { - return ((obj == this) || ((obj instanceof SpringResource) && ((SpringResource) obj).representation - .equals(this.representation))); + if (obj == this) { + return true; + } + if (!(obj instanceof SpringResource that)) { + return false; + } + return Objects.equals(representation, that.representation); } - /** - * This implementation always returns true. - */ + /** This implementation always returns true. */ @Override public boolean exists() { return true; @@ -80,7 +75,7 @@ public boolean exists() { /** * Returns the description. - * + * * @return The description. */ public String getDescription() { @@ -88,11 +83,10 @@ public String getDescription() { } /** - * This implementation throws IllegalStateException if attempting to read - * the underlying stream multiple times. + * This implementation throws IllegalStateException if attempting to read the underlying stream + * multiple times. */ - public InputStream getInputStream() throws IOException, - IllegalStateException { + public InputStream getInputStream() throws IOException, IllegalStateException { if (this.read && this.representation.isTransient()) { throw new IllegalStateException( "Representation has already been read and is transient."); @@ -102,20 +96,15 @@ public InputStream getInputStream() throws IOException, return this.representation.getStream(); } - /** - * This implementation returns the hash code of the underlying InputStream. - */ + /** This implementation returns the hash code of the underlying InputStream. */ @Override public int hashCode() { return SystemUtils.hashCode(this.representation); } - /** - * This implementation always returns true. - */ + /** This implementation always returns true. */ @Override public boolean isOpen() { return true; } - } diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringRouter.java b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringRouter.java index dcbdbd2d5c..c091837b56 100644 --- a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringRouter.java +++ b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringRouter.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; import java.util.Map; import java.util.logging.Level; - import org.restlet.Context; import org.restlet.Restlet; import org.restlet.engine.Engine; @@ -20,11 +18,11 @@ /** * Router that is easily configurable with Spring. Here is a usage example: - * + * *

  * <bean class="org.restlet.ext.spring.SpringRouter">
  *     <constructor-arg ref="application" />
- * 
+ *
  *     <property name="attachments">
  *         <map>
  *             <entry key="/users/{user}"                  value="org.restlet.example.tutorial.UserResource" />
@@ -34,49 +32,43 @@
  *     </property>
  * </bean>
  * 
- * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class SpringRouter extends Router { /** * Attach a single route. - * - * @param router - * The router to attach to. - * @param path - * The attachment URI path. - * @param route - * The route object to attach. + * + * @param router The router to attach to. + * @param path The attachment URI path. + * @param route The route object to attach. */ @SuppressWarnings("unchecked") public static void setAttachment(Router router, String path, Object route) { Class resourceClass; - if (route instanceof Restlet) { - router.attach(path, (Restlet) route); + if (route instanceof Restlet restlet) { + router.attach(path, restlet); } else if (route instanceof Class) { router.attach(path, (Class) route); - } else if (route instanceof String) { + } else if (route instanceof String string) { try { - resourceClass = Engine.loadClass((String) route); + resourceClass = Engine.loadClass(string); - if (org.restlet.resource.ServerResource.class - .isAssignableFrom(resourceClass)) { - router.attach(path, - (Class) resourceClass); + if (org.restlet.resource.ServerResource.class.isAssignableFrom(resourceClass)) { + router.attach(path, (Class) resourceClass); } else { router.getLogger() .warning( "Unknown class found in the mappings. Only subclasses of org.restlet.resource.Resource and ServerResource are allowed."); } } catch (ClassNotFoundException e) { - router.getLogger().log(Level.WARNING, - "Unable to set the router mappings", e); + router.getLogger().log(Level.WARNING, "Unable to set the router mappings", e); } } else { router.getLogger() @@ -87,11 +79,9 @@ public static void setAttachment(Router router, String path, Object route) { /** * Sets the map of routes to attach. - * - * @param router - * The router to attach to. - * @param routes - * The map of routes to attach + * + * @param router The router to attach to. + * @param routes The map of routes to attach */ public static void setAttachments(Router router, Map routes) { for (String key : routes.keySet()) { @@ -99,18 +89,15 @@ public static void setAttachments(Router router, Map routes) { } } - /** - * Constructor. - */ + /** Constructor. */ public SpringRouter() { super(); } /** * Constructor with a parent context. - * - * @param context - * The parent context. + * + * @param context The parent context. */ public SpringRouter(Context context) { super(context); @@ -118,36 +105,31 @@ public SpringRouter(Context context) { /** * Constructor with a parent Restlet. - * - * @param parent - * The parent Restlet. + * + * @param parent The parent Restlet. */ public SpringRouter(Restlet parent) { super(parent.getContext()); } /** - * Sets the map of routes to attach. The map keys are the URI templates and - * the values can be either Restlet instances, {@link ServerResource} - * subclasses (as {@link Class} instances or as qualified class names). - * - * @param routes - * The map of routes to attach. + * Sets the map of routes to attach. The map keys are the URI templates, and the values can be + * either Restlet instances, {@link ServerResource} subclasses (as {@link Class} instances or as + * qualified class names). + * + * @param routes The map of routes to attach. */ public void setAttachments(Map routes) { setAttachments(this, routes); } /** - * Sets the default route to attach. The route can be either Restlet - * instances, {@link ServerResource} subclasses (as {@link Class} instances - * or as qualified class names). - * - * @param route - * The default route to attach. + * Sets the default route to attach. The route can be either Restlet instances, {@link + * ServerResource} subclasses (as {@link Class} instances or as qualified class names). + * + * @param route The default route to attach. */ public void setDefaultAttachment(Object route) { setAttachment(this, "", route); } - } diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringServer.java b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringServer.java index f09ee75c8d..4f51f904fd 100644 --- a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringServer.java +++ b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/SpringServer.java @@ -1,35 +1,33 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; import java.util.Enumeration; import java.util.Properties; - import org.restlet.Context; import org.restlet.Restlet; import org.restlet.data.Protocol; /** * Server that is easily configurable with Spring. Here is a usage example: - * + * *

  * <bean id="server" class="org.restlet.ext.spring.SpringServer">
  *      <constructor-arg value="http" />
  *      <constructor-arg value="8111" />
  * </bean>
  * 
- * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @see Spring home page * @author Jerome Louvel */ @@ -37,9 +35,8 @@ public class SpringServer extends org.restlet.Server { /** * Constructor. - * - * @param protocol - * The server's protocol such as "HTTP" or "HTTPS". + * + * @param protocol The server's protocol such as "HTTP" or "HTTPS". */ public SpringServer(String protocol) { super(new Context(), Protocol.valueOf(protocol), (Restlet) null); @@ -47,11 +44,9 @@ public SpringServer(String protocol) { /** * Constructor. - * - * @param protocol - * The server's protocol such as "HTTP" or "HTTPS". - * @param port - * The port number. + * + * @param protocol The server's protocol such as "HTTP" or "HTTPS". + * @param port The port number. */ public SpringServer(String protocol, int port) { super(new Context(), Protocol.valueOf(protocol), port, (Restlet) null); @@ -59,13 +54,10 @@ public SpringServer(String protocol, int port) { /** * Constructor. - * - * @param protocol - * The server's protocol such as "HTTP" or "HTTPS". - * @param address - * The IP address. - * @param port - * The port number. + * + * @param protocol The server's protocol such as "HTTP" or "HTTPS". + * @param address The IP address. + * @param port The port number. */ public SpringServer(String protocol, String address, int port) { super(new Context(), Protocol.valueOf(protocol), address, port, null); @@ -73,17 +65,14 @@ public SpringServer(String protocol, String address, int port) { /** * Sets parameters on the server. - * - * @param parameters - * Parameters to set on the server. + * + * @param parameters Parameters to set on the server. */ public void setParameters(Properties parameters) { final Enumeration names = parameters.propertyNames(); while (names.hasMoreElements()) { final String name = (String) names.nextElement(); - getContext().getParameters() - .add(name, parameters.getProperty(name)); + getContext().getParameters().add(name, parameters.getProperty(name)); } } - } diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/package-info.java b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/package-info.java new file mode 100644 index 0000000000..d716dacca4 --- /dev/null +++ b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/package-info.java @@ -0,0 +1,9 @@ +/** + * Integration with Spring Framework 5.2. + * + * @since Restlet 1.0 + * @see Spring Framework + * @see User + * Guide - Spring extension + */ +package org.restlet.ext.spring; diff --git a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/package.html b/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/package.html deleted file mode 100644 index 8d7a3ec8d7..0000000000 --- a/org.restlet.ext.spring/src/main/java/org/restlet/ext/spring/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - -Integration with Spring Framework 5.2. - -@since Restlet 1.0 -@see Spring Framework -@see User Guide - Spring extension - - \ No newline at end of file diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanFinderTestCase.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanFinderTestCase.java index 7a590e2328..2591c1662e 100644 --- a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanFinderTestCase.java +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanFinderTestCase.java @@ -1,14 +1,18 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -20,20 +24,14 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.support.StaticApplicationContext; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - /** * @author Rhett Sutphin */ -public class SpringBeanFinderTestCase { +class SpringBeanFinderTestCase { - private static class AnotherResource extends ServerResource { - } + private static class AnotherResource extends ServerResource {} - private static class SomeResource extends ServerResource { - } + private static class SomeResource extends ServerResource {} private static class SomeServerResource extends ServerResource { private String src; @@ -61,10 +59,11 @@ public void setSrc(String src) { private SpringBeanFinder finder; private MutablePropertyValues createServerResourcePropertyValues() { - return new MutablePropertyValues(List.of(new PropertyValue("src","spring"))); + return new MutablePropertyValues(List.of(new PropertyValue("src", "spring"))); } - private void registerApplicationContextBean(String beanName, Class resourceClass) { + private void registerApplicationContextBean( + String beanName, Class resourceClass) { this.applicationContext.registerPrototype(beanName, resourceClass); this.applicationContext.refresh(); } @@ -73,8 +72,10 @@ private void registerBeanFactoryBean(String beanName, Class resourceClass) { registerBeanFactoryBean(beanName, resourceClass, null); } - private void registerBeanFactoryBean(String beanName, Class resourceClass, MutablePropertyValues values) { - this.beanFactory.registerBeanDefinition(beanName, + private void registerBeanFactoryBean( + String beanName, Class resourceClass, MutablePropertyValues values) { + this.beanFactory.registerBeanDefinition( + beanName, new RootBeanDefinition(resourceClass, new ConstructorArgumentValues(), values)); } @@ -94,52 +95,59 @@ protected void tearDownEach() { } @Test - public void testBeanResolutionFailsWithNeitherApplicationContextOrBeanFactory() { - IllegalStateException iae = assertThrows(IllegalStateException.class, () -> this.finder.create()); - assertEquals("Either a beanFactory or an applicationContext is required for SpringBeanFinder.", iae.getMessage()); + void testBeanResolutionFailsWithNeitherApplicationContextOrBeanFactory() { + IllegalStateException iae = + assertThrows(IllegalStateException.class, () -> this.finder.create()); + assertEquals( + "Either a beanFactory or an applicationContext is required for SpringBeanFinder.", + iae.getMessage()); } @Test - public void testBeanResolutionFailsWhenNoMatchingBeanButThereIsABeanFactory() { + void testBeanResolutionFailsWhenNoMatchingBeanButThereIsABeanFactory() { this.finder.setBeanFactory(beanFactory); - IllegalStateException iae = assertThrows(IllegalStateException.class, () -> this.finder.create()); + IllegalStateException iae = + assertThrows(IllegalStateException.class, () -> this.finder.create()); assertEquals("No bean named " + BEAN_NAME + " present.", iae.getMessage()); } @Test - public void testBeanResolutionFailsWhenNoMatchingBeanButThereIsAnApplicationContext() { + void testBeanResolutionFailsWhenNoMatchingBeanButThereIsAnApplicationContext() { this.finder.setApplicationContext(applicationContext); - IllegalStateException iae = assertThrows(IllegalStateException.class, () -> this.finder.create()); + IllegalStateException iae = + assertThrows(IllegalStateException.class, () -> this.finder.create()); assertEquals("No bean named " + BEAN_NAME + " present.", iae.getMessage()); } @Test - public void testExceptionWhenResourceBeanIsWrongType() { + void testExceptionWhenResourceBeanIsWrongType() { registerBeanFactoryBean(BEAN_NAME, String.class); this.finder.setBeanFactory(beanFactory); - ClassCastException classCastException = assertThrows(ClassCastException.class, () -> this.finder.create()); + ClassCastException classCastException = + assertThrows(ClassCastException.class, () -> this.finder.create()); assertEquals( "fish does not resolve to an instance of org.restlet.resource.ServerResource", classCastException.getMessage()); } @Test - public void testExceptionWhenServerResourceBeanIsWrongType() { + void testExceptionWhenServerResourceBeanIsWrongType() { registerBeanFactoryBean(BEAN_NAME, String.class); this.finder.setBeanFactory(beanFactory); - ClassCastException classCastException = assertThrows(ClassCastException.class, () -> this.finder.create()); + ClassCastException classCastException = + assertThrows(ClassCastException.class, () -> this.finder.create()); assertEquals( "fish does not resolve to an instance of org.restlet.resource.ServerResource", classCastException.getMessage()); } @Test - public void testPrefersApplicationContextOverBeanFactoryIfTheBeanIsInBoth() { + void testPrefersApplicationContextOverBeanFactoryIfTheBeanIsInBoth() { registerApplicationContextBean(BEAN_NAME, SomeResource.class); registerBeanFactoryBean(BEAN_NAME, AnotherResource.class); @@ -147,11 +155,13 @@ public void testPrefersApplicationContextOverBeanFactoryIfTheBeanIsInBoth() { ServerResource actual = this.finder.create(); - assertTrue(actual instanceof SomeResource, "Resource not from application context: " + actual.getClass().getName()); + assertTrue( + actual instanceof SomeResource, + "Resource not from application context: " + actual.getClass().getName()); } @Test - public void testReturnsResourceBeanWhenExists() { + void testReturnsResourceBeanWhenExists() { registerBeanFactoryBean(BEAN_NAME, SomeResource.class); this.finder.setBeanFactory(beanFactory); @@ -162,23 +172,25 @@ public void testReturnsResourceBeanWhenExists() { } @Test - public void testReturnsServerResourceBeanForLongFormOfCreate() { - registerBeanFactoryBean(BEAN_NAME, SomeServerResource.class, - createServerResourcePropertyValues()); + void testReturnsServerResourceBeanForLongFormOfCreate() { + registerBeanFactoryBean( + BEAN_NAME, SomeServerResource.class, createServerResourcePropertyValues()); this.finder.setBeanFactory(beanFactory); - final ServerResource actual = this.finder.create( - SomeServerResource.class, null, null); + final ServerResource actual = this.finder.create(SomeServerResource.class, null, null); assertTrue(actual instanceof SomeServerResource, "Resource not the correct type"); - assertEquals("spring", ((SomeServerResource) actual).getSrc(), "Resource not from spring context"); + assertEquals( + "spring", + ((SomeServerResource) actual).getSrc(), + "Resource not from spring context"); } @Test - public void testReturnsServerResourceBeanWhenExists() { - registerBeanFactoryBean(BEAN_NAME, SomeServerResource.class, - createServerResourcePropertyValues()); + void testReturnsServerResourceBeanWhenExists() { + registerBeanFactoryBean( + BEAN_NAME, SomeServerResource.class, createServerResourcePropertyValues()); this.finder.setBeanFactory(beanFactory); @@ -188,7 +200,7 @@ public void testReturnsServerResourceBeanWhenExists() { } @Test - public void testUsesApplicationContextIfPresent() { + void testUsesApplicationContextIfPresent() { registerApplicationContextBean(BEAN_NAME, SomeResource.class); this.finder.setApplicationContext(applicationContext); diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanRouterTestCase.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanRouterTestCase.java index e8566469a4..0991d4ae50 100644 --- a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanRouterTestCase.java +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanRouterTestCase.java @@ -1,14 +1,22 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,16 +37,10 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.*; - /** * @author Rhett Sutphin */ -public class SpringBeanRouterTestCase { +class SpringBeanRouterTestCase { private static class TestAuthenticator extends ChallengeAuthenticator { private TestAuthenticator() throws IllegalArgumentException { @@ -46,14 +48,11 @@ private TestAuthenticator() throws IllegalArgumentException { } } - private static class TestFilter extends Filter { - } + private static class TestFilter extends Filter {} - private static class TestResource extends ServerResource { - } + private static class TestResource extends ServerResource {} - private static class TestRestlet extends Restlet { - } + private static class TestRestlet extends Restlet {} private static final String FISH_URI = "/renewable/fish/{fish_name}"; @@ -69,12 +68,19 @@ private RouteList actualRoutes() { } private void assertFinderForBean(String expectedBeanName, Restlet restlet) { - assertInstanceOf(SpringBeanFinder.class, restlet, "Restlet is not a bean finder restlet: " - + restlet.getClass().getName()); + assertInstanceOf( + SpringBeanFinder.class, + restlet, + "Restlet is not a bean finder restlet: " + restlet.getClass().getName()); final SpringBeanFinder actualFinder = (SpringBeanFinder) restlet; - assertEquals(expectedBeanName, actualFinder.getBeanName(), + assertEquals( + expectedBeanName, + actualFinder.getBeanName(), "Finder does not point to correct bean"); - assertEquals(this.factory, actualFinder.getBeanFactory(), "Finder does not point to correct bean factory"); + assertEquals( + this.factory, + actualFinder.getBeanFactory(), + "Finder does not point to correct bean factory"); } private void doPostProcess() { @@ -82,18 +88,21 @@ private void doPostProcess() { } private TemplateRoute matchRouteFor(String uri) { - Request req = new Request(Method.GET, - new Template(uri).format(new Resolver() { - @Override - public String resolve(String name) { - return name; - } - })); + Request req = + new Request( + Method.GET, + new Template(uri) + .format( + new Resolver() { + @Override + public String resolve(String name) { + return name; + } + })); return (TemplateRoute) router.getNext(req, new Response(req)); } - private void registerBeanDefinition(String id, String alias, - Class beanClass, String scope) { + private void registerBeanDefinition(String id, String alias, Class beanClass, String scope) { BeanDefinition bd = new RootBeanDefinition(beanClass); bd.setScope(scope == null ? BeanDefinition.SCOPE_SINGLETON : scope); this.factory.registerBeanDefinition(id, bd); @@ -104,8 +113,7 @@ private void registerBeanDefinition(String id, String alias, } private void registerServerResourceBeanDefinition(String id, String alias) { - registerBeanDefinition(id, alias, ServerResource.class, - BeanDefinition.SCOPE_PROTOTYPE); + registerBeanDefinition(id, alias, ServerResource.class, BeanDefinition.SCOPE_PROTOTYPE); } private Set routeUris(RouteList routes) { @@ -113,8 +121,7 @@ private Set routeUris(RouteList routes) { for (Route actualRoute : routes) { if (actualRoute instanceof TemplateRoute) { - uris.add(((TemplateRoute) actualRoute).getTemplate() - .getPattern()); + uris.add(((TemplateRoute) actualRoute).getTemplate().getPattern()); } } @@ -137,20 +144,20 @@ protected void tearDownEach() throws Exception { } @Test - public void testExplicitAttachmentsMayBeRestlets() { + void testExplicitAttachmentsMayBeRestlets() { String expected = "/protected/timber"; - this.router - .setAttachments(Collections.singletonMap(expected, "timber")); + this.router.setAttachments(Collections.singletonMap(expected, "timber")); registerBeanDefinition("timber", null, TestAuthenticator.class, null); doPostProcess(); TemplateRoute timberRoute = matchRouteFor(expected); assertNotNull(timberRoute, "No route for " + expected); - assertInstanceOf(TestAuthenticator.class, timberRoute.getNext(), "Route is not for correct restlet"); + assertInstanceOf( + TestAuthenticator.class, timberRoute.getNext(), "Route is not for correct restlet"); } @Test - public void testExplicitAttachmentsTrumpBeanNames() { + void testExplicitAttachmentsTrumpBeanNames() { this.router.setAttachments(Collections.singletonMap(ORE_URI, "fish")); RouteList actualRoutes = actualRoutes(); assertEquals(2, actualRoutes.size(), "Wrong number of routes"); @@ -161,7 +168,7 @@ public void testExplicitAttachmentsTrumpBeanNames() { } @Test - public void testExplicitRoutingForNonResourceNonRestletBeansFails() { + void testExplicitRoutingForNonResourceNonRestletBeansFails() { this.router.setAttachments(Collections.singletonMap("/fail", "someOtherBean")); IllegalStateException ise = assertThrows(IllegalStateException.class, this::doPostProcess); @@ -171,7 +178,7 @@ public void testExplicitRoutingForNonResourceNonRestletBeansFails() { } @Test - public void testRoutesCreatedForBeanIdsIfAppropriate() { + void testRoutesCreatedForBeanIdsIfAppropriate() { String grain = "/renewable/grain/{grain_type}"; registerServerResourceBeanDefinition(grain, null); @@ -181,7 +188,7 @@ public void testRoutesCreatedForBeanIdsIfAppropriate() { } @Test - public void testRoutesCreatedForUrlAliases() { + void testRoutesCreatedForUrlAliases() { final Set actualUris = routeUris(actualRoutes()); assertEquals(2, actualUris.size(), "Wrong number of URIs"); assertTrue(actualUris.contains(ORE_URI), "Missing ore URI: " + actualUris); @@ -189,7 +196,7 @@ public void testRoutesCreatedForUrlAliases() { } @Test - public void testRoutesPointToFindersForBeans() { + void testRoutesPointToFindersForBeans() { final RouteList actualRoutes = actualRoutes(); assertEquals(2, actualRoutes.size(), "Wrong number of routes"); TemplateRoute oreRoute = matchRouteFor(ORE_URI); @@ -202,19 +209,21 @@ public void testRoutesPointToFindersForBeans() { } @Test - public void testRoutingIncludesAuthenticators() { + void testRoutingIncludesAuthenticators() { String expected = "/protected/timber"; - registerBeanDefinition("timber", expected, TestAuthenticator.class, - null); + registerBeanDefinition("timber", expected, TestAuthenticator.class, null); doPostProcess(); TemplateRoute authenticatorRoute = matchRouteFor(expected); assertNotNull(authenticatorRoute, "No route for authenticator"); - assertInstanceOf(TestAuthenticator.class, authenticatorRoute.getNext(), "Route is not for authenticator"); + assertInstanceOf( + TestAuthenticator.class, + authenticatorRoute.getNext(), + "Route is not for authenticator"); } @Test - public void testRoutingIncludesFilters() { + void testRoutingIncludesFilters() { String expected = "/filtered/timber"; registerBeanDefinition("timber", expected, TestFilter.class, null); doPostProcess(); @@ -225,7 +234,7 @@ public void testRoutingIncludesFilters() { } @Test - public void testRoutingIncludesOtherRestlets() { + void testRoutingIncludesOtherRestlets() { String expected = "/singleton"; registerBeanDefinition("timber", expected, TestRestlet.class, null); doPostProcess(); @@ -236,10 +245,10 @@ public void testRoutingIncludesOtherRestlets() { } @Test - public void testRoutingIncludesResourceSubclasses() { + void testRoutingIncludesResourceSubclasses() { String expected = "/renewable/timber/{id}"; - registerBeanDefinition("timber", expected, TestResource.class, - BeanDefinition.SCOPE_PROTOTYPE); + registerBeanDefinition( + "timber", expected, TestResource.class, BeanDefinition.SCOPE_PROTOTYPE); doPostProcess(); TemplateRoute timberRoute = matchRouteFor("/renewable/timber/sycamore"); @@ -248,15 +257,14 @@ public void testRoutingIncludesResourceSubclasses() { } @Test - public void testRoutingIncludesSpringRouterStyleExplicitlyMappedBeans() { + void testRoutingIncludesSpringRouterStyleExplicitlyMappedBeans() { final BeanDefinition bd = new RootBeanDefinition(ServerResource.class); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); this.factory.registerBeanDefinition("timber", bd); this.factory.registerAlias("timber", "no-slash"); String expectedTemplate = "/renewable/timber/{farm_type}"; - router.setAttachments(Collections.singletonMap(expectedTemplate, - "timber")); + router.setAttachments(Collections.singletonMap(expectedTemplate, "timber")); final RouteList actualRoutes = actualRoutes(); assertEquals(3, actualRoutes.size(), "Wrong number of routes"); @@ -266,7 +274,7 @@ public void testRoutingIncludesSpringRouterStyleExplicitlyMappedBeans() { } @Test - public void testRoutingSkipsResourcesWithoutAppropriateAliases() { + void testRoutingSkipsResourcesWithoutAppropriateAliases() { final BeanDefinition bd = new RootBeanDefinition(ServerResource.class); bd.setScope(BeanDefinition.SCOPE_PROTOTYPE); this.factory.registerBeanDefinition("timber", bd); diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringTestCase.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringTestCase.java index 70e2f13972..9705621135 100644 --- a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringTestCase.java +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringTestCase.java @@ -1,14 +1,17 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -17,21 +20,17 @@ import org.restlet.engine.Engine; import org.springframework.context.support.ClassPathXmlApplicationContext; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - /** * Unit test case for the Spring extension. - * + * * @author Jerome Louvel */ -public class SpringTestCase { +class SpringTestCase { public static int TEST_PORT = 1337; // referenced in SpringTestCase.xml @Test - public void testSpring() throws Exception { + void testSpring() throws Exception { // Start the Restlet component Component component = (Component) ctx.getBean("component"); component.start(); @@ -41,13 +40,11 @@ public void testSpring() throws Exception { } @Test - public void testSpringServerProperties() { + void testSpringServerProperties() { Server server = (Server) ctx.getBean("server"); - assertEquals("value1", server.getContext().getParameters() - .getFirstValue("key1")); - assertEquals("value2", server.getContext().getParameters() - .getFirstValue("key2")); + assertEquals("value1", server.getContext().getParameters().getFirstValue("key1")); + assertEquals("value2", server.getContext().getParameters().getFirstValue("key2")); } private ClassPathXmlApplicationContext ctx; @@ -63,5 +60,4 @@ void cleanUp() { Engine.clearThreadLocalVariables(); ctx.close(); } - } diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/OrderResource.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/OrderResource.java index f757a80641..6a1660f7df 100644 --- a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/OrderResource.java +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/OrderResource.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring.resources; import org.restlet.resource.Get; @@ -24,5 +23,4 @@ public void doInit() { public String toString() { return "Order \"" + this.orderId + "\" for user \"" + this.userName + "\""; } - } diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/OrdersResource.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/OrdersResource.java index bac7783487..c5ddac3221 100644 --- a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/OrdersResource.java +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/OrdersResource.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring.resources; import org.restlet.resource.Get; @@ -17,5 +16,4 @@ public class OrdersResource extends UserResource { public String toString() { return "Orders of user \"" + this.userName + "\""; } - } diff --git a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/UserResource.java b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/UserResource.java index 5df583c50d..0acae60057 100644 --- a/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/UserResource.java +++ b/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/resources/UserResource.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.spring.resources; import org.restlet.resource.Get; diff --git a/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/TemplateFilter.java b/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/TemplateFilter.java index 672bc10baa..f1c05f4d4c 100644 --- a/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/TemplateFilter.java +++ b/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/TemplateFilter.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.thymeleaf; import java.util.Locale; import java.util.Map; - import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -21,22 +19,21 @@ import org.restlet.util.Resolver; /** - * Filters response's entity and wraps it with a Thymeleaf's template - * representation. By default, the template representation provides a data model - * based on the request and response objects. In order for the wrapping to - * happen, the representations must have the {@link #THYMELEAF} encoding + * Filters response's entity and wraps it with a Thymeleaf's template representation. By default, + * the template representation provides a data model based on the request and response objects. In + * order for the wrapping to happen, the representations must have the {@link #THYMELEAF} encoding * set.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. * * @author Grzegorz Godlewski */ public abstract class TemplateFilter extends Filter { - private static final Encoding THYMELEAF = new Encoding("thymeleaf", - "Thymeleaf templated representation"); + private static final Encoding THYMELEAF = + new Encoding("thymeleaf", "Thymeleaf templated representation"); /** The template's data model as a map. */ private volatile Map mapDataModel; @@ -44,32 +41,27 @@ public abstract class TemplateFilter extends Filter { /** The template's data model as a resolver. */ private volatile Resolver resolverDataModel; - /** - * Constructor. - */ - public TemplateFilter() { + /** Constructor. */ + protected TemplateFilter() { super(); } /** * Constructor. * - * @param context - * The context. + * @param context The context. */ - public TemplateFilter(Context context) { + protected TemplateFilter(Context context) { super(context); } /** * Constructor. * - * @param context - * The context. - * @param next - * The next Restlet. + * @param context The context. + * @param next The next Restlet. */ - public TemplateFilter(Context context, Restlet next) { + protected TemplateFilter(Context context, Restlet next) { super(context, next); this.mapDataModel = null; this.resolverDataModel = null; @@ -78,15 +70,11 @@ public TemplateFilter(Context context, Restlet next) { /** * Constructor. * - * @param context - * The context. - * @param next - * The next Restlet. - * @param dataModel - * The filter's data model. + * @param context The context. + * @param next The next Restlet. + * @param dataModel The filter's data model. */ - public TemplateFilter(Context context, Restlet next, - Map dataModel) { + protected TemplateFilter(Context context, Restlet next, Map dataModel) { super(context, next); this.mapDataModel = dataModel; this.resolverDataModel = null; @@ -95,15 +83,11 @@ public TemplateFilter(Context context, Restlet next, /** * Constructor. * - * @param context - * The context. - * @param next - * The next Restlet. - * @param dataModel - * The filter's data model. + * @param context The context. + * @param next The next Restlet. + * @param dataModel The filter's data model. */ - public TemplateFilter(Context context, Restlet next, - Resolver dataModel) { + protected TemplateFilter(Context context, Restlet next, Resolver dataModel) { super(context, next); this.mapDataModel = null; this.resolverDataModel = dataModel; @@ -113,12 +97,13 @@ public TemplateFilter(Context context, Restlet next, protected void afterHandle(Request request, Response response) { if (response.isEntityAvailable() && response.getEntity().getEncodings().contains(THYMELEAF)) { - final TemplateRepresentation representation = new TemplateRepresentation( - (TemplateRepresentation) response.getEntity(), - getLocale(), response.getEntity().getMediaType()); + final TemplateRepresentation representation = + new TemplateRepresentation( + (TemplateRepresentation) response.getEntity(), + getLocale(), + response.getEntity().getMediaType()); - if ((this.mapDataModel == null) - && (this.resolverDataModel == null)) { + if ((this.mapDataModel == null) && (this.resolverDataModel == null)) { representation.setDataModel(request, response); } else { if (this.mapDataModel == null) { diff --git a/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/TemplateRepresentation.java b/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/TemplateRepresentation.java index 17cc4b7467..7bb7eaf52f 100644 --- a/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/TemplateRepresentation.java +++ b/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/TemplateRepresentation.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.thymeleaf; +import static org.restlet.Context.getCurrent; + import java.io.IOException; import java.io.Writer; import java.util.Collections; @@ -17,7 +18,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; - import org.restlet.Request; import org.restlet.Response; import org.restlet.data.Form; @@ -32,16 +32,15 @@ import org.thymeleaf.util.Validate; /** - * Thymeleaf template representation. Useful for dynamic string-based - * representations. - * + * Thymeleaf template representation. Useful for dynamic string-based representations. + * * @author Grzegorz Godlewski */ public class TemplateRepresentation extends WriterRepresentation { /** * Context that leverages an instance of {@link Resolver}. - * + * * @author Grzegorz Godlewski */ private static class ResolverContext implements IContext { @@ -53,11 +52,9 @@ private static class ResolverContext implements IContext { /** * Constructor. - * - * @param locale - * The Locale. - * @param resolver - * The Resolver instance. + * + * @param locale The Locale. + * @param resolver The Resolver instance. */ public ResolverContext(Locale locale, Resolver resolver) { this.locale = locale; @@ -65,8 +62,7 @@ public ResolverContext(Locale locale, Resolver resolver) { } public final void addContextExecutionInfo(final String templateName) { - Validate.notEmpty(templateName, - "Template name cannot be null or empty"); + Validate.notEmpty(templateName, "Template name cannot be null or empty"); } public Locale getLocale() { @@ -87,14 +83,12 @@ public Set getVariableNames() { public Object getVariable(final String key) { return resolver.resolve(key); } - } /** - * Returns a new instance of {@link TemplateEngine} based by default on a - * {@link ITemplateResolver} returned by calling - * {@link #createTemplateResolver()}. - * + * Returns a new instance of {@link TemplateEngine} based by default on a {@link + * ITemplateResolver} returned by calling {@link #createTemplateResolver()}. + * * @return A new instance of {@link TemplateEngine} */ public static TemplateEngine createTemplateEngine() { @@ -102,10 +96,9 @@ public static TemplateEngine createTemplateEngine() { } /** - * Returns a new instance of {@link TemplateEngine} based by default on a - * {@link ITemplateResolver} returned by calling - * {@link #createTemplateResolver()}. - * + * Returns a new instance of {@link TemplateEngine} based by default on a {@link + * ITemplateResolver} returned by calling {@link #createTemplateResolver()}. + * * @return A new instance of {@link TemplateEngine} */ public static TemplateEngine createTemplateEngine(ITemplateResolver resolver) { @@ -115,10 +108,9 @@ public static TemplateEngine createTemplateEngine(ITemplateResolver resolver) { } /** - * Returns a new instance of {@link ITemplateResolver} with default - * configuration (XHTML template model, templates located inside - * "/WEB-INF/templates/", suffixed by ".html". - * + * Returns a new instance of {@link ITemplateResolver} with default configuration (XHTML + * template model, templates located inside "/WEB-INF/templates/", suffixed by ".html". + * * @return A new instance of {@link ITemplateResolver}. */ public static ITemplateResolver createTemplateResolver() { @@ -150,56 +142,49 @@ public static ITemplateResolver createTemplateResolver() { /** * Constructor. - * - * @param templateName - * The Thymeleaf template's name. The actual template is - * retrieved using the Thymeleaf configuration. - * @param locale - * The locale of the template. - * @param dataModel - * The Thymeleaf template's data model. - * @param mediaType - * The representation's media type. + * + * @param templateName The Thymeleaf template's name. The actual template is retrieved using the + * Thymeleaf configuration. + * @param locale The locale of the template. + * @param dataModel The Thymeleaf template's data model. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(String templateName, Locale locale, - Map dataModel, MediaType mediaType) { + public TemplateRepresentation( + String templateName, + Locale locale, + Map dataModel, + MediaType mediaType) { this(templateName, createTemplateEngine(), locale, dataModel, mediaType); } /** * Constructor. - * - * @param templateName - * The Thymeleaf template's name. The full path is resolved by - * the configuration. - * @param locale - * The locale of the template. - * @param mediaType - * The representation's media type. + * + * @param templateName The Thymeleaf template's name. The full path is resolved by the + * configuration. + * @param locale The locale of the template. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(String templateName, Locale locale, - MediaType mediaType) { - this(templateName, locale, new ConcurrentHashMap(), - mediaType); + public TemplateRepresentation(String templateName, Locale locale, MediaType mediaType) { + this(templateName, locale, new ConcurrentHashMap<>(), mediaType); } /** * Constructor. - * - * @param templateName - * The Thymeleaf template's name. The actual template is - * retrieved using the Thymeleaf configuration. - * @param engine - * The template engine. - * @param locale - * The locale of the template. - * @param dataModel - * The Thymeleaf template's data model. - * @param mediaType - * The representation's media type. + * + * @param templateName The Thymeleaf template's name. The actual template is retrieved using the + * Thymeleaf configuration. + * @param engine The template engine. + * @param locale The locale of the template. + * @param dataModel The Thymeleaf template's data model. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(String templateName, TemplateEngine engine, - Locale locale, Map dataModel, MediaType mediaType) { + public TemplateRepresentation( + String templateName, + TemplateEngine engine, + Locale locale, + Map dataModel, + MediaType mediaType) { super(mediaType); this.locale = locale; this.engine = engine; @@ -209,52 +194,42 @@ public TemplateRepresentation(String templateName, TemplateEngine engine, /** * Constructor. - * - * @param templateName - * The Thymeleaf template's name. The full path is resolved by - * the configuration. - * @param locale - * The locale of the template - * @param mediaType - * The representation's media type. + * + * @param templateName The Thymeleaf template's name. The full path is resolved by the + * configuration. + * @param locale The locale of the template + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(String templateName, TemplateEngine engine, - Locale locale, MediaType mediaType) { - this(templateName, engine, locale, - new ConcurrentHashMap(), mediaType); + public TemplateRepresentation( + String templateName, TemplateEngine engine, Locale locale, MediaType mediaType) { + this(templateName, engine, locale, new ConcurrentHashMap<>(), mediaType); } /** * Constructor based on a Thymeleaf 'encoded' representation. - * - * @param templateRepresentation - * The representation to 'decode'. - * @param locale - * The locale of the template. - * @param mediaType - * The representation's media type. + * + * @param templateRepresentation The representation to 'decode'. + * @param locale The locale of the template. + * @param mediaType The representation's media-type. */ public TemplateRepresentation( - TemplateRepresentation templateRepresentation, Locale locale, - MediaType mediaType) { + TemplateRepresentation templateRepresentation, Locale locale, MediaType mediaType) { this(templateRepresentation, createTemplateEngine(), locale, mediaType); } /** * Constructor based on a Thymeleaf 'encoded' representation. - * - * @param templateRepresentation - * The representation to 'decode'. - * @param engine - * The template engine. - * @param locale - * The locale of the template. - * @param mediaType - * The representation's media type. + * + * @param templateRepresentation The representation to 'decode'. + * @param engine The template engine. + * @param locale The locale of the template. + * @param mediaType The representation's media-type. */ public TemplateRepresentation( TemplateRepresentation templateRepresentation, - TemplateEngine engine, Locale locale, MediaType mediaType) { + TemplateEngine engine, + Locale locale, + MediaType mediaType) { super(mediaType); this.locale = locale; this.engine = engine; @@ -263,7 +238,7 @@ public TemplateRepresentation( /** * Returns the representation's locale. - * + * * @return The representation's locale. */ public Locale getLocale() { @@ -272,7 +247,7 @@ public Locale getLocale() { /** * Returns the template's name. - * + * * @return The template's name. */ public String getTemplateName() { @@ -281,9 +256,8 @@ public String getTemplateName() { /** * Sets the Thymeleaf context. - * - * @param context - * The Thymeleaf context + * + * @param context The Thymeleaf context */ protected void setContext(IContext context) { this.context = context; @@ -291,9 +265,8 @@ protected void setContext(IContext context) { /** * Sets the template's data model. - * - * @param dataModel - * The template's data model. + * + * @param dataModel The template's data model. */ public void setDataModel(Map dataModel) { Context ctx = new Context(locale); @@ -302,16 +275,13 @@ public void setDataModel(Map dataModel) { } /** - * Sets the template's data model from a request/response pair. This default - * implementation uses a Resolver. - * + * Sets the template's data model from a request/response pair. This default implementation uses + * a Resolver. + * * @see Resolver * @see Resolver#createResolver(Request, Response) - * - * @param request - * The request where data are located. - * @param response - * The response where data are located. + * @param request The request where data are located. + * @param response The response where data are located. */ public void setDataModel(Request request, Response response) { Form form = new Form(request.getEntity()); @@ -322,9 +292,8 @@ public void setDataModel(Request request, Response response) { /** * Sets the template's data model from a resolver. - * - * @param resolver - * The resolver. + * + * @param resolver The resolver. */ public void setDataModel(Resolver resolver) { setContext(new ResolverContext(locale, resolver)); @@ -332,9 +301,8 @@ public void setDataModel(Resolver resolver) { /** * Sets the template's name. - * - * @param templateName - * The template's name. + * + * @param templateName The template's name. */ public void setTemplateName(String templateName) { this.templateName = templateName; @@ -342,9 +310,8 @@ public void setTemplateName(String templateName) { /** * Writes the datum as a stream of characters. - * - * @param writer - * The writer to use when writing. + * + * @param writer The writer to use when writing. */ @Override public void write(Writer writer) throws IOException { @@ -354,18 +321,13 @@ public void write(Writer writer) throws IOException { engine.process(templateName, context, writer); } catch (Exception e) { - final org.restlet.Context context = org.restlet.Context - .getCurrent(); + final var currentContext = getCurrent(); - if (context != null) { - context.getLogger().log(Level.WARNING, - "Unable to process the template", e); + if (currentContext != null) { + currentContext.getLogger().log(Level.WARNING, "Unable to process the template", e); } - e.printStackTrace(); - - throw new IOException("Template processing error. " - + e.getMessage()); + throw new IOException("Template processing error. " + e.getMessage()); } } } diff --git a/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/ThymeleafConverter.java b/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/ThymeleafConverter.java index 96ffd6e803..c7d3596925 100644 --- a/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/ThymeleafConverter.java +++ b/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/ThymeleafConverter.java @@ -1,18 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.thymeleaf; -import java.io.IOException; import java.util.List; import java.util.Locale; - import org.restlet.data.MediaType; import org.restlet.data.Preference; import org.restlet.engine.converter.ConverterHelper; @@ -23,19 +20,14 @@ import org.thymeleaf.templateresource.ITemplateResource; /** - * Converter between the Thymeleaf Template objects and Representations. The - * adjoined data model is based on the request and response objects. - * + * Converter between the Thymeleaf Template objects and Representations. The adjoined data model is + * based on the request and response objects. + * * @author Grzegorz Godlewski */ public class ThymeleafConverter extends ConverterHelper { - private static final VariantInfo VARIANT_ALL = new VariantInfo( - MediaType.ALL); - - private Locale getLocale(Resource resource) { - return Locale.getDefault(); - } + private static final VariantInfo VARIANT_ALL = new VariantInfo(MediaType.ALL); @Override public List> getObjectClasses(Variant source) { @@ -63,27 +55,24 @@ public float score(Object source, Variant target, Resource resource) { } @Override - public float score(Representation source, Class target, - Resource resource) { + public float score(Representation source, Class target, Resource resource) { return -1.0f; } @Override - public T toObject(Representation source, Class target, - Resource resource) throws IOException { + public T toObject(Representation source, Class target, Resource resource) { return null; } @Override - public Representation toRepresentation(Object source, Variant target, - Resource resource) throws IOException { + public Representation toRepresentation(Object source, Variant target, Resource resource) { - if (source instanceof ITemplateResource) { - Locale locale = getLocale(resource); + if (source instanceof ITemplateResource iTemplateResource) { + Locale locale = Locale.getDefault(); - TemplateRepresentation tr = new TemplateRepresentation( - ((ITemplateResource) source).getBaseName(), locale, - target.getMediaType()); + TemplateRepresentation tr = + new TemplateRepresentation( + iTemplateResource.getBaseName(), locale, target.getMediaType()); tr.setDataModel(resource.getRequest(), resource.getResponse()); return tr; } @@ -92,8 +81,7 @@ public Representation toRepresentation(Object source, Variant target, } @Override - public void updatePreferences(List> preferences, - Class entity) { + public void updatePreferences(List> preferences, Class entity) { if (ITemplateResource.class.isAssignableFrom(entity)) { updatePreferences(preferences, MediaType.ALL, 1.0F); } diff --git a/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/package-info.java b/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/package-info.java new file mode 100644 index 0000000000..87d07f9321 --- /dev/null +++ b/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/package-info.java @@ -0,0 +1,10 @@ +/** + * Integration with Thymeleaf 3.0. Thymeleaf is a "template engine"; a generic tool to generate text + * output (anything from HTML to autogenerated source code) based on templates. + * + * @since Restlet 2.2 + * @see Thymelead library + * @see User + * Guide - Thymeleaf extension + */ +package org.restlet.ext.thymeleaf; diff --git a/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/package.html b/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/package.html deleted file mode 100644 index 2318a11ea5..0000000000 --- a/org.restlet.ext.thymeleaf/src/main/java/org/restlet/ext/thymeleaf/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - -Integration with Thymeleaf 3.0. Thymeleaf is a "template engine"; a generic tool to generate text output (anything from HTML to autogenerated source code) based on templates. - -@since Restlet 2.2 -@see Thymelead library -@see User Guide - Thymeleaf extension - - \ No newline at end of file diff --git a/org.restlet.ext.thymeleaf/src/test/java/org/restlet/ext/thymeleaf/ThymeleafTestCase.java b/org.restlet.ext.thymeleaf/src/test/java/org/restlet/ext/thymeleaf/ThymeleafTestCase.java index e1ab55a270..5dcb449a8e 100644 --- a/org.restlet.ext.thymeleaf/src/test/java/org/restlet/ext/thymeleaf/ThymeleafTestCase.java +++ b/org.restlet.ext.thymeleaf/src/test/java/org/restlet/ext/thymeleaf/ThymeleafTestCase.java @@ -1,32 +1,30 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.thymeleaf; -import org.junit.jupiter.api.Test; -import org.restlet.data.MediaType; -import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Locale; import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.restlet.data.MediaType; +import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; /** * Unit test for the Thymeleaf extension. - * + * * @author Thierry Boileau */ -public class ThymeleafTestCase { +class ThymeleafTestCase { @Test - public void testTemplate() throws Exception { + void testTemplate() throws Exception { ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); templateResolver.setPrefix("org/restlet/ext/thymeleaf/"); templateResolver.setSuffix(".html"); @@ -34,10 +32,14 @@ public void testTemplate() throws Exception { final Map map = Map.of("welcome", "Hello, world"); - final String result = new TemplateRepresentation("test", - TemplateRepresentation.createTemplateEngine(templateResolver), - Locale.getDefault(), map, MediaType.TEXT_PLAIN).getText(); + final String result = + new TemplateRepresentation( + "test", + TemplateRepresentation.createTemplateEngine(templateResolver), + Locale.getDefault(), + map, + MediaType.TEXT_PLAIN) + .getText(); assertTrue(result.contains("Hello, world")); } - } diff --git a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/RepresentationResourceLoader.java b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/RepresentationResourceLoader.java index 2b5a57f35b..b25b0cee91 100644 --- a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/RepresentationResourceLoader.java +++ b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/RepresentationResourceLoader.java @@ -1,19 +1,17 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.velocity; import java.io.IOException; import java.io.Reader; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; - import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.runtime.resource.Resource; import org.apache.velocity.runtime.resource.loader.ResourceLoader; @@ -21,19 +19,18 @@ import org.restlet.representation.Representation; /** - * Velocity resource loader based on a static map of representations or on a - * default representation. - * + * Velocity resource loader based on a static map of representations or on a default representation. + * * @author Jerome Louvel */ public class RepresentationResourceLoader extends ResourceLoader { /** The cache of template representations. */ - private static final Map store = new ConcurrentHashMap(); + private static final Map store = new ConcurrentHashMap<>(); /** * Returns the cache of template representations. - * + * * @return The cache of template representations. */ public static Map getStore() { @@ -45,9 +42,8 @@ public static Map getStore() { /** * Constructor. - * - * @param defaultRepresentation - * The default representation to use. + * + * @param defaultRepresentation The default representation to use. */ public RepresentationResourceLoader(Representation defaultRepresentation) { this.defaultRepresentation = defaultRepresentation; @@ -65,7 +61,8 @@ public boolean isSourceModified(Resource resource) { } @Override - public Reader getResourceReader(String source, String encoding) throws ResourceNotFoundException { + public Reader getResourceReader(String source, String encoding) + throws ResourceNotFoundException { try { Representation resultRepresentation = getStore().get(source); @@ -85,6 +82,6 @@ public Reader getResourceReader(String source, String encoding) throws ResourceN @Override public void init(ExtProperties configuration) { + // No-op } - } diff --git a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/TemplateFilter.java b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/TemplateFilter.java index 5285715179..f570f01e21 100644 --- a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/TemplateFilter.java +++ b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/TemplateFilter.java @@ -1,17 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.velocity; -import java.io.IOException; import java.util.Map; - import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; import org.restlet.Context; @@ -24,16 +21,15 @@ import org.restlet.util.Resolver; /** - * Filter response's entity and wrap it with a Velocity's template - * representation. By default, the template representation provides a data model - * based on the request and response objects. In order for the wrapping to - * happen, the representations must have the {@link Encoding#VELOCITY} encoding - * set.
+ * Filter the response's entity and wrap it with a Velocity's template representation. By default, + * the template representation provides a data model based on the request and response objects. In + * order for the wrapping to happen, the representations must have the {@link Encoding#VELOCITY} + * encoding set.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Thierry Boileau */ public class TemplateFilter extends Filter { @@ -44,18 +40,15 @@ public class TemplateFilter extends Filter { /** The template's data model as a resolver. */ private volatile Resolver resolverDataModel; - /** - * Constructor. - */ + /** Constructor. */ public TemplateFilter() { super(); } /** * Constructor. - * - * @param context - * The context. + * + * @param context The context. */ public TemplateFilter(Context context) { super(context); @@ -63,11 +56,9 @@ public TemplateFilter(Context context) { /** * Constructor. - * - * @param context - * The context. - * @param next - * The next Restlet. + * + * @param context The context. + * @param next The next Restlet. */ public TemplateFilter(Context context, Restlet next) { super(context, next); @@ -77,16 +68,12 @@ public TemplateFilter(Context context, Restlet next) { /** * Constructor. - * - * @param context - * The context. - * @param next - * The next Restlet. - * @param dataModel - * The filter's data model. + * + * @param context The context. + * @param next The next Restlet. + * @param dataModel The filter's data model. */ - public TemplateFilter(Context context, Restlet next, - Map dataModel) { + public TemplateFilter(Context context, Restlet next, Map dataModel) { super(context, next); this.mapDataModel = dataModel; this.resolverDataModel = null; @@ -94,16 +81,12 @@ public TemplateFilter(Context context, Restlet next, /** * Constructor. - * - * @param context - * The context. - * @param next - * The next Restlet. - * @param dataModel - * The filter's data model. + * + * @param context The context. + * @param next The next Restlet. + * @param dataModel The filter's data model. */ - public TemplateFilter(Context context, Restlet next, - Resolver dataModel) { + public TemplateFilter(Context context, Restlet next, Resolver dataModel) { super(context, next); this.mapDataModel = null; this.resolverDataModel = dataModel; @@ -112,15 +95,13 @@ public TemplateFilter(Context context, Restlet next, @Override protected void afterHandle(Request request, Response response) { if (response.isEntityAvailable() - && response.getEntity().getEncodings() - .contains(Encoding.VELOCITY)) { + && response.getEntity().getEncodings().contains(Encoding.VELOCITY)) { try { - final TemplateRepresentation representation = new TemplateRepresentation( - response.getEntity(), response.getEntity() - .getMediaType()); + final TemplateRepresentation representation = + new TemplateRepresentation( + response.getEntity(), response.getEntity().getMediaType()); - if ((this.mapDataModel == null) - && (this.resolverDataModel == null)) { + if ((this.mapDataModel == null) && (this.resolverDataModel == null)) { representation.setDataModel(request, response); } else { if (this.mapDataModel == null) { @@ -135,8 +116,6 @@ protected void afterHandle(Request request, Response response) { response.setStatus(Status.CLIENT_ERROR_NOT_FOUND, e); } catch (ParseErrorException e) { response.setStatus(Status.SERVER_ERROR_INTERNAL, e); - } catch (IOException e) { - response.setStatus(Status.SERVER_ERROR_INTERNAL, e); } } } diff --git a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/TemplateRepresentation.java b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/TemplateRepresentation.java index 08993733b3..9ae58df7f6 100644 --- a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/TemplateRepresentation.java +++ b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/TemplateRepresentation.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.velocity; import java.io.IOException; @@ -15,7 +14,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; - import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; @@ -32,15 +30,14 @@ import org.restlet.util.Resolver; /** - * Velocity template representation. Useful for dynamic string-based - * representations. - * + * Velocity template representation. Useful for dynamic string-based representations. + * * @author Jerome Louvel */ public class TemplateRepresentation extends WriterRepresentation { /** * Velocity context based on a Resolver. - * + * * @see Resolver */ private static class ResolverContext implements org.apache.velocity.context.Context { @@ -49,9 +46,8 @@ private static class ResolverContext implements org.apache.velocity.context.Cont /** * Constructor. - * - * @param resolver - * The resolver. + * + * @param resolver The resolver. */ public ResolverContext(Resolver resolver) { super(); @@ -64,20 +60,15 @@ public boolean containsKey(String key) { } /** - * Gets the value corresponding to the provided key from the context. - * - * @Param key The name of the desired value. - * @Return The value corresponding to the provided key. + * Gets the value corresponding to the provided key from the context. @Param key The name of + * the desired value. @Return The value corresponding to the provided key. */ public Object get(String key) { return this.resolver.resolve(key); } /** - * Returns null since a resolver does not know by advance the whole - * values. - * - * @Return null. + * Returns null since a resolver does not know by advance the whole values. @Return null. */ @Override public String[] getKeys() { @@ -86,11 +77,9 @@ public String[] getKeys() { /** * Returns null since a resolver as a data model cannot be updated. - * - * @param key - * The name to key the provided value with. - * @param value - * The corresponding value. + * + * @param key The name to key the provided value with. + * @param value The corresponding value. * @return null. */ public Object put(String key, Object value) { @@ -99,16 +88,14 @@ public Object put(String key, Object value) { /** * Does nothing since resolver as a data model cannot be updated. - * - * @param value - * The name of the value to remove. + * + * @param key The name of the value to remove. * @return null. */ @Override public Object remove(String key) { return null; } - } /** The template's data model. */ @@ -125,124 +112,108 @@ public Object remove(String key) { /** * Constructor based on a Velocity 'encoded' representation. - * - * @param templateRepresentation - * The representation to 'decode'. - * @param dataModel - * The Velocity template's data model. - * @param mediaType - * The representation's media type. - * @throws IOException + * + * @param templateRepresentation The representation to 'decode'. + * @param dataModel The Velocity template's data model. + * @param mediaType The representation's media-type. * @throws ParseErrorException * @throws ResourceNotFoundException */ - public TemplateRepresentation(Representation templateRepresentation, - Map dataModel, MediaType mediaType) - throws ResourceNotFoundException, ParseErrorException, IOException { + public TemplateRepresentation( + Representation templateRepresentation, + Map dataModel, + MediaType mediaType) + throws ResourceNotFoundException, ParseErrorException { super(mediaType); setDataModel(dataModel); this.engine = null; this.template = new Template(); - CharacterSet charSet = (templateRepresentation.getCharacterSet() != null) ? templateRepresentation - .getCharacterSet() : CharacterSet.DEFAULT; + CharacterSet charSet = + (templateRepresentation.getCharacterSet() != null) + ? templateRepresentation.getCharacterSet() + : CharacterSet.DEFAULT; this.template.setEncoding(charSet.getName()); if (templateRepresentation.getModificationDate() != null) { - this.template.setLastModified(templateRepresentation - .getModificationDate().getTime()); + this.template.setLastModified(templateRepresentation.getModificationDate().getTime()); } this.template.setName("org.restlet.resource.representation"); this.template.setRuntimeServices(RuntimeSingleton.getRuntimeServices()); - this.template.setResourceLoader(new RepresentationResourceLoader( - templateRepresentation)); + this.template.setResourceLoader(new RepresentationResourceLoader(templateRepresentation)); this.template.process(); this.templateName = null; } /** * Constructor based on a Velocity 'encoded' representation. - * - * @param templateRepresentation - * The representation to 'decode'. - * @param mediaType - * The representation's media type. - * @throws IOException + * + * @param templateRepresentation The representation to 'decode'. + * @param mediaType The representation's media-type. * @throws ParseErrorException * @throws ResourceNotFoundException */ - public TemplateRepresentation(Representation templateRepresentation, - MediaType mediaType) throws ResourceNotFoundException, - ParseErrorException, IOException { + public TemplateRepresentation(Representation templateRepresentation, MediaType mediaType) + throws ResourceNotFoundException, ParseErrorException { super(mediaType); this.engine = null; this.template = new Template(); - CharacterSet charSet = (templateRepresentation.getCharacterSet() != null) ? templateRepresentation - .getCharacterSet() : CharacterSet.DEFAULT; + CharacterSet charSet = + (templateRepresentation.getCharacterSet() != null) + ? templateRepresentation.getCharacterSet() + : CharacterSet.DEFAULT; this.template.setEncoding(charSet.getName()); - this.template.setLastModified((templateRepresentation - .getModificationDate() == null) ? new Date().getTime() - : templateRepresentation.getModificationDate().getTime()); + this.template.setLastModified( + (templateRepresentation.getModificationDate() == null) + ? new Date().getTime() + : templateRepresentation.getModificationDate().getTime()); this.template.setName("org.restlet.resource.representation"); this.template.setRuntimeServices(RuntimeSingleton.getRuntimeServices()); - this.template.setResourceLoader(new RepresentationResourceLoader( - templateRepresentation)); + this.template.setResourceLoader(new RepresentationResourceLoader(templateRepresentation)); this.template.process(); this.templateName = null; } /** * Constructor. - * - * @param templateName - * The Velocity template's name. The actual template is retrieved - * using the Velocity configuration. - * @param dataModel - * The Velocity template's data model. - * @param mediaType - * The representation's media type. + * + * @param templateName The Velocity template's name. The actual template is retrieved using the + * Velocity configuration. + * @param dataModel The Velocity template's data model. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(String templateName, - Map dataModel, MediaType mediaType) { + public TemplateRepresentation( + String templateName, Map dataModel, MediaType mediaType) { super(mediaType); - try { - setDataModel(dataModel); - this.engine = new VelocityEngine(); - this.template = null; - this.templateName = templateName; - } catch (Exception e) { - e.printStackTrace(); - } + setDataModel(dataModel); + this.engine = new VelocityEngine(); + this.template = null; + this.templateName = templateName; } /** * Constructor. - * - * @param templateName - * The Velocity template's name. The full path is resolved by the - * configuration. - * @param mediaType - * The representation's media type. + * + * @param templateName The Velocity template's name. The full path is resolved by the + * configuration. + * @param mediaType The representation's media-type. */ public TemplateRepresentation(String templateName, MediaType mediaType) { - this(templateName, new ConcurrentHashMap(), mediaType); + this(templateName, new ConcurrentHashMap<>(), mediaType); } /** * Constructor. - * - * @param template - * The Velocity template. - * @param dataModel - * The Velocity template's data model. - * @param mediaType - * The representation's media type. + * + * @param template The Velocity template. + * @param dataModel The Velocity template's data model. + * @param mediaType The representation's media-type. */ - public TemplateRepresentation(Template template, - Map dataModel, MediaType mediaType) { + public TemplateRepresentation( + Template template, Map dataModel, MediaType mediaType) { super(mediaType); setDataModel(dataModel); this.engine = null; @@ -252,11 +223,9 @@ public TemplateRepresentation(Template template, /** * Constructor. - * - * @param template - * The Velocity template. - * @param mediaType - * The representation's media type. + * + * @param template The Velocity template. + * @param mediaType The representation's media-type. */ public TemplateRepresentation(Template template, MediaType mediaType) { super(mediaType); @@ -267,7 +236,7 @@ public TemplateRepresentation(Template template, MediaType mediaType) { /** * Returns the Velocity context. - * + * * @return The Velocity context. */ private org.apache.velocity.context.Context getContext() { @@ -276,7 +245,7 @@ private org.apache.velocity.context.Context getContext() { /** * Returns the Velocity engine. - * + * * @return The Velocity engine. */ public VelocityEngine getEngine() { @@ -285,22 +254,19 @@ public VelocityEngine getEngine() { /** * Returns the Velocity template. - * + * * @return The Velocity template. */ public Template getTemplate() { - if (this.template == null) { - if (this.templateName != null) { - try { - getEngine().init(); - this.template = getEngine().getTemplate(this.templateName); - } catch (Exception e) { - final Context context = Context.getCurrent(); - - if (context != null) { - context.getLogger().log(Level.WARNING, - "Unable to get template", e); - } + if (this.template == null && this.templateName != null) { + try { + getEngine().init(); + this.template = getEngine().getTemplate(this.templateName); + } catch (Exception e) { + final Context currentContext = Context.getCurrent(); + + if (currentContext != null) { + currentContext.getLogger().log(Level.WARNING, "Unable to get template", e); } } } @@ -310,9 +276,8 @@ public Template getTemplate() { /** * Sets the Velocity context. - * - * @param context - * The Velocity context + * + * @param context The Velocity context */ private void setContext(org.apache.velocity.context.Context context) { this.context = context; @@ -320,36 +285,30 @@ private void setContext(org.apache.velocity.context.Context context) { /** * Sets the template's data model. - * - * @param dataModel - * The template's data model. + * + * @param dataModel The template's data model. */ public void setDataModel(Map dataModel) { setContext(new VelocityContext(dataModel)); } /** - * Sets the template's data model from a request/response pair. This default - * implementation uses a Resolver. - * + * Sets the template's data model from a request/response pair. This default implementation uses + * a Resolver. + * * @see Resolver * @see Resolver#createResolver(Request, Response) - * - * @param request - * The request where data are located. - * @param response - * The response where data are located. + * @param request The request where data are located. + * @param response The response where data are located. */ public void setDataModel(Request request, Response response) { - setContext(new ResolverContext(Resolver.createResolver(request, - response))); + setContext(new ResolverContext(Resolver.createResolver(request, response))); } /** * Sets the template's data model from a resolver. - * - * @param resolver - * The resolver. + * + * @param resolver The resolver. */ public void setDataModel(Resolver resolver) { setContext(new ResolverContext(resolver)); @@ -357,9 +316,8 @@ public void setDataModel(Resolver resolver) { /** * Writes the datum as a stream of characters. - * - * @param writer - * The writer to use when writing. + * + * @param writer The writer to use when writing. */ @Override public void write(Writer writer) throws IOException { @@ -368,18 +326,13 @@ public void write(Writer writer) throws IOException { // Process the template getTemplate().merge(getContext(), writer); } catch (Exception e) { - final Context context = Context.getCurrent(); + final Context currentContext = Context.getCurrent(); - if (context != null) { - context.getLogger().log(Level.WARNING, - "Unable to process the template", e); + if (currentContext != null) { + currentContext.getLogger().log(Level.WARNING, "Unable to process the template", e); } - e.printStackTrace(); - - throw new IOException("Template processing error. " - + e.getMessage()); + throw new IOException("Template processing error. " + e.getMessage()); } } - } diff --git a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/VelocityConverter.java b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/VelocityConverter.java index 906e29d4d2..9cbdf6768f 100644 --- a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/VelocityConverter.java +++ b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/VelocityConverter.java @@ -1,17 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.velocity; -import java.io.IOException; import java.util.List; - import org.apache.velocity.Template; import org.restlet.data.MediaType; import org.restlet.data.Preference; @@ -22,9 +19,9 @@ import org.restlet.resource.Resource; /** - * Converter between the Velocity Template objects and Representations. The - * adjoined data model is based on the request and response objects. - * + * Converter between the Velocity Template objects and Representations. The adjoined data model is + * based on the request and response objects. + * * @author Thierry Boileau. */ public class VelocityConverter extends ConverterHelper { @@ -62,16 +59,15 @@ public float score(Representation source, Class target, Resource resource } @Override - public T toObject(Representation source, Class target, - Resource resource) throws IOException { + public T toObject(Representation source, Class target, Resource resource) { return null; } @Override - public Representation toRepresentation(Object source, Variant target, Resource resource) throws IOException { + public Representation toRepresentation(Object source, Variant target, Resource resource) { - if (source instanceof Template) { - TemplateRepresentation tr = new TemplateRepresentation((Template) source, target.getMediaType()); + if (source instanceof Template template) { + TemplateRepresentation tr = new TemplateRepresentation(template, target.getMediaType()); tr.setDataModel(resource.getRequest(), resource.getResponse()); return tr; } diff --git a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/package-info.java b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/package-info.java new file mode 100644 index 0000000000..d759385fe1 --- /dev/null +++ b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/package-info.java @@ -0,0 +1,10 @@ +/** + * Integration with Apache Velocity 2.1. Velocity is a "template engine"; a generic tool to generate + * text output (anything from HTML to autogenerated source code) based on templates. + * + * @since Restlet 1.0 + * @see Velocity template engine + * @see User + * Guide - Velocity extension + */ +package org.restlet.ext.velocity; diff --git a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/package.html b/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/package.html deleted file mode 100644 index 515a7065cc..0000000000 --- a/org.restlet.ext.velocity/src/main/java/org/restlet/ext/velocity/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - -Integration with Apache Velocity 2.1. Velocity is a "template engine"; a generic tool to generate text output (anything from HTML to autogenerated source code) based on templates. - -@since Restlet 1.0 -@see Velocity template engine -@see User Guide - Velocity extension - - \ No newline at end of file diff --git a/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateFilterTestCase.java b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateFilterTestCase.java index 5e3ceaf047..10a065535f 100644 --- a/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateFilterTestCase.java +++ b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/TemplateFilterTestCase.java @@ -1,41 +1,45 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.velocity; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.restlet.*; +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.data.LocalReference; import org.restlet.data.Method; import org.restlet.data.Protocol; import org.restlet.engine.Engine; import org.restlet.resource.Directory; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test case for template filters. * * @author Thierry Boileau */ -public class TemplateFilterTestCase { +class TemplateFilterTestCase { @Test - public void representationShouldBeUsedAsTemplate() throws Exception { - Request request = new Request(Method.GET,"/template.txt.vm"); + void representationShouldBeUsedAsTemplate() throws Exception { + Request request = new Request(Method.GET, "/template.txt.vm"); Response response = testApplication.handle(request); assertEquals("Method=GET/Path=/template.txt.vm", response.getEntity().getText()); } @Test - public void representationShouldNotBeUsedAsTemplate() throws Exception { + void representationShouldNotBeUsedAsTemplate() throws Exception { Request request = new Request(Method.GET, "/notATemplate.txt"); Response response = testApplication.handle(request); @@ -56,7 +60,11 @@ private static class MyVelocityApplication extends Application { @Override public Restlet createInboundRoot() { - final Directory directory = new Directory(getContext(), LocalReference.createClapReference(TemplateFilterTestCase.class.getPackage())); + final Directory directory = + new Directory( + getContext(), + LocalReference.createClapReference( + TemplateFilterTestCase.class.getPackage())); // Create a Directory that manages a local directory return new TemplateFilter(getContext(), directory); diff --git a/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/VelocityTestCase.java b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/VelocityTestCase.java index 135321e816..6e400c6016 100644 --- a/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/VelocityTestCase.java +++ b/org.restlet.ext.velocity/src/test/java/org/restlet/ext/velocity/VelocityTestCase.java @@ -1,14 +1,19 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.velocity; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; +import java.io.FileWriter; +import java.util.Map; +import java.util.TreeMap; import org.junit.jupiter.api.Test; import org.restlet.data.LocalReference; import org.restlet.data.MediaType; @@ -17,25 +22,17 @@ import org.restlet.representation.Representation; import org.restlet.resource.ClientResource; -import java.io.File; -import java.io.FileWriter; -import java.util.Map; -import java.util.TreeMap; - -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test case for the Velocity extension. - * + * * @author Jerome Louvel */ -public class VelocityTestCase { +class VelocityTestCase { @Test - public void testRepresentationTemplate() throws Exception { + void testRepresentationTemplate() throws Exception { // Create a temporary directory for the tests - File testDir = new File(System.getProperty("java.io.tmpdir"), - "VelocityTestCase"); + File testDir = new File(System.getProperty("java.io.tmpdir"), "VelocityTestCase"); testDir.mkdir(); // Create a temporary template file @@ -51,8 +48,8 @@ public void testRepresentationTemplate() throws Exception { Reference ref = LocalReference.createFileReference(testFile); ClientResource r = new ClientResource(ref); Representation templateFile = r.get(); - TemplateRepresentation tr = new TemplateRepresentation(templateFile, - map, MediaType.TEXT_PLAIN); + TemplateRepresentation tr = + new TemplateRepresentation(templateFile, map, MediaType.TEXT_PLAIN); final String result = tr.getText(); assertEquals("Value=myValue", result); @@ -62,10 +59,9 @@ public void testRepresentationTemplate() throws Exception { } @Test - public void testStandardTemplate() throws Exception { + void testStandardTemplate() throws Exception { // Create a temporary directory for the tests - final File testDir = new File(System.getProperty("java.io.tmpdir"), - "VelocityTestCase"); + final File testDir = new File(System.getProperty("java.io.tmpdir"), "VelocityTestCase"); testDir.mkdir(); // Create a temporary template file @@ -78,10 +74,9 @@ public void testStandardTemplate() throws Exception { map.put("value", "myValue"); // Standard approach - final TemplateRepresentation tr = new TemplateRepresentation( - testFile.getName(), map, MediaType.TEXT_PLAIN); - tr.getEngine().setProperty("file.resource.loader.path", - testDir.getAbsolutePath()); + final TemplateRepresentation tr = + new TemplateRepresentation(testFile.getName(), map, MediaType.TEXT_PLAIN); + tr.getEngine().setProperty("file.resource.loader.path", testDir.getAbsolutePath()); final String result = tr.getText(); assertEquals("Value=myValue", result); diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/DomRepresentation.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/DomRepresentation.java index 33633c2243..cd2e0049c3 100644 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/DomRepresentation.java +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/DomRepresentation.java @@ -1,18 +1,18 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; import java.io.IOException; import java.io.InputStream; import java.io.Writer; - +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactoryConfigurationError; import org.restlet.data.CharacterSet; import org.restlet.data.MediaType; import org.restlet.representation.Representation; @@ -22,9 +22,9 @@ import org.xml.sax.SAXException; /** - * XML representation based on a DOM document. DOM is a standard XML object - * model defined by the W3C. - * + * XML representation based on a DOM document. DOM is a standard XML object model defined by the + * W3C. + * * @author Jerome Louvel */ public class DomRepresentation extends XmlRepresentation { @@ -37,18 +37,15 @@ public class DomRepresentation extends XmlRepresentation { /** The source XML representation. */ private volatile Representation xmlRepresentation; - /** - * Default constructor. Uses the {@link MediaType#TEXT_XML} media type. - */ + /** Default constructor. Uses the {@link MediaType#TEXT_XML} media type. */ public DomRepresentation() throws IOException { this(MediaType.TEXT_XML); } /** * Constructor for an empty document. - * - * @param mediaType - * The representation's media type. + * + * @param mediaType The representation's media-type. */ public DomRepresentation(MediaType mediaType) throws IOException { super(mediaType); @@ -57,11 +54,9 @@ public DomRepresentation(MediaType mediaType) throws IOException { /** * Constructor from an existing DOM document. - * - * @param mediaType - * The representation's media type. - * @param xmlDocument - * The source DOM document. + * + * @param mediaType The representation's media-type. + * @param xmlDocument The source DOM document. */ public DomRepresentation(MediaType mediaType, Document xmlDocument) { super(mediaType); @@ -70,42 +65,35 @@ public DomRepresentation(MediaType mediaType, Document xmlDocument) { /** * Constructor. - * - * @param xmlRepresentation - * A source XML representation to parse. + * + * @param xmlRepresentation A source XML representation to parse. */ public DomRepresentation(Representation xmlRepresentation) { super((xmlRepresentation == null) ? null : xmlRepresentation.getMediaType()); - this.setAvailable((xmlRepresentation == null) ? false : xmlRepresentation.isAvailable()); + this.setAvailable(xmlRepresentation != null && xmlRepresentation.isAvailable()); this.xmlRepresentation = xmlRepresentation; } /** - * Creates a new JAXP Transformer object that will be used to serialize this - * DOM. This method may be overridden to set custom properties on - * the Transformer. - * + * Creates a new JAXP Transformer object that will be used to serialize this DOM. This method + * may be overridden to set custom properties on the Transformer. + * * @return The transformer to be used for serialization. */ - protected javax.xml.transform.Transformer createTransformer() - throws IOException { + protected javax.xml.transform.Transformer createTransformer() throws IOException { try { - javax.xml.transform.Transformer transformer = javax.xml.transform.TransformerFactory - .newInstance().newTransformer(); - transformer.setOutputProperty( - javax.xml.transform.OutputKeys.METHOD, "xml"); + javax.xml.transform.Transformer transformer = + javax.xml.transform.TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(javax.xml.transform.OutputKeys.METHOD, "xml"); transformer.setOutputProperty( - javax.xml.transform.OutputKeys.INDENT, - isIndenting() ? "yes" : "no"); + javax.xml.transform.OutputKeys.INDENT, isIndenting() ? "yes" : "no"); if (getCharacterSet() != null) { transformer.setOutputProperty( - javax.xml.transform.OutputKeys.ENCODING, - getCharacterSet().getName()); + javax.xml.transform.OutputKeys.ENCODING, getCharacterSet().getName()); } else { transformer.setOutputProperty( - javax.xml.transform.OutputKeys.ENCODING, - CharacterSet.ISO_8859_1.getName()); + javax.xml.transform.OutputKeys.ENCODING, CharacterSet.ISO_8859_1.getName()); } DocumentType docType = getDocument().getDoctype(); @@ -126,16 +114,15 @@ protected javax.xml.transform.Transformer createTransformer() return transformer; } catch (javax.xml.transform.TransformerConfigurationException tce) { - throw new IOException("Couldn't write the XML representation: " - + tce.getMessage()); + throw new IOException("Couldn't write the XML representation: " + tce.getMessage()); } } /** - * Returns the wrapped DOM document. If no document is defined yet, it - * attempts to parse the XML representation eventually given at construction - * time. Otherwise, it just creates a new document. - * + * Returns the wrapped DOM document. If no document is defined yet, it attempts to parse the XML + * representation eventually given at construction time. Otherwise, it just creates a new + * document. + * * @return The wrapped DOM document. */ @Override @@ -145,7 +132,8 @@ public Document getDocument() throws IOException { try { this.document = getDocumentBuilder().parse(getInputSource()); } catch (SAXException se) { - throw new IOException("Couldn't read the XML representation. " + se.getMessage()); + throw new IOException( + "Couldn't read the XML representation. " + se.getMessage()); } } else { this.document = getDocumentBuilder().newDocument(); @@ -157,7 +145,7 @@ public Document getDocument() throws IOException { /** * Returns a DOM source. - * + * * @return A DOM source. */ @Override @@ -175,7 +163,7 @@ public InputSource getInputSource() throws IOException { /** * Indicates if the XML serialization should be indented. False by default. - * + * * @return True if the XML serialization should be indented. */ public boolean isIndenting() { @@ -183,8 +171,8 @@ public boolean isIndenting() { } /** - * Releases the wrapped DOM document and the source XML representation if - * they have been defined. + * Releases the wrapped DOM document and the source XML representation if they have been + * defined. */ @Override public void release() { @@ -199,9 +187,8 @@ public void release() { /** * Sets the wrapped DOM document. - * - * @param dom - * The wrapped DOM document. + * + * @param dom The wrapped DOM document. */ public void setDocument(Document dom) { this.document = dom; @@ -209,9 +196,8 @@ public void setDocument(Document dom) { /** * Indicates if the XML serialization should be indented. - * - * @param indenting - * True if the XML serialization should be indented. + * + * @param indenting True if the XML serialization should be indented. */ public void setIndenting(boolean indenting) { this.indenting = indenting; @@ -222,19 +208,12 @@ public void write(Writer writer) throws IOException { try { if (getDocument() != null) { final javax.xml.transform.Transformer transformer = createTransformer(); - transformer.transform(new javax.xml.transform.dom.DOMSource( - getDocument()), + transformer.transform( + new javax.xml.transform.dom.DOMSource(getDocument()), new javax.xml.transform.stream.StreamResult(writer)); } - } catch (javax.xml.transform.TransformerConfigurationException tce) { - throw new IOException("Couldn't write the XML representation: " - + tce.getMessage()); - } catch (javax.xml.transform.TransformerException te) { - throw new IOException("Couldn't write the XML representation: " - + te.getMessage()); - } catch (javax.xml.transform.TransformerFactoryConfigurationError tfce) { - throw new IOException("Couldn't write the XML representation: " - + tfce.getMessage()); + } catch (TransformerException | TransformerFactoryConfigurationError te) { + throw new IOException("Couldn't write the XML representation: " + te.getMessage()); } } } diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/NodeList.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/NodeList.java index 1549d7a77a..7700c89569 100644 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/NodeList.java +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/NodeList.java @@ -1,35 +1,30 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; import java.util.AbstractList; - import org.w3c.dom.Node; /** - * DOM nodes set that implements the standard List interface for easier - * iteration. - * + * DOM nodes set that implements the standard List interface for easier iteration. + * * @author Jerome Louvel */ -public class NodeList extends AbstractList implements - org.w3c.dom.NodeList { +public class NodeList extends AbstractList implements org.w3c.dom.NodeList { /** The wrapped node list. */ private volatile org.w3c.dom.NodeList nodes; /** * Constructor. - * - * @param nodes - * The node list to wrap. + * + * @param nodes The node list to wrap. */ public NodeList(org.w3c.dom.NodeList nodes) { this.nodes = nodes; @@ -40,16 +35,12 @@ public Node get(int index) { return this.nodes.item(index); } - /** - * {@inheritDoc org.w3c.dom.NodeList#getLength()} - */ + /** {@inheritDoc org.w3c.dom.NodeList#getLength()} */ public int getLength() { return this.nodes.getLength(); } - /** - * {@inheritDoc org.w3c.dom.NodeList#item(int)} - */ + /** {@inheritDoc org.w3c.dom.NodeList#item(int)} */ public Node item(int index) { return this.nodes.item(index); } @@ -58,5 +49,4 @@ public Node item(int index) { public int size() { return this.nodes.getLength(); } - } diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/SaxRepresentation.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/SaxRepresentation.java index 1d8d08d68c..5d09b68bac 100644 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/SaxRepresentation.java +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/SaxRepresentation.java @@ -1,28 +1,24 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; import java.io.IOException; import java.io.Writer; - import javax.xml.XMLConstants; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.Result; -import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXSource; - import org.restlet.data.MediaType; import org.restlet.representation.Representation; import org.w3c.dom.Document; @@ -31,32 +27,28 @@ import org.xml.sax.XMLReader; /** - * XML representation for SAX events processing. The purpose is to create a - * streamable content based on a custom Java object model instead of a neutral - * DOM tree. This domain object can then be directly modified and efficiently - * serialized at a later time.
+ * XML representation for SAX events processing. The purpose is to create a streamable content based + * on a custom Java object model instead of a neutral DOM tree. This domain object can then be + * directly modified and efficiently serialized at a later time.
*
- * Subclasses only need to override the ContentHandler methods required for the - * reading and also the write(XmlWriter writer) method when serialization is - * requested.
+ * Subclasses only need to override the ContentHandler methods required for the reading and also the + * write(XmlWriter writer) method when serialization is requested.
*
- * SECURITY WARNING: Using XML parsers configured to not prevent nor limit - * document type definition (DTD) entity resolution can expose the parser to an - * XML Entity Expansion injection attack, see + * SECURITY WARNING: Using XML parsers configured to not prevent nor limit document type definition + * (DTD) entity resolution can expose the parser to an XML Entity Expansion injection attack, see * https://github.com/restlet/restlet-framework-java/wiki/XEE-security-enhancements. - * + * * @author Jerome Louvel */ public class SaxRepresentation extends XmlRepresentation { /** - * True for turning on secure parsing XML representations; default value - * provided by system property "org.restlet.ext.xml.secureProcessing", true - * by default. + * True for turning on secure parsing XML representations; the default value provided by system + * property "org.restlet.ext.xml.secureProcessing", true by default. */ - public static final boolean XML_SECURE_PROCESSING = (System - .getProperty("org.restlet.ext.xml.secureProcessing") == null) ? true - : Boolean.getBoolean("org.restlet.ext.xml.secureProcessing"); + public static final boolean XML_SECURE_PROCESSING = + (System.getProperty("org.restlet.ext.xml.secureProcessing") == null) + || Boolean.getBoolean("org.restlet.ext.xml.secureProcessing"); /** Limits potential XML overflow attacks. */ private boolean secureProcessing; @@ -67,18 +59,15 @@ public class SaxRepresentation extends XmlRepresentation { /** The source XML representation. */ private volatile Representation xmlRepresentation; - /** - * Default constructor. Uses the {@link MediaType#TEXT_XML} media type. - */ + /** Default constructor. Uses the {@link MediaType#TEXT_XML} media type. */ public SaxRepresentation() { this(MediaType.TEXT_XML); } /** * Constructor. - * - * @param mediaType - * The representation media type. + * + * @param mediaType The representation media type. */ public SaxRepresentation(MediaType mediaType) { super(mediaType); @@ -87,26 +76,21 @@ public SaxRepresentation(MediaType mediaType) { /** * Constructor. - * - * @param mediaType - * The representation's media type. - * @param xmlDocument - * A DOM document to parse. + * + * @param mediaType The representation's media-type. + * @param xmlDocument A DOM document to parse. */ public SaxRepresentation(MediaType mediaType, Document xmlDocument) { super(mediaType); this.secureProcessing = XML_SECURE_PROCESSING; - this.source = new SAXSource( - SAXSource.sourceToInputSource(new DOMSource(xmlDocument))); + this.source = new SAXSource(SAXSource.sourceToInputSource(new DOMSource(xmlDocument))); } /** * Constructor. - * - * @param mediaType - * The representation's media type. - * @param xmlSource - * A SAX input source to parse. + * + * @param mediaType The representation's media-type. + * @param xmlSource A SAX input source to parse. */ public SaxRepresentation(MediaType mediaType, InputSource xmlSource) { super(mediaType); @@ -116,11 +100,9 @@ public SaxRepresentation(MediaType mediaType, InputSource xmlSource) { /** * Constructor. - * - * @param mediaType - * The representation's media type. - * @param xmlSource - * A JAXP source to parse. + * + * @param mediaType The representation's media-type. + * @param xmlSource A JAXP source to parse. */ public SaxRepresentation(MediaType mediaType, SAXSource xmlSource) { super(mediaType); @@ -130,33 +112,29 @@ public SaxRepresentation(MediaType mediaType, SAXSource xmlSource) { /** * Constructor. - * - * @param xmlRepresentation - * A source XML representation to parse. + * + * @param xmlRepresentation A source XML representation to parse. */ public SaxRepresentation(Representation xmlRepresentation) { - super((xmlRepresentation == null) ? null : xmlRepresentation - .getMediaType()); + super((xmlRepresentation == null) ? null : xmlRepresentation.getMediaType()); this.secureProcessing = XML_SECURE_PROCESSING; this.xmlRepresentation = xmlRepresentation; } @Override public InputSource getInputSource() throws IOException { - return (getSaxSource() == null) ? null : getSaxSource() - .getInputSource(); + return (getSaxSource() == null) ? null : getSaxSource().getInputSource(); } /** - * Returns the SAX source that can be parsed by the - * {@link #parse(ContentHandler)} method or used for an XSLT transformation. + * Returns the SAX source that can be parsed by the {@link #parse(ContentHandler)} method or + * used for an XSLT transformation. */ @Override public SAXSource getSaxSource() throws IOException { if (this.source == null && this.xmlRepresentation != null) { - if (xmlRepresentation instanceof XmlRepresentation) { - this.source = ((XmlRepresentation) xmlRepresentation) - .getSaxSource(); + if (xmlRepresentation instanceof XmlRepresentation xmlRepresentationCast) { + this.source = xmlRepresentationCast.getSaxSource(); } else { try { SAXParserFactory spf = SAXParserFactory.newInstance(); @@ -172,8 +150,7 @@ public SAXSource getSaxSource() throws IOException { } spf.setXIncludeAware(isXIncludeAware()); - spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, - isSecureProcessing()); + spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, isSecureProcessing()); spf.setFeature( "http://xml.org/sax/features/external-general-entities", isExpandingEntityRefs()); @@ -181,17 +158,17 @@ public SAXSource getSaxSource() throws IOException { "http://xml.org/sax/features/external-parameter-entities", isExpandingEntityRefs()); XMLReader xmlReader = spf.newSAXParser().getXMLReader(); - this.source = new SAXSource(xmlReader, new InputSource( - xmlRepresentation.getReader())); + this.source = + new SAXSource( + xmlReader, new InputSource(xmlRepresentation.getReader())); } catch (Exception e) { - throw new IOException( - "Unable to create customized SAX source", e); + throw new IOException("Unable to create customized SAX source", e); } } if (xmlRepresentation.getLocationRef() != null) { - this.source.setSystemId(xmlRepresentation.getLocationRef() - .getTargetRef().toString()); + this.source.setSystemId( + xmlRepresentation.getLocationRef().getTargetRef().toString()); } } @@ -200,7 +177,7 @@ public SAXSource getSaxSource() throws IOException { /** * Indicates if it limits potential XML overflow attacks. - * + * * @return True if it limits potential XML overflow attacks. */ public boolean isSecureProcessing() { @@ -209,28 +186,17 @@ public boolean isSecureProcessing() { /** * Parses the source and sends SAX events to a content handler. - * - * @param contentHandler - * The SAX content handler to use for parsing. + * + * @param contentHandler The SAX content handler to use for parsing. */ public void parse(ContentHandler contentHandler) throws IOException { if (contentHandler != null) { try { Result result = new SAXResult(contentHandler); - TransformerFactory.newInstance().newTransformer() - .transform(getSaxSource(), result); - } catch (TransformerConfigurationException tce) { + TransformerFactory.newInstance().newTransformer().transform(getSaxSource(), result); + } catch (TransformerException | TransformerFactoryConfigurationError te) { throw new IOException( - "Couldn't parse the source representation: " - + tce.getMessage(), tce); - } catch (TransformerException te) { - throw new IOException( - "Couldn't parse the source representation: " - + te.getMessage(), te); - } catch (TransformerFactoryConfigurationError tfce) { - throw new IOException( - "Couldn't parse the source representation: " - + tfce.getMessage(), tfce); + "Couldn't parse the source representation: " + te.getMessage(), te); } } else { throw new IOException( @@ -238,9 +204,7 @@ public void parse(ContentHandler contentHandler) throws IOException { } } - /** - * Releases the namespaces map. - */ + /** Releases the namespaces map. */ @Override public void release() { if (this.source != null) { @@ -254,11 +218,9 @@ public void release() { } /** - * Sets a SAX source that can be parsed by the - * {@link #parse(ContentHandler)} method. - * - * @param source - * A SAX source. + * Sets a SAX source that can be parsed by the {@link #parse(ContentHandler)} method. + * + * @param source A SAX source. */ public void setSaxSource(SAXSource source) { this.source = source; @@ -266,9 +228,8 @@ public void setSaxSource(SAXSource source) { /** * Indicates if it limits potential XML overflow attacks. - * - * @param secureProcessing - * True if it limits potential XML overflow attacks. + * + * @param secureProcessing True if it limits potential XML overflow attacks. */ public void setSecureProcessing(boolean secureProcessing) { this.secureProcessing = secureProcessing; @@ -281,13 +242,11 @@ public void write(Writer writer) throws IOException { } /** - * Writes the representation to a XML writer. The default implementation - * calls {@link #parse(ContentHandler)} using the {@link XmlWriter} - * parameter as the content handler. This behavior is intended to be - * overridden. - * - * @param writer - * The XML writer to write to. + * Writes the representation to an XML writer. The default implementation calls {@link + * #parse(ContentHandler)} using the {@link XmlWriter} parameter as the content handler. This + * behavior is intended to be overridden. + * + * @param writer The XML writer to write to. * @throws IOException */ public void write(XmlWriter writer) throws IOException { diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/TransformRepresentation.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/TransformRepresentation.java index fbe9ad58b2..c7043fcd76 100644 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/TransformRepresentation.java +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/TransformRepresentation.java @@ -1,19 +1,17 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; import java.io.IOException; import java.io.Writer; import java.util.HashMap; import java.util.Map; - import javax.xml.transform.ErrorListener; import javax.xml.transform.Result; import javax.xml.transform.Source; @@ -30,7 +28,6 @@ import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; - import org.restlet.Context; import org.restlet.ext.xml.internal.AbstractXmlReader; import org.restlet.ext.xml.internal.ContextResolver; @@ -42,78 +39,68 @@ import org.xml.sax.XMLReader; /** - * Representation able to apply an XSLT transformation. The internal JAXP - * transformer is created when the getTransformer() method is first called. So, - * if you need to specify a custom URI resolver, do it before actually using the - * representation for a transformation.
+ * Representation able to apply an XSLT transformation. The internal JAXP transformer is created + * when the getTransformer() method is first called. So, if you need to specify a custom URI + * resolver, do it before actually using the representation for a transformation.
*
- * This representation should be viewed as a wrapper representation that applies - * a transform sheet on a source representation when it is read or written out. - * Therefore, it isn't intended to be reused on different sources. For this use - * case, you should instead use the {@link org.restlet.routing.Transformer} - * filter. - * + * This representation should be viewed as a wrapper representation that applies a transform sheet + * on a source representation when it is read or written out. Therefore, it isn't intended to be + * reused on different sources. For this use case, you should instead use the {@link + * org.restlet.routing.Transformer} filter. + * * @author Jerome Louvel */ public class TransformRepresentation extends WriterRepresentation { /** - * Wraps a source representation into a {@link SAXSource}. This method can - * detect other {@link XmlRepresentation} instances to use their - * {@link XmlRepresentation#getSaxSource()} method as well as other - * {@link TransformRepresentation} instances to support transformation - * chaining. - * - * @param representation - * The source representation. + * Wraps a source representation into a {@link SAXSource}. This method can detect other {@link + * XmlRepresentation} instances to use their {@link XmlRepresentation#getSaxSource()} method as + * well as other {@link TransformRepresentation} instances to support transformation chaining. + * + * @param representation The source representation. * @return The SAX source. * @throws IOException */ - public static SAXSource toSaxSource(Representation representation) - throws IOException { + public static SAXSource toSaxSource(Representation representation) throws IOException { SAXSource result = null; - if (representation instanceof XmlRepresentation) { - result = ((XmlRepresentation) representation).getSaxSource(); - } else if (representation instanceof TransformRepresentation) { - final TransformRepresentation source = (TransformRepresentation) representation; - XMLReader reader = new AbstractXmlReader() { - - /** - * Parses the input source by sending the result event to the - * XML reader's content handler. - * - * @param input - * The input source. - */ - public void parse(InputSource input) throws IOException, - SAXException { - try { - source.getTransformer().transform( - source.getSaxSource(), - new SAXResult(getContentHandler())); - } catch (TransformerException te) { - throw new IOException("Transformer exception. " - + te.getMessage()); - } - } - - public void parse(String systemId) throws IOException, - SAXException { - throw new IllegalStateException("Not implemented"); - } - }; - - result = new SAXSource(reader, new InputSource( - representation.getReader())); - } else { + switch (representation) { + case XmlRepresentation xmlRepresentation -> result = xmlRepresentation.getSaxSource(); + case TransformRepresentation source -> { + XMLReader reader = + new AbstractXmlReader() { + + /** + * Parses the input source by sending the result event to the XML + * reader's content handler. + * + * @param input The input source. + */ + public void parse(InputSource input) throws IOException, SAXException { + try { + source.getTransformer() + .transform( + source.getSaxSource(), + new SAXResult(getContentHandler())); + } catch (TransformerException te) { + throw new IOException( + "Transformer exception. " + te.getMessage()); + } + } + + public void parse(String systemId) { + throw new IllegalStateException("Not implemented"); + } + }; + + result = new SAXSource(reader, new InputSource(representation.getReader())); + } // Prepare the source and result documents - result = new SAXSource(new InputSource(representation.getReader())); + default -> result = new SAXSource(new InputSource(representation.getReader())); } // Copy the representation's URI as an XML system ID. if (representation.getLocationRef() != null) { - result.setSystemId(representation.getLocationRef().getTargetRef() - .toString()); + result.setSystemId(representation.getLocationRef().getTargetRef().toString()); } return result; @@ -141,93 +128,77 @@ public void parse(String systemId) throws IOException, private volatile URIResolver uriResolver; /** - * Constructor. Note that a default URI resolver will be created based on - * the given context. - * - * @param context - * The parent context. - * @param source - * The source representation to transform. - * @param transformSheet - * The XSLT transform sheet to apply. + * Constructor. Note that a default URI resolver will be created based on the given context. + * + * @param context The parent context. + * @param source The source representation to transform. + * @param transformSheet The XSLT transform sheet to apply. */ - public TransformRepresentation(Context context, Representation source, - Representation transformSheet) { - this((context == null) ? null : new ContextResolver(context), source, - transformSheet); + public TransformRepresentation( + Context context, Representation source, Representation transformSheet) { + this((context == null) ? null : new ContextResolver(context), source, transformSheet); } /** * Default constructor. - * - * @param source - * The source representation to transform. - * @param transformSheet - * The XSLT transform sheet to apply. + * + * @param source The source representation to transform. + * @param transformSheet The XSLT transform sheet to apply. */ - public TransformRepresentation(Representation source, - Representation transformSheet) { + public TransformRepresentation(Representation source, Representation transformSheet) { this((URIResolver) null, source, transformSheet); } /** - * Constructor. Note that a default URI resolver will be created based on - * the given context. - * - * @param uriResolver - * The JAXP URI resolver. - * @param source - * The source representation to transform. - * @param transformSheet - * The XSLT transform sheet to apply. + * Constructor. Note that a default URI resolver will be created based on the given context. + * + * @param uriResolver The JAXP URI resolver. + * @param source The source representation to transform. + * @param transformSheet The XSLT transform sheet to apply. */ - public TransformRepresentation(URIResolver uriResolver, - Representation source, Representation transformSheet) { + public TransformRepresentation( + URIResolver uriResolver, Representation source, Representation transformSheet) { this(uriResolver, source, transformSheet, null); } /** * Constructor. - * - * @param uriResolver - * The optional JAXP URI resolver. - * @param source - * The source representation to transform. - * @param templates - * The precompiled JAXP template. + * + * @param uriResolver The optional JAXP URI resolver. + * @param source The source representation to transform. + * @param templates The precompiled JAXP template. */ - private TransformRepresentation(URIResolver uriResolver, - Representation source, Representation transformSheet, + private TransformRepresentation( + URIResolver uriResolver, + Representation source, + Representation transformSheet, Templates templates) { super(null); this.sourceRepresentation = source; this.templates = templates; this.transformSheet = transformSheet; this.uriResolver = uriResolver; - this.parameters = new HashMap(); - this.outputProperties = new HashMap(); + this.parameters = new HashMap<>(); + this.outputProperties = new HashMap<>(); this.errorListener = null; } /** * Constructor. - * - * @param uriResolver - * The optional JAXP URI resolver. - * @param source - * The source representation to transform. - * @param templates - * The precompiled JAXP template. + * + * @param uriResolver The optional JAXP URI resolver. + * @param source The source representation to transform. + * @param templates The precompiled JAXP template. */ - public TransformRepresentation(URIResolver uriResolver, - Representation source, Templates templates) { + public TransformRepresentation( + URIResolver uriResolver, Representation source, Templates templates) { this(uriResolver, source, null, templates); } /** - * Returns the transformer's error listener. Default value is null, leaving - * the original listener intact. - * + * Returns the transformer's error listener. The default value is null, leaving the original + * listener intact. + * * @return The transformer's error listener. */ public ErrorListener getErrorListener() { @@ -236,7 +207,7 @@ public ErrorListener getErrorListener() { /** * Returns the modifiable map of JAXP transformer output properties. - * + * * @return The JAXP transformer output properties. */ public Map getOutputProperties() { @@ -245,7 +216,7 @@ public Map getOutputProperties() { /** * Returns the modifiable map of JAXP transformer parameters. - * + * * @return The JAXP transformer parameters. */ public Map getParameters() { @@ -253,9 +224,9 @@ public Map getParameters() { } /** - * Returns the SAX source associated to the source representation. - * - * @return The SAX source associated to the source representation. + * Returns the SAX source associated with the source representation. + * + * @return The SAX source associated with the source representation. * @throws IOException */ public SAXSource getSaxSource() throws IOException { @@ -264,18 +235,16 @@ public SAXSource getSaxSource() throws IOException { /** * Returns the default SAX transformer factory. - * + * * @return The default SAX transformer factory. */ private SAXTransformerFactory getSaxTransformerFactory() { - SAXTransformerFactory result = (SAXTransformerFactory) TransformerFactory - .newInstance(); - return result; + return (SAXTransformerFactory) TransformerFactory.newInstance(); } /** * Returns the source representation to transform. - * + * * @return The source representation to transform. */ public Representation getSourceRepresentation() { @@ -283,42 +252,35 @@ public Representation getSourceRepresentation() { } /** - * Returns the templates to be used and reused. If no one exists, it creates - * a new one based on the transformSheet representation and on the URI - * resolver. - * + * Returns the templates to be used and reused. If no one exists, it creates a new one based on + * the transformSheet representation and on the URI resolver. + * * @return The templates to be used and reused. */ public Templates getTemplates() throws IOException { - if (this.templates == null) { - if (getTransformSheet() != null) { - try { - // Prepare the XSLT transformer documents - final StreamSource transformSource = new StreamSource( - getTransformSheet().getStream()); - - if (getTransformSheet().getLocationRef() != null) { - transformSource.setSystemId(getTransformSheet() - .getLocationRef().getTargetRef().toString()); - } - - // Create the transformer factory - final TransformerFactory transformerFactory = TransformerFactory - .newInstance(); - - // Set the URI resolver - if (getUriResolver() != null) { - transformerFactory.setURIResolver(getUriResolver()); - } - - // Create a new transformer - this.templates = transformerFactory - .newTemplates(transformSource); - } catch (TransformerConfigurationException tce) { - throw new IOException( - "Transformer configuration exception. " - + tce.getMessage()); + if (this.templates == null && getTransformSheet() != null) { + try { + // Prepare the XSLT transformer documents + final StreamSource transformSource = + new StreamSource(getTransformSheet().getStream()); + + if (getTransformSheet().getLocationRef() != null) { + transformSource.setSystemId( + getTransformSheet().getLocationRef().getTargetRef().toString()); + } + + // Create the transformer factory + final TransformerFactory transformerFactory = TransformerFactory.newInstance(); + + // Set the URI resolver + if (getUriResolver() != null) { + transformerFactory.setURIResolver(getUriResolver()); } + + // Create a new transformer + this.templates = transformerFactory.newTemplates(transformSource); + } catch (TransformerConfigurationException tce) { + throw new IOException("Transformer configuration exception. " + tce.getMessage()); } } @@ -326,19 +288,19 @@ public Templates getTemplates() throws IOException { } /** - * Returns a new transformer to be used. Creation is based on the - * {@link #getTemplates()}.newTransformer() method. - * + * Returns a new transformer to be used. Creation is based on the {@link + * #getTemplates()}.newTransformer() method. + * * @return The new transformer to be used. */ public Transformer getTransformer() throws IOException { Transformer result = null; try { - Templates templates = getTemplates(); + Templates currentTemplates = getTemplates(); - if (templates != null) { - result = templates.newTransformer(); + if (currentTemplates != null) { + result = currentTemplates.newTransformer(); if (getErrorListener() != null) { result.setErrorListener(getErrorListener()); @@ -353,39 +315,34 @@ public Transformer getTransformer() throws IOException { } for (String name : getOutputProperties().keySet()) { - result.setOutputProperty(name, - getOutputProperties().get(name)); + result.setOutputProperty(name, getOutputProperties().get(name)); } } } catch (TransformerConfigurationException tce) { - throw new IOException("Transformer configuration exception. " - + tce.getMessage()); + throw new IOException("Transformer configuration exception. " + tce.getMessage()); } catch (TransformerFactoryConfigurationError tfce) { throw new IOException( - "Transformer factory configuration exception. " - + tfce.getMessage()); + "Transformer factory configuration exception. " + tfce.getMessage()); } return result; } /** - * Returns the SAX transformer handler associated to the transform sheet. - * + * Returns the SAX transformer handler associated with the transform sheet. + * * @return The SAX transformer handler. * @throws IOException */ public TransformerHandler getTransformerHandler() throws IOException { TransformerHandler result = null; - Templates templates = getTemplates(); + Templates currentTemplates = getTemplates(); - if (templates != null) { + if (currentTemplates != null) { try { - result = getSaxTransformerFactory().newTransformerHandler( - templates); + result = getSaxTransformerFactory().newTransformerHandler(currentTemplates); } catch (TransformerConfigurationException tce) { - throw new IOException("Transformer configuration exception. " - + tce.getMessage()); + throw new IOException("Transformer configuration exception. " + tce.getMessage()); } } @@ -394,7 +351,7 @@ public TransformerHandler getTransformerHandler() throws IOException { /** * Returns the XSLT transform sheet to apply to the source representation. - * + * * @return The XSLT transform sheet to apply. */ public Representation getTransformSheet() { @@ -403,7 +360,7 @@ public Representation getTransformSheet() { /** * Returns the URI resolver. - * + * * @return The URI resolver. */ public URIResolver getUriResolver() { @@ -412,20 +369,19 @@ public URIResolver getUriResolver() { /** * Returns the SAX XML filter applying the transform sheet to its input. - * + * * @return The SAX XML filter. * @throws IOException */ public XMLFilter getXmlFilter() throws IOException { XMLFilter result = null; - final Templates templates = getTemplates(); + final Templates currentTemplates = getTemplates(); - if (templates != null) { + if (currentTemplates != null) { try { - result = getSaxTransformerFactory().newXMLFilter(templates); + result = getSaxTransformerFactory().newXMLFilter(currentTemplates); } catch (TransformerConfigurationException tce) { - throw new IOException("Transformer configuration exception. " - + tce.getMessage()); + throw new IOException("Transformer configuration exception. " + tce.getMessage()); } } @@ -433,8 +389,8 @@ public XMLFilter getXmlFilter() throws IOException { } /** - * Releases the source and transform sheet representations, the transformer - * and the URI resolver. + * Releases the source and transform sheet representations, the transformer, and the URI + * resolver. */ @Override public void release() { @@ -461,9 +417,8 @@ public void release() { /** * Sets the transformer's error listener. - * - * @param errorListener - * The transformer's error listener. + * + * @param errorListener The transformer's error listener. */ public void setErrorListener(ErrorListener errorListener) { this.errorListener = errorListener; @@ -471,9 +426,8 @@ public void setErrorListener(ErrorListener errorListener) { /** * Sets the modifiable map of JAXP transformer output properties. - * - * @param outputProperties - * The JAXP transformer output properties. + * + * @param outputProperties The JAXP transformer output properties. */ public void setOutputProperties(Map outputProperties) { this.outputProperties = outputProperties; @@ -481,9 +435,8 @@ public void setOutputProperties(Map outputProperties) { /** * Sets the JAXP transformer parameters. - * - * @param parameters - * The JAXP transformer parameters. + * + * @param parameters The JAXP transformer parameters. */ public void setParameters(Map parameters) { this.parameters = parameters; @@ -491,9 +444,8 @@ public void setParameters(Map parameters) { /** * Sets the source representation to transform. - * - * @param source - * The source representation to transform. + * + * @param source The source representation to transform. */ public void setSourceRepresentation(Representation source) { this.sourceRepresentation = source; @@ -501,9 +453,8 @@ public void setSourceRepresentation(Representation source) { /** * Sets the templates to be used and reused. - * - * @param templates - * The templates to be used and reused. + * + * @param templates The templates to be used and reused. */ public void setTemplates(Templates templates) { this.templates = templates; @@ -511,9 +462,8 @@ public void setTemplates(Templates templates) { /** * Sets the XSLT transform sheet to apply to message entities. - * - * @param transformSheet - * The XSLT transform sheet to apply to message entities. + * + * @param transformSheet The XSLT transform sheet to apply to message entities. */ public void setTransformSheet(Representation transformSheet) { this.transformSheet = transformSheet; @@ -521,9 +471,8 @@ public void setTransformSheet(Representation transformSheet) { /** * Sets the URI resolver. - * - * @param uriResolver - * The URI resolver. + * + * @param uriResolver The URI resolver. */ public void setUriResolver(URIResolver uriResolver) { this.uriResolver = uriResolver; @@ -531,35 +480,30 @@ public void setUriResolver(URIResolver uriResolver) { /** * Transforms the given JAXP source into the given result. - * - * @param source - * The JAXP source object. - * @param result - * The JAXP result object. + * + * @param source The JAXP source object. + * @param result The JAXP result object. * @throws IOException */ public void transform(Source source, Result result) throws IOException { if (getTransformer() == null) { Context.getCurrentLogger() - .warning( - "Unable to apply the transformation. No transformer found!"); + .warning("Unable to apply the transformation. No transformer found!"); } else { try { // Generates the result of the transformation getTransformer().transform(source, result); } catch (TransformerException te) { - throw new IOException("Transformer exception. " - + te.getMessage()); + throw new IOException("Transformer exception. " + te.getMessage()); } } } /** - * Writes the transformed source into the given JAXP result. The source is - * retrieved using the {@link #getSaxSource()} method. - * - * @param result - * The JAXP result object. + * Writes the transformed source into the given JAXP result. The source is retrieved using the + * {@link #getSaxSource()} method. + * + * @param result The JAXP result object. * @throws IOException */ public void write(Result result) throws IOException { @@ -567,9 +511,8 @@ public void write(Result result) throws IOException { } /** - * Writes the transformed source into the given output stream. By default, - * it leverages the {@link #write(Result)} method using a - * {@link StreamResult} object. + * Writes the transformed source into the given output stream. By default, it leverages the + * {@link #write(Result)} method using a {@link StreamResult} object. */ @Override public void write(Writer writer) throws IOException { diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/Transformer.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/Transformer.java index fb66faeb92..22003d312f 100644 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/Transformer.java +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/Transformer.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; - import org.restlet.Request; import org.restlet.Response; import org.restlet.data.CharacterSet; @@ -22,50 +20,35 @@ import org.restlet.routing.Filter; /** - * Filter that can transform XML representations by applying an XSLT transform - * sheet. It uses the {@link org.restlet.representation.TransformRepresentation} - * to actually transform the XML entities.
+ * Filter that can transform XML representations by applying an XSLT transform sheet. It uses the + * {@link org.restlet.representation.Representation} to actually transform the XML entities.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class Transformer extends Filter { - /** - * Mode that transforms request entities before their handling by the - * attached Restlet. - */ + /** Mode that transforms request entities before their handling by the attached Restlet. */ public static final int MODE_REQUEST = 1; - /** - * Mode that transforms response entities after their handling by the - * attached Restlet. - */ + /** Mode that transforms response entities after their handling by the attached Restlet. */ public static final int MODE_RESPONSE = 2; /** The transformation mode. */ private volatile int mode; - /** - * The character set of the result representation. The default value is - * null. - */ + /** The character set of the result representation. The default value is null. */ private volatile CharacterSet resultCharacterSet; - /** - * The encodings of the result representation. - */ + /** The encodings of the result representation. */ private volatile List resultEncodings; /** The languages of the result representation. */ private volatile List resultLanguages; - /** - * The media type of the result representation. MediaType.APPLICATION_XML by - * default. - */ + /** The media type of the result representation. MediaType.APPLICATION_XML by default. */ private volatile MediaType resultMediaType; /** The XSLT transform sheet to apply to message entities. */ @@ -73,11 +56,9 @@ public class Transformer extends Filter { /** * Constructor. - * - * @param mode - * The transformation mode. - * @param transformSheet - * The XSLT transform sheet to apply to message entities. + * + * @param mode The transformation mode. + * @param transformSheet The XSLT transform sheet to apply to message entities. */ public Transformer(int mode, Representation transformSheet) { this.mode = mode; @@ -103,11 +84,10 @@ protected int beforeHandle(Request request, Response response) { } /** - * Indicates if the filter can transform the given message entity. By - * default, it always returns true. - * - * @param representation - * The entity representation to test. + * Indicates if the filter can transform the given message entity. By default, it always returns + * true. + * + * @param representation The entity representation to test. * @return True if the transformation can be applied. */ protected boolean canTransform(Representation representation) { @@ -116,7 +96,7 @@ protected boolean canTransform(Representation representation) { /** * Returns the transformation mode. See MODE_* constants. - * + * * @return The transformation mode. */ public int getMode() { @@ -124,9 +104,8 @@ public int getMode() { } /** - * Returns the character set of the result representation. The default value - * is null. - * + * Returns the character set of the result representation. The default value is null. + * * @return The character set of the result representation. */ public CharacterSet getResultCharacterSet() { @@ -135,7 +114,7 @@ public CharacterSet getResultCharacterSet() { /** * Returns the modifiable list of encodings of the result representation. - * + * * @return The encoding of the result representation. */ public List getResultEncodings() { @@ -145,7 +124,7 @@ public List getResultEncodings() { synchronized (this) { re = this.resultEncodings; if (re == null) { - this.resultEncodings = re = new CopyOnWriteArrayList(); + this.resultEncodings = re = new CopyOnWriteArrayList<>(); } } } @@ -154,7 +133,7 @@ public List getResultEncodings() { /** * Returns the modifiable list of languages of the result representation. - * + * * @return The language of the result representation. */ public List getResultLanguages() { @@ -164,7 +143,7 @@ public List getResultLanguages() { synchronized (this) { v = this.resultLanguages; if (v == null) { - this.resultLanguages = v = new CopyOnWriteArrayList(); + this.resultLanguages = v = new CopyOnWriteArrayList<>(); } } } @@ -174,7 +153,7 @@ public List getResultLanguages() { /** * Returns the media type of the result representation. The default value is * MediaType.APPLICATION_XML. - * + * * @return The media type of the result representation. */ public MediaType getResultMediaType() { @@ -183,7 +162,7 @@ public MediaType getResultMediaType() { /** * Returns the XSLT transform sheet to apply to message entities. - * + * * @return The XSLT transform sheet to apply to message entities. */ public Representation getTransformSheet() { @@ -192,9 +171,8 @@ public Representation getTransformSheet() { /** * Sets the transformation mode. See MODE_* constants. - * - * @param mode - * The transformation mode. + * + * @param mode The transformation mode. */ public void setMode(int mode) { this.mode = mode; @@ -202,9 +180,8 @@ public void setMode(int mode) { /** * Sets the character set of the result representation. - * - * @param resultCharacterSet - * The character set of the result representation. + * + * @param resultCharacterSet The character set of the result representation. */ public void setResultCharacterSet(CharacterSet resultCharacterSet) { this.resultCharacterSet = resultCharacterSet; @@ -212,9 +189,8 @@ public void setResultCharacterSet(CharacterSet resultCharacterSet) { /** * Sets the encodings of the result representation. - * - * @param resultEncodings - * The encodings of the result representation. + * + * @param resultEncodings The encodings of the result representation. */ public void setResultEncodings(List resultEncodings) { this.resultEncodings = resultEncodings; @@ -222,9 +198,8 @@ public void setResultEncodings(List resultEncodings) { /** * Sets the languages of the result representation. - * - * @param resultLanguages - * The languages of the result representation. + * + * @param resultLanguages The languages of the result representation. */ public void setResultLanguages(List resultLanguages) { this.resultLanguages = resultLanguages; @@ -232,9 +207,8 @@ public void setResultLanguages(List resultLanguages) { /** * Sets the media type of the result representation. - * - * @param resultMediaType - * The media type of the result representation. + * + * @param resultMediaType The media type of the result representation. */ public void setResultMediaType(MediaType resultMediaType) { this.resultMediaType = resultMediaType; @@ -242,25 +216,22 @@ public void setResultMediaType(MediaType resultMediaType) { /** * Sets the XSLT transform sheet to apply to message entities. - * - * @param transformSheet - * The XSLT transform sheet to apply to message entities. + * + * @param transformSheet The XSLT transform sheet to apply to message entities. */ public void setTransformSheet(Representation transformSheet) { this.transformSheet = transformSheet; } /** - * Transforms a source XML representation by applying an XSLT transform - * sheet to it. - * - * @param source - * The source XML representation. + * Transforms a source XML representation by applying an XSLT transform sheet to it. + * + * @param source The source XML representation. * @return The generated result representation. */ public Representation transform(Representation source) { - final Representation result = new TransformRepresentation(getContext(), - source, getTransformSheet()); + final Representation result = + new TransformRepresentation(getContext(), source, getTransformSheet()); if (this.resultLanguages != null) { result.getLanguages().addAll(getResultLanguages()); @@ -274,5 +245,4 @@ public Representation transform(Representation source) { result.setMediaType(getResultMediaType()); return result; } - } diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlConverter.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlConverter.java index e0cfda1b94..feab246fd7 100644 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlConverter.java +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlConverter.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; import java.io.IOException; import java.util.List; - import org.restlet.data.MediaType; import org.restlet.data.Preference; import org.restlet.engine.converter.ConverterHelper; @@ -23,19 +21,18 @@ /** * Converter between the XML APIs and XML Representation classes. - * + * * @author Jerome Louvel */ public class XmlConverter extends ConverterHelper { - private static final VariantInfo VARIANT_APPLICATION_ALL_XML = new VariantInfo( - MediaType.APPLICATION_ALL_XML); + private static final VariantInfo VARIANT_APPLICATION_ALL_XML = + new VariantInfo(MediaType.APPLICATION_ALL_XML); - private static final VariantInfo VARIANT_APPLICATION_XML = new VariantInfo( - MediaType.APPLICATION_XML); + private static final VariantInfo VARIANT_APPLICATION_XML = + new VariantInfo(MediaType.APPLICATION_XML); - private static final VariantInfo VARIANT_TEXT_XML = new VariantInfo( - MediaType.TEXT_XML); + private static final VariantInfo VARIANT_TEXT_XML = new VariantInfo(MediaType.TEXT_XML); @Override public List> getObjectClasses(Variant source) { @@ -74,11 +71,9 @@ public float score(Object source, Variant target, Resource resource) { if (source instanceof Document) { if (target == null) { result = 0.5F; - } else if (MediaType.APPLICATION_ALL_XML.isCompatible(target - .getMediaType())) { + } else if (MediaType.APPLICATION_ALL_XML.isCompatible(target.getMediaType())) { result = 0.8F; - } else if (MediaType.APPLICATION_XML.isCompatible(target - .getMediaType())) { + } else if (MediaType.APPLICATION_XML.isCompatible(target.getMediaType())) { result = 0.9F; } else if (MediaType.TEXT_XML.isCompatible(target.getMediaType())) { result = 0.9F; @@ -91,19 +86,16 @@ public float score(Object source, Variant target, Resource resource) { } @Override - public float score(Representation source, Class target, - Resource resource) { + public float score(Representation source, Class target, Resource resource) { float result = -1.0F; if ((target != null) && (Document.class.isAssignableFrom(target) - || DomRepresentation.class.isAssignableFrom(target) || SaxRepresentation.class - .isAssignableFrom(target))) { - if (MediaType.APPLICATION_ALL_XML.isCompatible(source - .getMediaType())) { + || DomRepresentation.class.isAssignableFrom(target) + || SaxRepresentation.class.isAssignableFrom(target))) { + if (MediaType.APPLICATION_ALL_XML.isCompatible(source.getMediaType())) { result = 0.8F; - } else if (MediaType.APPLICATION_XML.isCompatible(source - .getMediaType())) { + } else if (MediaType.APPLICATION_XML.isCompatible(source.getMediaType())) { result = 0.9F; } else if (MediaType.TEXT_XML.isCompatible(source.getMediaType())) { result = 0.9F; @@ -117,53 +109,53 @@ public float score(Representation source, Class target, @SuppressWarnings("unchecked") @Override - public T toObject(Representation source, Class target, - Resource resource) throws IOException { - - Object result = null; - if (target != null) { - if (Document.class.isAssignableFrom(target)) { - if (source instanceof DomRepresentation) { - result = ((DomRepresentation) source).getDocument(); - } else { - result = new DomRepresentation(source).getDocument(); - } - } else if (DomRepresentation.class.isAssignableFrom(target)) { - if (source instanceof DomRepresentation) { - result = source; - } else { - result = new DomRepresentation(source); - } - } else if (SaxRepresentation.class.isAssignableFrom(target)) { - if (source instanceof SaxRepresentation) { - result = source; - } else { - result = new SaxRepresentation(source); - } + public T toObject(Representation source, Class target, Resource resource) + throws IOException { + if (target == null) { + return null; + } + + final Object result; + if (Document.class.isAssignableFrom(target)) { + if (source instanceof DomRepresentation domRepresentation) { + result = domRepresentation.getDocument(); + } else { + result = new DomRepresentation(source).getDocument(); } + } else if (DomRepresentation.class.isAssignableFrom(target)) { + if (source instanceof DomRepresentation) { + result = source; + } else { + result = new DomRepresentation(source); + } + } else if (SaxRepresentation.class.isAssignableFrom(target)) { + if (source instanceof SaxRepresentation) { + result = source; + } else { + result = new SaxRepresentation(source); + } + } else { + return null; } return (T) result; } @Override - public Representation toRepresentation(Object source, Variant target, - Resource resource) throws IOException { + public Representation toRepresentation(Object source, Variant target, Resource resource) { Representation result = null; - if (source instanceof Document) { - result = new DomRepresentation(target.getMediaType(), - (Document) source); - } else if (source instanceof Representation) { - result = (Representation) source; + if (source instanceof Document document) { + result = new DomRepresentation(target.getMediaType(), document); + } else if (source instanceof Representation representation) { + result = representation; } return result; } @Override - public void updatePreferences(List> preferences, - Class entity) { + public void updatePreferences(List> preferences, Class entity) { if (Document.class.isAssignableFrom(entity) || DomRepresentation.class.isAssignableFrom(entity) || SaxRepresentation.class.isAssignableFrom(entity)) { diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlRepresentation.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlRepresentation.java index e4489f0263..d65139fd37 100644 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlRepresentation.java +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlRepresentation.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; import java.io.IOException; @@ -17,14 +16,15 @@ import java.util.List; import java.util.Map; import java.util.logging.Level; - import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.SchemaFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathFactory; - import org.restlet.Context; import org.restlet.data.MediaType; import org.restlet.representation.Representation; @@ -37,91 +37,82 @@ import org.xml.sax.SAXException; /** - * Representation based on an XML document. It knows how to evaluate XPath - * expressions and how to manage a namespace context. This class also offers - * convenient methods to validate the document against a specified XML scheme.
+ * Representation based on an XML document. It knows how to evaluate XPath expressions and how to + * manage a namespace context. This class also offers convenient methods to validate the document + * against a specified XML scheme.
*
- * SECURITY WARNING: Using XML parsers configured to not prevent nor limit - * document type definition (DTD) entity resolution can expose the parser to an - * XML Entity Expansion injection attack. - * + * SECURITY WARNING: Using XML parsers configured to not prevent nor limit document type definition + * (DTD) entity resolution can expose the parser to an XML Entity Expansion injection attack. + * * @see XML - * Entity Expansion injection attack + * href="https://github.com/restlet/restlet-framework-java/wiki/XEE-security-enhancements">XML + * Entity Expansion injection attack * @author Jerome Louvel */ -public abstract class XmlRepresentation extends WriterRepresentation implements javax.xml.namespace.NamespaceContext { +public abstract class XmlRepresentation extends WriterRepresentation + implements javax.xml.namespace.NamespaceContext { /** - * True for expanding entity references when parsing XML representations; - * default value provided by system property - * "org.restlet.ext.xml.expandingEntityRefs", false by default. + * True for expanding entity references when parsing XML representations; default value provided + * by system property "org.restlet.ext.xml.expandingEntityRefs", false by default. */ - public final static boolean XML_EXPANDING_ENTITY_REFS = Boolean - .getBoolean("org.restlet.ext.xml.expandingEntityRefs"); + public static final boolean XML_EXPANDING_ENTITY_REFS = + Boolean.getBoolean("org.restlet.ext.xml.expandingEntityRefs"); /** - * True for validating DTD documents when parsing XML representations; - * default value provided by system property - * "org.restlet.ext.xml.validatingDtd", false by default. + * True for validating DTD documents when parsing XML representations; default value provided by + * system property "org.restlet.ext.xml.validatingDtd", false by default. */ - public final static boolean XML_VALIDATING_DTD = Boolean - .getBoolean("org.restlet.ext.xml.validatingDtd"); + public static final boolean XML_VALIDATING_DTD = + Boolean.getBoolean("org.restlet.ext.xml.validatingDtd"); /** - * Appends the text content of a given node and its descendants to the given - * buffer. - * - * @param node - * The node. - * @param sb - * The buffer. + * Appends the text content of a given node and its descendants to the given buffer. + * + * @param node The node. + * @param sb The buffer. */ private static void appendTextContent(Node node, StringBuilder sb) { switch (node.getNodeType()) { - case Node.TEXT_NODE: - case Node.CDATA_SECTION_NODE: - case Node.COMMENT_NODE: - case Node.PROCESSING_INSTRUCTION_NODE: - sb.append(node.getNodeValue()); - break; - case Node.ENTITY_REFERENCE_NODE: - if (node.getNodeName().startsWith("#")) { - int ch = Integer.parseInt(node.getNodeName().substring(1)); - sb.append((char) ch); - } - break; - case Node.ELEMENT_NODE: - case Node.ATTRIBUTE_NODE: - case Node.ENTITY_NODE: - case Node.DOCUMENT_FRAGMENT_NODE: - for (int i = 0; i < node.getChildNodes().getLength(); i++) { - appendTextContent(node.getChildNodes().item(i), sb); - } - break; - default: - break; + case Node.TEXT_NODE, + Node.CDATA_SECTION_NODE, + Node.COMMENT_NODE, + Node.PROCESSING_INSTRUCTION_NODE: + sb.append(node.getNodeValue()); + break; + case Node.ENTITY_REFERENCE_NODE: + if (node.getNodeName().startsWith("#")) { + int ch = Integer.parseInt(node.getNodeName().substring(1)); + sb.append((char) ch); + } + break; + case Node.ELEMENT_NODE, + Node.ATTRIBUTE_NODE, + Node.ENTITY_NODE, + Node.DOCUMENT_FRAGMENT_NODE: + for (int i = 0; i < node.getChildNodes().getLength(); i++) { + appendTextContent(node.getChildNodes().item(i), sb); + } + break; + default: + break; } } /** * Returns a SAX source. - * - * @param xmlRepresentation - * The XML representation to wrap. + * + * @param xmlRepresentation The XML representation to wrap. * @return A SAX source. * @throws IOException */ - public static javax.xml.transform.sax.SAXSource getSaxSource( - Representation xmlRepresentation) throws IOException { - javax.xml.transform.sax.SAXSource result = null; + public static SAXSource getSaxSource(Representation xmlRepresentation) throws IOException { + SAXSource result = null; if (xmlRepresentation != null) { - result = new javax.xml.transform.sax.SAXSource(new InputSource( - xmlRepresentation.getStream())); + result = new SAXSource(new InputSource(xmlRepresentation.getStream())); if (xmlRepresentation.getLocationRef() != null) { - result.setSystemId(xmlRepresentation.getLocationRef() - .getTargetRef().toString()); + result.setSystemId(xmlRepresentation.getLocationRef().getTargetRef().toString()); } } @@ -130,20 +121,20 @@ public static javax.xml.transform.sax.SAXSource getSaxSource( /** * Returns the wrapped schema. - * + * * @return The wrapped schema. * @throws IOException + * @throws SAXException */ - private static javax.xml.validation.Schema getSchema( - Representation schemaRepresentation) throws Exception { + private static javax.xml.validation.Schema getSchema(Representation schemaRepresentation) + throws IOException, SAXException { javax.xml.validation.Schema result = null; if (schemaRepresentation != null) { - final javax.xml.transform.stream.StreamSource streamSource = new javax.xml.transform.stream.StreamSource( - schemaRepresentation.getStream()); - result = javax.xml.validation.SchemaFactory.newInstance( - getSchemaLanguageUri(schemaRepresentation)).newSchema( - streamSource); + final StreamSource streamSource = new StreamSource(schemaRepresentation.getStream()); + result = + SchemaFactory.newInstance(getSchemaLanguageUri(schemaRepresentation)) + .newSchema(streamSource); } return result; @@ -151,22 +142,20 @@ private static javax.xml.validation.Schema getSchema( /** * Returns the schema URI for the current schema media type. - * + * * @return The schema URI. */ - private static String getSchemaLanguageUri( - Representation schemaRepresentation) { + private static String getSchemaLanguageUri(Representation schemaRepresentation) { String result = null; if (schemaRepresentation != null) { - if (MediaType.APPLICATION_W3C_SCHEMA.equals(schemaRepresentation - .getMediaType())) { + if (MediaType.APPLICATION_W3C_SCHEMA.equals(schemaRepresentation.getMediaType())) { result = XMLConstants.W3C_XML_SCHEMA_NS_URI; - } else if (MediaType.APPLICATION_RELAXNG_COMPACT - .equals(schemaRepresentation.getMediaType())) { + } else if (MediaType.APPLICATION_RELAXNG_COMPACT.equals( + schemaRepresentation.getMediaType())) { result = XMLConstants.RELAXNG_NS_URI; - } else if (MediaType.APPLICATION_RELAXNG_XML - .equals(schemaRepresentation.getMediaType())) { + } else if (MediaType.APPLICATION_RELAXNG_XML.equals( + schemaRepresentation.getMediaType())) { result = XMLConstants.RELAXNG_NS_URI; } } @@ -176,9 +165,8 @@ private static String getSchemaLanguageUri( /** * Returns the text content of a given node and its descendants. - * - * @param node - * The node. + * + * @param node The node. * @return The text content of a given node. */ public static String getTextContent(Node node) { @@ -188,43 +176,41 @@ public static String getTextContent(Node node) { } /** - * Specifies that the parser will convert CDATA nodes to text nodes and - * append it to the adjacent (if any) text node. By default, the value of - * this is set to false. + * Specifies that the parser will convert CDATA nodes to text nodes and append it to the + * adjacent (if any) text node. By default, the value of this is set to false. */ private volatile boolean coalescing; /** - * A SAX {@link EntityResolver} to use when resolving external entity - * references while parsing this type of XML representations. - * + * A SAX {@link EntityResolver} to use when resolving external entity references while parsing + * this type of XML representations. + * * @see DocumentBuilder#setEntityResolver(EntityResolver) */ private volatile EntityResolver entityResolver; /** - * A SAX {@link ErrorHandler} to use for signaling SAX exceptions while - * parsing this type of XML representations. - * + * A SAX {@link ErrorHandler} to use for signaling SAX exceptions while parsing this type of XML + * representations. + * * @see DocumentBuilder#setErrorHandler(ErrorHandler) */ private volatile ErrorHandler errorHandler; /** - * Specifies that the parser will expand entity reference nodes. By default, - * the value of this is set to true. + * Specifies that the parser will expand entity reference nodes. By default, the value of this + * is set to true. */ private volatile boolean expandingEntityRefs; /** - * Indicates if the parser will ignore comments. By default, the value of - * this is set to false. + * Indicates if the parser will ignore comments. By default, the value of this is set to false. */ private volatile boolean ignoringComments; /** - * Indicates if the parser will ignore extra white spaces in element - * content. By default, the value of this is set to false. + * Indicates if the parser will ignore extra white spaces in element content. By default, the + * value of this is set to false. */ private volatile boolean ignoringExtraWhitespaces; @@ -235,50 +221,45 @@ public static String getTextContent(Node node) { private volatile Map namespaces; /** - * A (compiled) {@link javax.xml.validation.Schema} to use when validating - * this type of XML representations. - * + * A (compiled) {@link javax.xml.validation.Schema} to use when validating this type of XML + * representations. + * * @see DocumentBuilderFactory#setSchema(javax.xml.validation.Schema) */ private volatile javax.xml.validation.Schema schema; /** - * Indicates the desire for validating this type of XML representations - * against a DTD. Note that for XML schema or Relax NG validation, use the - * "schema" property instead. - * + * Indicates the desire for validating this type of XML representations against a DTD. Note that + * for XML schema or Relax NG validation, use the "schema" property instead. + * * @see DocumentBuilderFactory#setValidating(boolean) */ private volatile boolean validatingDtd; /** - * Indicates the desire for processing XInclude if found in this - * type of XML representations. By default, the value of this is set to - * false. - * + * Indicates the desire for processing XInclude if found in this type of XML + * representations. By default, the value of this is set to false. + * * @see DocumentBuilderFactory#setXIncludeAware(boolean) */ private volatile boolean xIncludeAware; /** * Constructor. - * - * @param mediaType - * The representation's mediaType. + * + * @param mediaType The representation's mediaType. */ - public XmlRepresentation(MediaType mediaType) { + protected XmlRepresentation(MediaType mediaType) { this(mediaType, UNKNOWN_SIZE); } /** * Constructor. - * - * @param mediaType - * The representation's mediaType. - * @param expectedSize - * The expected input stream size. + * + * @param mediaType The representation's mediaType. + * @param expectedSize The expected input stream size. */ - public XmlRepresentation(MediaType mediaType, long expectedSize) { + protected XmlRepresentation(MediaType mediaType, long expectedSize) { super(mediaType, expectedSize); this.coalescing = false; this.entityResolver = null; @@ -293,20 +274,23 @@ public XmlRepresentation(MediaType mediaType, long expectedSize) { this.schema = null; } + @Override + public boolean equals(final Object obj) { + return super.equals(obj); + } + /** - * Evaluates an XPath expression as a boolean. If the evaluation fails, null - * will be returned. - * + * Evaluates an XPath expression as a boolean. If the evaluation fails, null will be returned. + * * @return The evaluation result. */ public Boolean getBoolean(String expression) { - return (Boolean) internalEval(expression, - javax.xml.xpath.XPathConstants.BOOLEAN); + return (Boolean) internalEval(expression, javax.xml.xpath.XPathConstants.BOOLEAN); } /** * Returns the XML representation as a DOM document. - * + * * @return The DOM document. */ protected Document getDocument() throws Exception { @@ -315,11 +299,11 @@ protected Document getDocument() throws Exception { /** * Returns a document builder properly configured. - * + * * @return A document builder properly configured. */ protected DocumentBuilder getDocumentBuilder() throws IOException { - DocumentBuilder result = null; + DocumentBuilder result; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -327,39 +311,45 @@ protected DocumentBuilder getDocumentBuilder() throws IOException { dbf.setValidating(isValidatingDtd()); dbf.setCoalescing(isCoalescing()); dbf.setExpandEntityReferences(false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities",isExpandingEntityRefs()); - dbf.setFeature("http://xml.org/sax/features/external-general-entities",isExpandingEntityRefs()); - + dbf.setFeature( + "http://xml.org/sax/features/external-parameter-entities", + isExpandingEntityRefs()); + dbf.setFeature( + "http://xml.org/sax/features/external-general-entities", + isExpandingEntityRefs()); + dbf.setIgnoringComments(isIgnoringComments()); dbf.setIgnoringElementContentWhitespace(isIgnoringExtraWhitespaces()); - try { - dbf.setXIncludeAware(isXIncludeAware()); - } catch (UnsupportedOperationException uoe) { - Context.getCurrentLogger().log(Level.FINE, - "The JAXP parser doesn't support XInclude.", uoe); - } + setXIncludeAwareAndMuteExceptions(dbf); javax.xml.validation.Schema xsd = getSchema(); if (xsd != null) { dbf.setSchema(xsd); } - result = dbf.newDocumentBuilder(); result.setEntityResolver(getEntityResolver()); result.setErrorHandler(getErrorHandler()); } catch (ParserConfigurationException pce) { - throw new IOException("Couldn't create the empty document: " - + pce.getMessage()); + throw new IOException("Couldn't create the empty document: " + pce.getMessage()); } return result; } + private void setXIncludeAwareAndMuteExceptions(final DocumentBuilderFactory dbf) { + try { + dbf.setXIncludeAware(isXIncludeAware()); + } catch (UnsupportedOperationException uoe) { + Context.getCurrentLogger() + .log(Level.FINE, "The JAXP parser doesn't support XInclude.", uoe); + } + } + /** * Returns a DOM source. - * + * * @return A DOM source. * @throws IOException */ @@ -370,8 +360,7 @@ public javax.xml.transform.dom.DOMSource getDomSource() throws IOException { try { document = getDocumentBuilder().parse(getInputSource()); } catch (SAXException se) { - throw new IOException("Couldn't read the XML representation. " - + se.getMessage()); + throw new IOException("Couldn't read the XML representation. " + se.getMessage()); } if (document != null) { @@ -387,7 +376,7 @@ public javax.xml.transform.dom.DOMSource getDomSource() throws IOException { /** * Return the possibly null current SAX {@link EntityResolver}. - * + * * @return The possibly null current SAX {@link EntityResolver}. */ public EntityResolver getEntityResolver() { @@ -396,7 +385,7 @@ public EntityResolver getEntityResolver() { /** * Return the possibly null current SAX {@link ErrorHandler}. - * + * * @return The possibly null current SAX {@link ErrorHandler}. */ public ErrorHandler getErrorHandler() { @@ -405,77 +394,67 @@ public ErrorHandler getErrorHandler() { /** * Returns the XML representation as a SAX input source. - * + * * @return The SAX input source. */ public abstract InputSource getInputSource() throws IOException; /** - * Returns the map of namespaces. Namespace prefixes are keys and URI - * references are values. - * + * Returns the map of namespaces. Namespace prefixes are keys and URI references are values. + * * @return The map of namespaces. */ public Map getNamespaces() { if (this.namespaces == null) { - this.namespaces = new HashMap(); + this.namespaces = new HashMap<>(); } return this.namespaces; } - /** - * {@inheritDoc - * javax.xml.namespace.NamespaceContext#getNamespaceURI(java.lang.String} - */ + /** {@inheritDoc} */ public String getNamespaceURI(String prefix) { return (this.namespaces == null) ? null : this.namespaces.get(prefix); } /** - * Evaluates an XPath expression as a DOM Node. If the evaluation fails, - * null will be returned. - * + * Evaluates an XPath expression as a DOM Node. If the evaluation fails, null will be returned. + * * @return The evaluation result. */ public Node getNode(String expression) { - return (Node) internalEval(expression, - javax.xml.xpath.XPathConstants.NODE); + return (Node) internalEval(expression, javax.xml.xpath.XPathConstants.NODE); } /** - * Evaluates an XPath expression as a DOM NodeList. If the evaluation fails, - * null will be returned. - * + * Evaluates an XPath expression as a DOM NodeList. If the evaluation fails, null will be + * returned. + * * @return The evaluation result. */ public NodeList getNodes(String expression) { - final org.w3c.dom.NodeList nodes = (org.w3c.dom.NodeList) internalEval( - expression, javax.xml.xpath.XPathConstants.NODESET); + final org.w3c.dom.NodeList nodes = + (org.w3c.dom.NodeList) + internalEval(expression, javax.xml.xpath.XPathConstants.NODESET); return (nodes == null) ? null : new NodeList(nodes); } /** - * Evaluates an XPath expression as a number. If the evaluation fails, null - * will be returned. - * + * Evaluates an XPath expression as a number. If the evaluation fails, null will be returned. + * * @return The evaluation result. */ public Double getNumber(String expression) { - return (Double) internalEval(expression, - javax.xml.xpath.XPathConstants.NUMBER); + return (Double) internalEval(expression, javax.xml.xpath.XPathConstants.NUMBER); } - /** - * {@inheritDoc - * javax.xml.namespace.NamespaceContext#getPrefix(java.lang.String} - */ + /** {@inheritDoc} */ public String getPrefix(String namespaceURI) { String result = null; boolean found = false; - for (Iterator iterator = getNamespaces().keySet().iterator(); iterator - .hasNext() && !found;) { + for (Iterator iterator = getNamespaces().keySet().iterator(); + iterator.hasNext() && !found; ) { String key = iterator.next(); if (getNamespaces().get(key).equals(namespaceURI)) { found = true; @@ -486,15 +465,12 @@ public String getPrefix(String namespaceURI) { return result; } - /** - * {@inheritDoc - * javax.xml.namespace.NamespaceContext#getPrefixes(java.lang.String} - */ + /** {@inheritDoc} */ public Iterator getPrefixes(String namespaceURI) { - final List result = new ArrayList(); + final List result = new ArrayList<>(); - for (Iterator iterator = getNamespaces().keySet().iterator(); iterator - .hasNext();) { + for (Iterator iterator = getNamespaces().keySet().iterator(); + iterator.hasNext(); ) { String key = iterator.next(); if (getNamespaces().get(key).equals(namespaceURI)) { result.add(key); @@ -506,20 +482,19 @@ public Iterator getPrefixes(String namespaceURI) { /** * Returns a SAX source. - * + * * @return A SAX source. * @throws IOException */ - public javax.xml.transform.sax.SAXSource getSaxSource() throws IOException { + public SAXSource getSaxSource() throws IOException { return getSaxSource(this); } /** - * Return the possibly null {@link javax.xml.validation.Schema} to use for - * this type of XML representations. - * - * @return the {@link javax.xml.validation.Schema} object of this type of - * XML representations. + * Return the possibly null {@link javax.xml.validation.Schema} to use for this type of XML + * representations. + * + * @return the {@link javax.xml.validation.Schema} object of this type of XML representations. */ public javax.xml.validation.Schema getSchema() { return schema; @@ -527,14 +502,12 @@ public javax.xml.validation.Schema getSchema() { /** * Returns a stream of XML markup. - * + * * @return A stream of XML markup. * @throws IOException */ - public javax.xml.transform.stream.StreamSource getStreamSource() - throws IOException { - final javax.xml.transform.stream.StreamSource result = new javax.xml.transform.stream.StreamSource( - getStream()); + public StreamSource getStreamSource() throws IOException { + final StreamSource result = new StreamSource(getStream()); if (getLocationRef() != null) { result.setSystemId(getLocationRef().getTargetRef().toString()); @@ -545,24 +518,20 @@ public javax.xml.transform.stream.StreamSource getStreamSource() /** * Evaluates an XPath expression as a string. - * + * * @return The evaluation result. */ public String getText(String expression) { - return (String) internalEval(expression, - javax.xml.xpath.XPathConstants.STRING); + return (String) internalEval(expression, javax.xml.xpath.XPathConstants.STRING); } /** - * Evaluates an XPath expression and returns the result as in the given - * return type. - * - * @param returnType - * The qualified name of the return type. + * Evaluates an XPath expression and returns the result as in the given return type. + * + * @param returnType The qualified name of the return type. * @return The evaluation result. */ - private Object internalEval(String expression, - javax.xml.namespace.QName returnType) { + private Object internalEval(String expression, javax.xml.namespace.QName returnType) { try { Object result = null; @@ -586,11 +555,16 @@ private Object internalEval(String expression, } } + @Override + public int hashCode() { + return super.hashCode(); + } + /** - * Indicates if the parser should be coalescing text. If true the parser - * will convert CDATA nodes to text nodes and append it to the adjacent (if - * any) text node. By default, the value of this is set to false. - * + * Indicates if the parser should be coalescing text. If true, the parser will convert CDATA + * nodes to text nodes and append it to the adjacent (if any) text node. By default, the value + * of this is set to false. + * * @return True if parser should be coalescing text. */ public boolean isCoalescing() { @@ -598,9 +572,9 @@ public boolean isCoalescing() { } /** - * Indicates if the parser will expand entity reference nodes. By default, - * the value of this is set to true. - * + * Indicates if the parser will expand entity reference nodes. By default, the value of this is + * set to true. + * * @return True if the parser will expand entity reference nodes. */ public boolean isExpandingEntityRefs() { @@ -608,9 +582,8 @@ public boolean isExpandingEntityRefs() { } /** - * Indicates if the parser will ignore comments. By default, the value of - * this is set to false. - * + * Indicates if the parser ignores comments. By default, the value of this is set to false. + * * @return True if the parser will ignore comments. */ public boolean isIgnoringComments() { @@ -618,11 +591,10 @@ public boolean isIgnoringComments() { } /** - * Indicates if the parser will ignore extra white spaces in element - * content. Note that the {@link #isValidatingDtd()} must be true when this - * property is 'true' as validation is needed for it to work. By default, the - * value of this is set to false. - * + * Indicates if the parser will ignore extra white spaces in element content. Note that the + * {@link #isValidatingDtd()} must be true when this property is 'true' as validation is needed + * for it to work. By default, the value of this is set to false. + * * @return True if the parser will ignore extra white spaces. */ public boolean isIgnoringExtraWhitespaces() { @@ -630,18 +602,18 @@ public boolean isIgnoringExtraWhitespaces() { } /** - * Indicates if processing is namespace aware. - * - * @return True if processing is namespace aware. + * Indicates if processing is namespace-aware. + * + * @return True if processing is namespace-aware. */ public boolean isNamespaceAware() { return this.namespaceAware; } /** - * Indicates the desire for validating this type of XML representations - * against an XML schema if one is referenced within the contents. - * + * Indicates the desire for validating this type of XML representations against an XML schema if + * one is referenced within the contents. + * * @return True if the schema-based validation is enabled. */ public boolean isValidatingDtd() { @@ -649,19 +621,16 @@ public boolean isValidatingDtd() { } /** - * Indicates the desire for processing XInclude if found in this - * type of XML representations. By default, the value of this is set to - * false. - * + * Indicates the desire for processing XInclude if found in this type of XML + * representations. By default, the value of this is set to false. + * * @return The current value of the xIncludeAware flag. */ public boolean isXIncludeAware() { return xIncludeAware; } - /** - * Releases the namespaces map. - */ + /** Releases the namespaces map. */ @Override public void release() { if (this.namespaces != null) { @@ -672,23 +641,21 @@ public void release() { } /** - * Indicates if the parser should be coalescing text. If true the parser - * will convert CDATA nodes to text nodes and append it to the adjacent (if - * any) text node. By default, the value of this is set to false. - * - * @param coalescing - * True if parser should be coalescing text. + * Indicates if the parser should be coalescing text. If true, the parser will convert CDATA + * nodes to text nodes and append it to the adjacent (if any) text node. By default, the value + * of this is set to false. + * + * @param coalescing True if parser should be coalescing text. */ public void setCoalescing(boolean coalescing) { this.coalescing = coalescing; } /** - * Set the {@link EntityResolver} to use when resolving external entity - * references encountered in this type of XML representations. - * - * @param entityResolver - * the {@link EntityResolver} to set. + * Set the {@link EntityResolver} to use when resolving external entity references encountered + * in this type of XML representations. + * + * @param entityResolver the {@link EntityResolver} to set. */ public void setEntityResolver(EntityResolver entityResolver) { this.entityResolver = entityResolver; @@ -696,45 +663,39 @@ public void setEntityResolver(EntityResolver entityResolver) { /** * Set the {@link ErrorHandler} to use when signaling SAX event exceptions. - * - * @param errorHandler - * the {@link ErrorHandler} to set. + * + * @param errorHandler the {@link ErrorHandler} to set. */ public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } /** - * Indicates if the parser will expand entity reference nodes. By default, - * the value of this is set to true. - * - * @param expandEntityRefs - * True if the parser will expand entity reference nodes. + * Indicates if the parser will expand entity reference nodes. By default, the value of this is + * set to true. + * + * @param expandEntityRefs True if the parser will expand entity reference nodes. */ public void setExpandingEntityRefs(boolean expandEntityRefs) { this.expandingEntityRefs = expandEntityRefs; } /** - * Indicates if the parser will ignore comments. By default, the value of - * this is set to false. - * - * @param ignoringComments - * True if the parser will ignore comments. + * Indicates if the parser ignores comments. By default, the value of this is set to false. + * + * @param ignoringComments True if the parser will ignore comments. */ public void setIgnoringComments(boolean ignoringComments) { this.ignoringComments = ignoringComments; } /** - * Indicates if the parser will ignore extra white spaces in element - * content. Note that the {@link #setValidatingDtd(boolean)} will be invoked - * with 'true' if setting this property to 'true' as validation is needed - * for it to work. - * - * @param ignoringExtraWhitespaces - * True if the parser will ignore extra white spaces in element - * content. + * Indicates if the parser will ignore extra white spaces in element content. Note that the + * {@link #setValidatingDtd(boolean)} will be invoked with 'true' if setting this property to + * 'true' as validation is needed for it to work. + * + * @param ignoringExtraWhitespaces True if the parser will ignore extra white spaces in element + * content. */ public void setIgnoringExtraWhitespaces(boolean ignoringExtraWhitespaces) { if (this.ignoringExtraWhitespaces != ignoringExtraWhitespaces) { @@ -747,10 +708,9 @@ public void setIgnoringExtraWhitespaces(boolean ignoringExtraWhitespaces) { } /** - * Indicates if processing is namespace aware. - * - * @param namespaceAware - * Indicates if processing is namespace aware. + * Indicates if processing is namespace-aware. + * + * @param namespaceAware Indicates if processing is namespace-aware. */ public void setNamespaceAware(boolean namespaceAware) { this.namespaceAware = namespaceAware; @@ -758,60 +718,53 @@ public void setNamespaceAware(boolean namespaceAware) { /** * Sets the map of namespaces. - * - * @param namespaces - * The map of namespaces. + * + * @param namespaces The map of namespaces. */ public void setNamespaces(Map namespaces) { this.namespaces = namespaces; } /** - * Set a (compiled) {@link javax.xml.validation.Schema} to use when parsing - * and validating this type of XML representations. - * - * @param schema - * The (compiled) {@link javax.xml.validation.Schema} object to - * set. + * Set a (compiled) {@link javax.xml.validation.Schema} to use when parsing and validating this + * type of XML representations. + * + * @param schema The (compiled) {@link javax.xml.validation.Schema} object to set. */ public void setSchema(javax.xml.validation.Schema schema) { this.schema = schema; } /** - * Set a schema representation to be compiled and used when parsing and - * validating this type of XML representations. - * - * @param schemaRepresentation - * The schema representation to set. + * Set a schema representation to be compiled and used when parsing and validating this type of + * XML representations. + * + * @param schemaRepresentation The schema representation to set. */ public void setSchema(Representation schemaRepresentation) { try { this.schema = getSchema(schemaRepresentation); } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, - "Unable to compile the schema representation", e); + Context.getCurrentLogger() + .log(Level.WARNING, "Unable to compile the schema representation", e); } } /** - * Indicates the desire for validating this type of XML representations - * against an XML schema if one is referenced within the contents. - * - * @param validating - * The new validation flag to set. + * Indicates the desire for validating this type of XML representations against an XML schema if + * one is referenced within the contents. + * + * @param validating The new validation flag to set. */ public void setValidatingDtd(boolean validating) { this.validatingDtd = validating; } /** - * Indicates the desire for processing XInclude if found in this - * type of XML representations. By default, the value of this is set to - * false. - * - * @param includeAware - * The new value of the xIncludeAware flag. + * Indicates the desire for processing XInclude if found in this type of XML + * representations. By default, the value of this is set to false. + * + * @param includeAware The new value of the xIncludeAware flag. */ public void setXIncludeAware(boolean includeAware) { xIncludeAware = includeAware; @@ -819,9 +772,8 @@ public void setXIncludeAware(boolean includeAware) { /** * Validates the XML representation against a given schema. - * - * @param schema - * The XML schema to use. + * + * @param schema The XML schema to use. */ public void validate(javax.xml.validation.Schema schema) throws Exception { validate(schema, null); @@ -829,22 +781,19 @@ public void validate(javax.xml.validation.Schema schema) throws Exception { /** * Validates the XML representation against a given schema. - * - * @param schema - * The XML schema to use. - * @param result - * The Result object that receives (possibly augmented) XML. - */ - public void validate(javax.xml.validation.Schema schema, - javax.xml.transform.Result result) throws Exception { + * + * @param schema The XML schema to use. + * @param result The Result object that receives (possibly augmented) XML. + */ + public void validate(javax.xml.validation.Schema schema, javax.xml.transform.Result result) + throws Exception { schema.newValidator().validate(getSaxSource(), result); } /** * Validates the XML representation against a given schema. - * - * @param schemaRepresentation - * The XML schema representation to use. + * + * @param schemaRepresentation The XML schema representation to use. */ public void validate(Representation schemaRepresentation) throws Exception { validate(schemaRepresentation, null); @@ -852,15 +801,12 @@ public void validate(Representation schemaRepresentation) throws Exception { /** * Validates the XML representation against a given schema. - * - * @param schemaRepresentation - * The XML schema representation to use. - * @param result - * The Result object that receives (possibly augmented) XML. - */ - public void validate(Representation schemaRepresentation, - javax.xml.transform.Result result) throws Exception { + * + * @param schemaRepresentation The XML schema representation to use. + * @param result The Result object that receives (possibly augmented) XML. + */ + public void validate(Representation schemaRepresentation, javax.xml.transform.Result result) + throws Exception { validate(getSchema(schemaRepresentation), result); } - } diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlWriter.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlWriter.java index de5a767128..13eba38005 100644 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlWriter.java +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/XmlWriter.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; import java.io.IOException; @@ -16,12 +15,12 @@ import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; +import java.util.ArrayDeque; import java.util.Arrays; +import java.util.Deque; import java.util.Enumeration; import java.util.Map; -import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; - import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; @@ -30,207 +29,161 @@ import org.xml.sax.helpers.XMLFilterImpl; /** - * XML writer doing the opposite work of a SAX-based XML reader. The - * implementation is based on the work of David Megginson, the creator of SAX - * who placed the original code in the public domain. - * - *

- * This class can be used by itself or as part of a SAX event stream: it takes - * as input a series of SAX2 ContentHandler events and uses the information in - * those events to write an XML document. Since this class is a filter, it can - * also pass the events on down a filter chain for further processing (you can - * use the XmlWriter to take a snapshot of the current state at any point in a - * filter chain), and it can be used directly as a ContentHandler for a SAX2 - * XMLReader. - *

- * - *

- * The client creates a document by invoking the methods for standard SAX2 - * events, always beginning with the {@link #startDocument startDocument} method - * and ending with the {@link #endDocument endDocument} method. There are - * convenience methods provided so that clients to not have to create empty - * attribute lists or provide empty strings as parameters; for example, the + * XML writer doing the opposite work of a SAX-based XML reader. The implementation is based on the + * work of David Megginson, the creator of SAX, who placed the original code in the public domain. + * + *

This class can be used by itself or as part of a SAX event stream: it takes as input a series + * of SAX2 ContentHandler events and uses the information in those events to write an XML document. + * Since this class is a filter, it can also pass the events on down a filter chain for further + * processing (you can use the XmlWriter to take a snapshot of the current state at any point in a + * filter chain), and it can be used directly as a ContentHandler for a SAX2 XMLReader. + * + *

The client creates a document by invoking the methods for standard SAX2 events, always + * beginning with the {@link #startDocument startDocument} method and ending with the {@link + * #endDocument endDocument} method. There are convenience methods provided so that clients do not + * have to create empty attribute lists or provide empty strings as parameters; for example, the * method invocation - *

- * + * *
  * w.startElement("foo");
  * 
- * - *

- * is equivalent to the regular SAX2 ContentHandler method - *

- * + * + *

is equivalent to the regular SAX2 ContentHandler method + * *

  * w.startElement("", "foo", "", new AttributesImpl());
  * 
- * - *

- * Except that it is more efficient because it does not allocate a new empty - * attribute list each time. The following code will send a simple XML document - * to standard output: - *

- * + * + *

Except that it is more efficient because it does not allocate a new empty attribute list each + * time. The following code will send a simple XML document to standard output: + * *

  * XmlWriter w = new XmlWriter();
- * 
+ *
  * w.startDocument();
  * w.startElement("greeting");
  * w.characters("Hello, world!");
  * w.endElement("greeting");
  * w.endDocument();
  * 
- * - *

- * The resulting document will look like this: - *

- * + * + *

The resulting document will look like this: + * *

  *           <?xml version="1.0" standalone='yes'?>
- *          
+ *
  *           <greeting>Hello, world!</greeting>
  * 
- * - *

- * In fact, there is an even simpler convenience method, dataElement, - * designed for writing elements that contain only character data, so the code - * to generate the document could be shortened to - *

- * + * + *

In fact, there is an even simpler convenience method, dataElement, designed for + * writing elements that contain only character data, so the code to generate the document could be + * shortened to + * *

  * XmlWriter w = new XmlWriter();
- * 
+ *
  * w.startDocument();
  * w.dataElement("greeting", "Hello, world!");
  * w.endDocument();
  * 
- * + * *

Whitespace

- * - *

- * According to the XML Recommendation, all whitespace in an XML - * document is potentially significant to an application, so this class never - * adds newlines or indentation. If you insert three elements in a row, as in - *

- * + * + *

According to the XML Recommendation, all whitespace in an XML document is potentially + * significant to an application, so this class never adds newlines or indentation. If you insert + * three elements in a row, as in + * *

  * w.dataElement("item", "1");
  * w.dataElement("item", "2");
  * w.dataElement("item", "3");
  * 
- * - *

- * you will end up with - *

- * + * + *

you will end up with + * *

  *           <item>1</item><item>3</item><item>3</item>
  * 
- * - *

- * You need to invoke one of the characters methods explicitly to add - * newlines or indentation. Alternatively, you can use the data format mode (set - * the "dataFormat" property) which is optimized for writing purely - * data-oriented (or field-oriented) XML, and does automatic linebreaks and - * indentation (but does not support mixed content properly). See details below. - *

- * + * + *

You need to invoke one of the characters methods explicitly to add newlines or + * indentation. Alternatively, you can use the data format mode (set the "dataFormat" property) + * which is optimized for writing purely data-oriented (or field-oriented) XML and does automatic + * linebreaks and indentation (but does not support mixed content properly). See details below. + * *

Namespace Support

- * - *

- * The writer contains extensive support for XML Namespaces, so that a client - * application does not have to keep track of prefixes and supply - * xmlns attributes. By default, the XML writer will generate - * Namespace declarations in the form _NS1, _NS2, etc., wherever they are + * + *

The writer contains extensive support for XML Namespaces, so that a client application does + * not have to keep track of prefixes and supply xmlns attributes. By default, the XML + * writer will generate Namespace declarations in the form _NS1, _NS2, etc., wherever they are * needed, as in the following example: - *

- * + * *
  * w.startDocument();
  * w.emptyElement("http://www.foo.com/ns/", "foo");
  * w.endDocument();
  * 
- * - *

- * The resulting document will look like this: - *

- * + * + *

The resulting document will look like this: + * *

  *           <?xml version="1.0" standalone='yes'?>
- *          
+ *
  *           <_NS1:foo xmlns:_NS1="http://www.foo.com/ns/"/>
  * 
- * - *

- * In many cases, document authors will prefer to choose their own prefixes - * rather than using the (ugly) default names. The XML writer allows two methods - * for selecting prefixes: - *

- * + * + *

In many cases, document authors will prefer to choose their own prefixes rather than using the + * (ugly) default names. The XML writer allows two methods for selecting prefixes: + * *

    - *
  1. the qualified name
  2. - *
  3. the {@link #setPrefix setPrefix} method.
  4. + *
  5. the qualified name + *
  6. the {@link #setPrefix setPrefix} method. *
- * - *

- * Whenever the XML writer finds a new Namespace URI, it checks to see if a - * qualified (prefixed) name is also available; if so it attempts to use the - * name's prefix (as long as the prefix is not already in use for another - * Namespace URI). - *

- * - *

- * Before writing a document, the client can also pre-map a prefix to a - * Namespace URI with the setPrefix method: - *

- * + * + *

Whenever the XML writer finds a new Namespace URI, it checks to see if a qualified (prefixed) + * name is also available; if so it attempts to use the name's prefix (as long as the prefix is not + * already in use for another Namespace URI). + * + *

Before writing a document, the client can also pre-map a prefix to a Namespace URI with the + * setPrefix method: + * *

  * w.setPrefix("http://www.foo.com/ns/", "foo");
  * w.startDocument();
  * w.emptyElement("http://www.foo.com/ns/", "foo");
  * w.endDocument();
  * 
- * - *

- * The resulting document will look like this: - *

- * + * + *

The resulting document will look like this: + * *

  *           <?xml version="1.0" standalone='yes'?>
- *          
+ *
  *           <foo:foo xmlns:foo="http://www.foo.com/ns/"/>
  * 
- * - *

- * The default Namespace simply uses an empty string as the prefix: - *

- * + * + *

The default Namespace simply uses an empty string as the prefix: + * *

  * w.setPrefix("http://www.foo.com/ns/", "");
  * w.startDocument();
  * w.emptyElement("http://www.foo.com/ns/", "foo");
  * w.endDocument();
  * 
- * - *

- * The resulting document will look like this: - *

- * + * + *

The resulting document will look like this: + * *

  *           <?xml version="1.0" standalone='yes'?>
- *          
+ *
  *           <foo xmlns="http://www.foo.com/ns/"/>
  * 
- * - *

- * By default, the XML writer will not declare a Namespace until it is actually - * used. Sometimes, this approach will create a large number of Namespace - * declarations, as in the following example: - *

- * + * + *

By default, the XML writer will not declare a Namespace until it is actually used. Sometimes, + * this approach will create a large number of Namespace declarations, as in the following example: + * *

  *           <xml version="1.0" standalone='yes'?>
- *          
+ *
  *           <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  *            <rdf:Description about="http://www.foo.com/ids/books/12345">
  *             <dc:title xmlns:dc="http://www.purl.org/dc/">A Dark Night</dc:title>
@@ -239,27 +192,22 @@
  *            </rdf:Description>
  *           </rdf:RDF>
  * 
- * - *

- * The "rdf" prefix is declared only once, because the RDF Namespace is used by - * the root element and can be inherited by all of its descendants; the "dc" - * prefix, on the other hand, is declared three times, because no higher element - * uses the Namespace. To solve this problem, you can instruct the XML writer to - * predeclare Namespaces on the root element even if they are not used there: - *

- * + * + *

The "rdf" prefix is declared only once because the RDF Namespace is used by the root element + * and can be inherited by all of its descendants; the "dc" prefix, on the other hand, is declared + * three times because no higher element uses the Namespace. To solve this problem, you can instruct + * the XML writer to predeclare Namespaces on the root element even if they are not used there: + * *

  * w.forceNSDecl("http://www.purl.org/dc/");
  * 
- * - *

- * Now, the "dc" prefix will be declared on the root element even though it's - * not needed there, and can be inherited by its descendants: - *

- * + * + *

Now, the "dc" prefix will be declared on the root element even though it's not needed there, + * and can be inherited by its descendants: + * *

  *           <xml version="1.0" standalone='yes'?>
- *          
+ *
  *           <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  *                       xmlns:dc="http://www.purl.org/dc/">
  *            <rdf:Description about="http://www.foo.com/ids/books/12345">
@@ -269,43 +217,30 @@
  *            </rdf:Description>
  *           </rdf:RDF>
  * 
- * - *

- * This approach is also useful for declaring Namespace prefixes that be used by - * qualified names appearing in attribute values or character data. - *

- * + * + *

This approach is also useful for declaring Namespace prefixes that be used by qualified names + * appearing in attribute values or character data. + * *

Data Format

- * - *

- * This mode, enabled by the "dataFormat" property, pretty-prints field-oriented - * XML without mixed content. All added indentation and newlines will be passed - * on down the filter chain (if any). - *

- * - *

- * In general, all whitespace in an XML document is potentially significant, so - * a general-purpose XML writing tool cannot add newlines or indentation. - *

- * - *

- * There is, however, a large class of XML documents where information is - * strictly fielded: each element contains either character data or other - * elements, but not both. For this special case, it is possible for a writing - * tool to provide automatic indentation and newlines without requiring extra - * work from the user. Note that this class will likely not yield appropriate - * results for document-oriented XML like XHTML pages, which mix character data - * and elements together. - *

- * - *

- * This writer mode will automatically place each start tag on a new line, - * optionally indented if an indent step is provided (by default, there is no - * indentation). If an element contains other elements, the end tag will also - * appear on a new line with leading indentation. Consider, for example, the - * following code: - *

- * + * + *

This mode, enabled by the "dataFormat" property, pretty-prints field-oriented XML without + * mixed content. All added indentation and newlines will be passed on down the filter chain (if + * any). + * + *

In general, all whitespace in an XML document is potentially significant, so a general-purpose + * XML writing tool cannot add newlines or indentation. + * + *

There is, however, a large class of XML documents where information is strictly fielded: each + * element contains either character data or other elements, but not both. For this special case, it + * is possible for a writing tool to provide automatic indentation and newlines without requiring + * extra work from the user. Note that this class will likely not yield appropriate results for + * document-oriented XML like XHTML pages, which mix character data and elements. + * + *

This writer mode will automatically place each start tag on a new line, optionally indented if + * an indent step is provided (by default, there is no indentation). If an element contains other + * elements, the end tag will also appear on a new line with leading indentation. Consider, for + * example, the following code: + * *

  * XmlWriter w = new XmlWriter();
  * w.setDataFormat(true);
@@ -318,24 +253,22 @@
  * w.endElement("Person");
  * w.endDocument();
  * 
- * - *

- * This code will produce the following document: - *

- * + * + *

This code will produce the following document: + * *

  *           <?xml version="1.0" standalone='yes'?>
- *          
+ *
  *           <Person>
  *             <name>Jane Smith</name>
  *             <date-of-birth>1965-05-23</date-of-birth>
  *             <citizenship>US</citizenship>
  *           </Person>
  * 
- * + * + * @author David Megginson, Jerome Louvel (contact@restlet.com) * @see org.xml.sax.XMLFilter * @see org.xml.sax.ContentHandler - * @author David Megginson, Jerome Louvel (contact@restlet.com) */ public final class XmlWriter extends XMLFilterImpl { private static final Object SEEN_DATA = new Object(); @@ -348,57 +281,40 @@ public final class XmlWriter extends XMLFilterImpl { private volatile int depth = 0; - /** - * The document declarations table. - */ + /** The document declarations table. */ private volatile Map doneDeclTable; - /** - * The element level. - */ + /** The element level. */ private volatile int elementLevel = 0; - /** - * Constant representing empty attributes. - */ - private final Attributes EMPTY_ATTS = new AttributesImpl(); + /** Constant representing empty attributes. */ + private final Attributes emptyAttributes = new AttributesImpl(); - /** - * The forced declarations table. - */ + /** The forced declarations table. */ private volatile Map forcedDeclTable; private volatile int indentStep = 0; - /** - * The namespace support. - */ + /** The namespace support. */ private volatile NamespaceSupport nsSupport; - /** - * The underlying writer. - */ + /** The underlying writer. */ private volatile Writer output; - /** - * The prefix counter. - */ + /** The prefix counter. */ private volatile int prefixCounter = 0; - /** - * The prefixes table. - */ + /** The prefixes table. */ private volatile Map prefixTable; private volatile Object state = SEEN_NOTHING; - private volatile Stack stateStack = new Stack(); + private volatile Deque stateStack = new ArrayDeque<>(); /** * Create a new XML writer. - *

- * Write to standard output. - *

+ * + *

Write to standard output. */ public XmlWriter() { init(null); @@ -406,9 +322,8 @@ public XmlWriter() { /** * Constructor. - * - * @param out - * The underlying output stream. + * + * @param out The underlying output stream. */ public XmlWriter(OutputStream out) { this(new OutputStreamWriter(out)); @@ -416,9 +331,8 @@ public XmlWriter(OutputStream out) { /** * Constructor. - * - * @param out - * The underlying output stream. + * + * @param out The underlying output stream. */ public XmlWriter(OutputStream out, Charset cs) { this(new OutputStreamWriter(out, cs)); @@ -426,9 +340,8 @@ public XmlWriter(OutputStream out, Charset cs) { /** * Constructor. - * - * @param out - * The underlying output stream. + * + * @param out The underlying output stream. */ public XmlWriter(OutputStream out, CharsetEncoder enc) { this(new OutputStreamWriter(out, enc)); @@ -436,23 +349,19 @@ public XmlWriter(OutputStream out, CharsetEncoder enc) { /** * Constructor. - * - * @param out - * The underlying output stream. + * + * @param out The underlying output stream. */ - public XmlWriter(OutputStream out, String charsetName) - throws UnsupportedEncodingException { + public XmlWriter(OutputStream out, String charsetName) throws UnsupportedEncodingException { this(new OutputStreamWriter(out, charsetName)); } /** * Create a new XML writer. - *

- * Write to the writer provided. - *

- * - * @param writer - * The output destination, or null to use standard output. + * + *

Write to the writer provided. + * + * @param writer The output destination, or null to use standard output. */ public XmlWriter(Writer writer) { init(writer); @@ -460,12 +369,10 @@ public XmlWriter(Writer writer) { /** * Create a new XML writer. - *

- * Use the specified XML reader as the parent. - *

- * - * @param xmlreader - * The parent in the filter chain, or null for no parent. + * + *

Use the specified XML reader as the parent. + * + * @param xmlreader The parent in the filter chain, or null for no parent. */ public XmlWriter(XMLReader xmlreader) { super(xmlreader); @@ -474,15 +381,11 @@ public XmlWriter(XMLReader xmlreader) { /** * Create a new XML writer. - *

- * Use the specified XML reader as the parent, and write to the specified - * writer. - *

- * - * @param xmlreader - * The parent in the filter chain, or null for no parent. - * @param writer - * The output destination, or null to use standard output. + * + *

Use the specified XML reader as the parent and write to the specified writer. + * + * @param xmlreader The parent in the filter chain, or null for no parent. + * @param writer The output destination, or null to use standard output. */ public XmlWriter(XMLReader xmlreader, Writer writer) { super(xmlreader); @@ -490,22 +393,16 @@ public XmlWriter(XMLReader xmlreader, Writer writer) { } /** - * Write character data. Pass the event on down the filter chain for further - * processing. - * - * @param ch - * The array of characters to write. - * @param start - * The starting position in the array. - * @param len - * The number of characters to write. - * @exception org.xml.sax.SAXException - * If there is an error writing the characters, or if a - * restlet further down the filter chain raises an exception. + * Write character data. Pass the event on down the filter chain for further processing. + * + * @param ch The array of characters to write. + * @param start The starting position in the array. + * @param len The number of characters to write. + * @throws org.xml.sax.SAXException If there is an error writing the characters, or if a restlet + * further down the filter chain raises an exception. * @see org.xml.sax.ContentHandler#characters */ - private void characters(boolean dataFormat, char ch[], int start, int len) - throws SAXException { + private void characters(boolean dataFormat, char[] ch, int start, int len) throws SAXException { if (dataFormat) { this.state = SEEN_DATA; } @@ -520,56 +417,44 @@ private void characters(boolean dataFormat, char ch[], int start, int len) /** * Write a string of character data, with XML escaping. - *

- * This is a convenience method that takes an XML String, converts it to a - * character array, then invokes {@link #characters(char[], int, int)}. - *

- * - * @param data - * The character data. - * @exception org.xml.sax.SAXException - * If there is an error writing the string, or if a restlet - * further down the filter chain raises an exception. + * + *

This is a convenience method that takes an XML String, converts it to a character array, + * then invokes {@link #characters(char[], int, int)}. + * + * @param data The character data. + * @throws org.xml.sax.SAXException If there is an error writing the string, or if a restlet + * further down the filter chain raises an exception. * @see #characters(char[], int, int) */ - private void characters(boolean dataFormat, String data) - throws SAXException { - final char ch[] = data.toCharArray(); + private void characters(boolean dataFormat, String data) throws SAXException { + final char[] ch = data.toCharArray(); characters(dataFormat, ch, 0, ch.length); } /** - * Write character data. Pass the event on down the filter chain for further - * processing. - * - * @param ch - * The array of characters to write. - * @param start - * The starting position in the array. - * @param len - * The number of characters to write. - * @exception org.xml.sax.SAXException - * If there is an error writing the characters, or if a - * restlet further down the filter chain raises an exception. + * Write character data. Pass the event on down the filter chain for further processing. + * + * @param ch The array of characters to write. + * @param start The starting position in the array. + * @param len The number of characters to write. + * @throws org.xml.sax.SAXException If there is an error writing the characters, or if a restlet + * further down the filter chain raises an exception. * @see org.xml.sax.ContentHandler#characters */ @Override - public void characters(char ch[], int start, int len) throws SAXException { + public void characters(char[] ch, int start, int len) throws SAXException { characters(isDataFormat(), ch, start, len); } /** * Write a string of character data, with XML escaping. - *

- * This is a convenience method that takes an XML String, converts it to a - * character array, then invokes {@link #characters(char[], int, int)}. - *

- * - * @param data - * The character data. - * @exception org.xml.sax.SAXException - * If there is an error writing the string, or if a restlet - * further down the filter chain raises an exception. + * + *

This is a convenience method that takes an XML String, converts it to a character array, + * then invokes {@link #characters(char[], int, int)}. + * + * @param data The character data. + * @throws org.xml.sax.SAXException If there is an error writing the string, or if a restlet + * further down the filter chain raises an exception. * @see #characters(char[], int, int) */ public void characters(String data) throws SAXException { @@ -577,107 +462,73 @@ public void characters(String data) throws SAXException { } /** - * Write an element with character data content but no attributes or - * Namespace URI. - * - *

- * This is a convenience method to write a complete element with character - * data content, including the start tag and end tag. The method provides an - * empty string for the Namespace URI, and empty string for the qualified - * name, and an empty attribute list. - *

- * - *

- * This method invokes - * {@link #startElement(String, String, String, Attributes)}, followed by - * {@link #characters(String)}, followed by - * {@link #endElement(String, String, String)}. - *

- * - * @param localName - * The element's local name. - * @param content - * The character data content. - * @exception org.xml.sax.SAXException - * If there is an error writing the empty tag, or if a - * restlet further down the filter chain raises an exception. + * Write an element with character data content but no attributes or Namespace URI. + * + *

This is a convenience method to write a complete element with character data content, + * including the start tag and end tag. The method provides an empty string for the Namespace + * URI, and empty string for the qualified name, and an empty attribute list. + * + *

This method invokes {@link #startElement(String, String, String, Attributes)}, followed by + * {@link #characters(String)}, followed by {@link #endElement(String, String, String)}. + * + * @param localName The element's local name. + * @param content The character data content. + * @throws org.xml.sax.SAXException If there is an error writing the empty tag, or if a restlet + * further down the filter chain raises an exception. * @see #startElement(String, String, String, Attributes) * @see #characters(String) * @see #endElement(String, String, String) */ - public void dataElement(String localName, String content) - throws SAXException { - dataElement("", localName, "", this.EMPTY_ATTS, content); + public void dataElement(String localName, String content) throws SAXException { + dataElement("", localName, "", this.emptyAttributes, content); } /** * Write an element with character data content but no attributes. - * - *

- * This is a convenience method to write a complete element with character - * data content, including the start tag and end tag. This method provides - * an empty string for the qname and an empty attribute list. - *

- * - *

- * This method invokes - * {@link #startElement(String, String, String, Attributes)}, followed by - * {@link #characters(String)}, followed by - * {@link #endElement(String, String, String)}. - *

- * - * @param uri - * The element's Namespace URI. - * @param localName - * The element's local name. - * @param content - * The character data content. - * @exception org.xml.sax.SAXException - * If there is an error writing the empty tag, or if a - * restlet further down the filter chain raises an exception. + * + *

This is a convenience method to write a complete element with character data content, + * including the start tag and end tag. This method provides an empty string for the qname and + * an empty attribute list. + * + *

This method invokes {@link #startElement(String, String, String, Attributes)}, followed by + * {@link #characters(String)}, followed by {@link #endElement(String, String, String)}. + * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @param content The character data content. + * @throws org.xml.sax.SAXException If there is an error writing the empty tag, or if a restlet + * further down the filter chain raises an exception. * @see #startElement(String, String, String, Attributes) * @see #characters(String) * @see #endElement(String, String, String) */ - public void dataElement(String uri, String localName, String content) - throws SAXException { - dataElement(uri, localName, "", this.EMPTY_ATTS, content); + public void dataElement(String uri, String localName, String content) throws SAXException { + dataElement(uri, localName, "", this.emptyAttributes, content); } /** * Write an element with character data content. - * - *

- * This is a convenience method to write a complete element with character - * data content, including the start tag and end tag. - *

- * - *

- * This method invokes - * {@link #startElement(String, String, String, Attributes)}, followed by - * {@link #characters(String)}, followed by - * {@link #endElement(String, String, String)}. - *

- * - * @param uri - * The element's Namespace URI. - * @param localName - * The element's local name. - * @param qName - * The element's default qualified name. - * @param atts - * The element's attributes. - * @param content - * The character data content. - * @exception org.xml.sax.SAXException - * If there is an error writing the empty tag, or if a - * restlet further down the filter chain raises an exception. + * + *

This is a convenience method to write a complete element with character data content, + * including the start tag and end tag. + * + *

This method invokes {@link #startElement(String, String, String, Attributes)}, followed by + * {@link #characters(String)}, followed by {@link #endElement(String, String, String)}. + * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @param qName The element's default qualified name. + * @param atts The element's attributes. + * @param content The character data content. + * @throws org.xml.sax.SAXException If there is an error writing the empty tag, or if a restlet + * further down the filter chain raises an exception. * @see #startElement(String, String, String, Attributes) * @see #characters(String) * @see #endElement(String, String, String) */ - public void dataElement(String uri, String localName, String qName, - Attributes atts, String content) throws SAXException { + public void dataElement( + String uri, String localName, String qName, Attributes atts, String content) + throws SAXException { startElement(uri, localName, qName, atts); characters(content); endElement(uri, localName, qName); @@ -685,10 +536,9 @@ public void dataElement(String uri, String localName, String qName, /** * Print indentation for the current level. - * - * @exception org.xml.sax.SAXException - * If there is an error writing the indentation characters, - * or if a filter further down the chain raises an exception. + * + * @throws org.xml.sax.SAXException If there is an error writing the indentation characters, or + * if a filter further down the chain raises an exception. */ private void doIndent() throws SAXException { if ((this.indentStep > 0) && (this.depth > 0)) { @@ -700,17 +550,13 @@ private void doIndent() throws SAXException { } /** - * Determine the prefix for an element or attribute name. TODO: this method - * probably needs some cleanup. - * - * @param uri - * The Namespace URI. - * @param qName - * The qualified name (optional); this will be used to indicate - * the preferred prefix if none is currently bound. - * @param isElement - * true if this is an element name, false if it is an attribute - * name (which cannot use the default Namespace). + * Determine the prefix for an element or attribute name. + * + * @param uri The Namespace URI. + * @param qName The qualified name (optional); this will be used to indicate the preferred + * prefix if none is currently bound. + * @param isElement true if this is an element name, false if it is an attribute name (which + * cannot use the default Namespace). */ private String doPrefix(String uri, String qName, boolean isElement) { final String defaultNS = this.nsSupport.getURI(""); @@ -721,7 +567,7 @@ private String doPrefix(String uri, String qName, boolean isElement) { return null; } String prefix; - if (isElement && (defaultNS != null) && uri.equals(defaultNS)) { + if (isElement && uri.equals(defaultNS)) { prefix = ""; } else { prefix = this.nsSupport.getPrefix(uri); @@ -731,14 +577,15 @@ private String doPrefix(String uri, String qName, boolean isElement) { } prefix = this.doneDeclTable.get(uri); if ((prefix != null) - && (((!isElement || (defaultNS != null)) && prefix.isEmpty()) || (this.nsSupport - .getURI(prefix) != null))) { + && (((!isElement || (defaultNS != null)) && prefix.isEmpty()) + || (this.nsSupport.getURI(prefix) != null))) { prefix = null; } if (prefix == null) { prefix = this.prefixTable.get(uri); if ((prefix != null) - && (((!isElement || (defaultNS != null)) && prefix.isEmpty()) || (this.nsSupport.getURI(prefix) != null))) { + && (((!isElement || (defaultNS != null)) && prefix.isEmpty()) + || (this.nsSupport.getURI(prefix) != null))) { prefix = null; } } @@ -752,8 +599,9 @@ private String doPrefix(String uri, String qName, boolean isElement) { prefix = qName.substring(0, i); } } - for (; (prefix == null) || (this.nsSupport.getURI(prefix) != null); prefix = "__NS" - + ++this.prefixCounter) { + for (; + (prefix == null) || (this.nsSupport.getURI(prefix) != null); + prefix = "__NS" + ++this.prefixCounter) { // Do nothing } @@ -767,74 +615,56 @@ private String doPrefix(String uri, String qName, boolean isElement) { // ////////////////////////////////////////////////////////////////// /** - * Add an empty element without a Namespace URI, qname or attributes. - * - *

- * This method will supply an empty string for the qname, and empty string - * for the Namespace URI, and an empty attribute list. It invokes - * {@link #emptyElement(String, String, String, Attributes)} directly. - *

- * - * @param localName - * The element's local name. - * @exception org.xml.sax.SAXException - * If there is an error writing the empty tag, or if a - * restlet further down the filter chain raises an exception. + * Add an empty element without a Namespace URI, qname, or attributes. + * + *

This method will supply an empty string for the qname, and empty string for the Namespace + * URI, and an empty attribute list. It invokes {@link #emptyElement(String, String, String, + * Attributes)} directly. + * + * @param localName The element's local name. + * @throws org.xml.sax.SAXException If there is an error writing the empty tag, or if a restlet + * further down the filter chain raises an exception. * @see #emptyElement(String, String, String, Attributes) */ public void emptyElement(String localName) throws SAXException { - emptyElement("", localName, "", this.EMPTY_ATTS); + emptyElement("", localName, "", this.emptyAttributes); } /** * Add an empty element without a qname or attributes. - * - *

- * This method will supply an empty string for the qname and an empty - * attribute list. It invokes - * {@link #emptyElement(String, String, String, Attributes)} directly. - *

- * - * @param uri - * The element's Namespace URI. - * @param localName - * The element's local name. - * @exception org.xml.sax.SAXException - * If there is an error writing the empty tag, or if a - * restlet further down the filter chain raises an exception. + * + *

This method will supply an empty string for the qname and an empty attribute list. It + * invokes {@link #emptyElement(String, String, String, Attributes)} directly. + * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @throws org.xml.sax.SAXException If there is an error writing the empty tag, or if a restlet + * further down the filter chain raises an exception. * @see #emptyElement(String, String, String, Attributes) */ public void emptyElement(String uri, String localName) throws SAXException { - emptyElement(uri, localName, "", this.EMPTY_ATTS); + emptyElement(uri, localName, "", this.emptyAttributes); } /** - * Write an empty element. This method writes an empty element tag rather - * than a start tag followed by an end tag. Both a {@link #startElement - * startElement} and an {@link #endElement endElement} event will be passed - * on down the filter chain. - * - * @param uri - * The element's Namespace URI, or the empty string if the - * element has no Namespace or if Namespace processing is not - * being performed. - * @param localName - * The element's local name (without prefix). This parameter must - * be provided. - * @param qName - * The element's qualified name (with prefix), or the empty - * string if none is available. This parameter is strictly - * advisory: the writer may or may not use the prefix attached. - * @param atts - * The element's attribute list. - * @exception org.xml.sax.SAXException - * If there is an error writing the empty tag, or if a - * restlet further down the filter chain raises an exception. + * Write an empty element. This method writes an empty element tag rather than a start tag + * followed by an end tag. Both a {@link #startElement startElement} and an {@link #endElement + * endElement} event will be passed on down the filter chain. + * + * @param uri The element's Namespace URI, or the empty string if the element has no Namespace + * or if Namespace processing is not being performed. + * @param localName The element's local name (without prefix). This parameter must be provided. + * @param qName The element's qualified name (with prefix), or the empty string if none is + * available. This parameter is strictly advisory: the writer may or may not use the prefix + * attached. + * @param atts The element's attribute list. + * @throws org.xml.sax.SAXException If there is an error writing the empty tag, or if a restlet + * further down the filter chain raises an exception. * @see #startElement * @see #endElement */ - public void emptyElement(String uri, String localName, String qName, - Attributes atts) throws SAXException { + public void emptyElement(String uri, String localName, String qName, Attributes atts) + throws SAXException { if (isDataFormat()) { this.state = SEEN_ELEMENT; if (this.depth > 0) { @@ -857,12 +687,11 @@ public void emptyElement(String uri, String localName, String qName, } /** - * Write a newline at the end of the document. Pass the event on down the - * filter chain for further processing. - * - * @exception org.xml.sax.SAXException - * If there is an error writing the newline, or if a restlet - * further down the filter chain raises an exception. + * Write a newline at the end of the document. Pass the event on down the filter chain for + * further processing. + * + * @throws org.xml.sax.SAXException If there is an error writing the newline, or if a restlet + * further down the filter chain raises an exception. * @see org.xml.sax.ContentHandler#endDocument */ @Override @@ -878,18 +707,13 @@ public void endDocument() throws SAXException { /** * End an element without a Namespace URI or qname. - * - *

- * This method will supply an empty string for the qName and an empty string - * for the Namespace URI. It invokes - * {@link #endElement(String, String, String)} directly. - *

- * - * @param localName - * The element's local name. - * @exception org.xml.sax.SAXException - * If there is an error writing the end tag, or if a restlet - * further down the filter chain raises an exception. + * + *

This method will supply an empty string for the qName and an empty string for the + * Namespace URI. It invokes {@link #endElement(String, String, String)} directly. + * + * @param localName The element's local name. + * @throws org.xml.sax.SAXException If there is an error writing the end tag, or if a restlet + * further down the filter chain raises an exception. * @see #endElement(String, String, String) */ public void endElement(String localName) throws SAXException { @@ -898,19 +722,14 @@ public void endElement(String localName) throws SAXException { /** * End an element without a qname. - * - *

- * This method will supply an empty string for the qName. It invokes - * {@link #endElement(String, String, String)} directly. - *

- * - * @param uri - * The element's Namespace URI. - * @param localName - * The element's local name. - * @exception org.xml.sax.SAXException - * If there is an error writing the end tag, or if a restlet - * further down the filter chain raises an exception. + * + *

This method will supply an empty string for the qName. It invokes {@link + * #endElement(String, String, String)} directly. + * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @throws org.xml.sax.SAXException If there is an error writing the end tag, or if a restlet + * further down the filter chain raises an exception. * @see #endElement(String, String, String) */ public void endElement(String uri, String localName) throws SAXException { @@ -918,26 +737,19 @@ public void endElement(String uri, String localName) throws SAXException { } /** - * Write an end tag. Pass the event on down the filter chain for further - * processing. - * - * @param uri - * The Namespace URI, or the empty string if none is available. - * @param localName - * The element's local (unprefixed) name (required). - * @param qName - * The element's qualified (prefixed) name, or the empty string - * is none is available. This method will use the qName as a - * template for generating a prefix if necessary, but it is not - * guaranteed to use the same qName. - * @exception org.xml.sax.SAXException - * If there is an error writing the end tag, or if a restlet - * further down the filter chain raises an exception. + * Write an end tag. Pass the event on down the filter chain for further processing. + * + * @param uri The Namespace URI, or the empty string if none is available. + * @param localName The element's local (unprefixed) name (required). + * @param qName The element's qualified (prefixed) name, or the empty string is none is + * available. This method will use the qName as a template for generating a prefix if + * necessary, but it is not guaranteed to use the same qName. + * @throws org.xml.sax.SAXException If there is an error writing the end tag, or if a restlet + * further down the filter chain raises an exception. * @see org.xml.sax.ContentHandler#endElement */ @Override - public void endElement(String uri, String localName, String qName) - throws SAXException { + public void endElement(String uri, String localName, String qName) throws SAXException { if (isDataFormat()) { this.depth--; if (this.state == SEEN_ELEMENT) { @@ -963,16 +775,14 @@ public void endElement(String uri, String localName, String qName) /** * Flush the output. - *

- * This method flushes the output stream. It is especially useful when you - * need to make certain that the entire document has been written to output - * but do not want to close the output stream. - *

- *

- * This method is invoked automatically by the {@link #endDocument - * endDocument} method after writing a document. - *

- * + * + *

This method flushes the output stream. It is especially useful when you need to make + * certain that the entire document has been written to output but do not want to close the + * output stream. + * + *

This method is invoked automatically by the {@link #endDocument endDocument} method after + * writing a document. + * * @see #reset */ public void flush() throws IOException { @@ -985,20 +795,16 @@ public void flush() throws IOException { /** * Force a Namespace to be declared on the root element. - *

- * By default, the XMLWriter will declare only the Namespaces needed for an - * element; as a result, a Namespace may be declared many places in a - * document if it is not used on the root element. - *

- *

- * This method forces a Namespace to be declared on the root element even if - * it is not used there, and reduces the number of xmlns attributes in the - * document. - *

- * - * @param uri - * The Namespace URI to declare. - * @see #forceNSDecl(java.lang.String,java.lang.String) + * + *

By default, the XMLWriter will declare only the Namespaces needed for an element; as a + * result, a Namespace may be declared many places in a document if it is not used on the root + * element. + * + *

This method forces a Namespace to be declared on the root element even if it is not used + * there and reduces the number of xmlns attributes in the document. + * + * @param uri The Namespace URI to declare. + * @see #forceNSDecl(java.lang.String, java.lang.String) * @see #setPrefix */ public void forceNSDecl(String uri) { @@ -1011,16 +817,12 @@ public void forceNSDecl(String uri) { /** * Force a Namespace declaration with a preferred prefix. - *

- * This is a convenience method that invokes {@link #setPrefix setPrefix} - * then {@link #forceNSDecl(java.lang.String) forceNSDecl}. - *

- * - * @param uri - * The Namespace URI to declare on the root element. - * @param prefix - * The preferred prefix for the Namespace, or "" for the default - * Namespace. + * + *

This is a convenience method that invokes {@link #setPrefix setPrefix} then {@link + * #forceNSDecl(java.lang.String) forceNSDecl}. + * + * @param uri The Namespace URI to declare on the root element. + * @param prefix The preferred prefix for the Namespace, or "" for the default Namespace. * @see #setPrefix * @see #forceNSDecl(java.lang.String) */ @@ -1030,8 +832,8 @@ public void forceNSDecl(String uri, String prefix) { } /** - * Force all Namespaces to be declared. This method is used on the root - * element to ensure that the predeclared Namespaces all appear. + * Force all Namespaces to be declared. This method is used on the root element to ensure that + * the predeclared Namespaces all appear. */ private void forceNSDecls() { for (final String prefix : this.forcedDeclTable.keySet()) { @@ -1041,13 +843,11 @@ private void forceNSDecls() { /** * Return the current indent step. - *

- * Return the current indent step: each start tag will be indented by this - * number of spaces times the number of ancestors that the element has. - *

- * - * @return The number of spaces in each indentation step, or 0 or less for - * no indentation. + * + *

Return the current indent step: each start tag will be indented by this number of spaces + * times the number of ancestors that the element has. + * + * @return The number of spaces in each indentation step, or 0 or less for no indentation. */ public int getIndentStep() { return this.indentStep; @@ -1055,9 +855,8 @@ public int getIndentStep() { /** * Get the current or preferred prefix for a Namespace URI. - * - * @param uri - * The Namespace URI. + * + * @param uri The Namespace URI. * @return The preferred prefix, or "" for the default Namespace. * @see #setPrefix */ @@ -1067,7 +866,7 @@ public String getPrefix(String uri) { /** * Returns the underlying writer. - * + * * @return The underlying writer. */ public Writer getWriter() { @@ -1075,42 +874,34 @@ public Writer getWriter() { } /** - * Write ignorable whitespace. Pass the event on down the filter chain for - * further processing. - * - * @param ch - * The array of characters to write. - * @param start - * The starting position in the array. - * @param length - * The number of characters to write. - * @exception org.xml.sax.SAXException - * If there is an error writing the whitespace, or if a - * restlet further down the filter chain raises an exception. + * Write ignorable whitespace. Pass the event on down the filter chain for further processing. + * + * @param ch The array of characters to write. + * @param start The starting position in the array. + * @param length The number of characters to write. + * @throws org.xml.sax.SAXException If there is an error writing the whitespace, or if a restlet + * further down the filter chain raises an exception. * @see org.xml.sax.ContentHandler#ignorableWhitespace */ @Override - public void ignorableWhitespace(char ch[], int start, int length) - throws SAXException { + public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { writeEsc(ch, start, length, false); super.ignorableWhitespace(ch, start, length); } /** * Internal initialization method. - * - *

- * All of the public constructors invoke this method. - * - * @param writer - * The output destination, or null to use standard output. + * + *

All the public constructors invoke this method. + * + * @param writer The output destination, or null to use standard output. */ private void init(Writer writer) { setOutput(writer); this.nsSupport = new NamespaceSupport(); - this.prefixTable = new ConcurrentHashMap(); - this.forcedDeclTable = new ConcurrentHashMap(); - this.doneDeclTable = new ConcurrentHashMap(); + this.prefixTable = new ConcurrentHashMap<>(); + this.forcedDeclTable = new ConcurrentHashMap<>(); + this.doneDeclTable = new ConcurrentHashMap<>(); } public boolean isDataFormat() { @@ -1118,21 +909,17 @@ public boolean isDataFormat() { } /** - * Write a processing instruction. Pass the event on down the filter chain - * for further processing. - * - * @param target - * The PI target. - * @param data - * The PI data. - * @exception org.xml.sax.SAXException - * If there is an error writing the PI, or if a restlet - * further down the filter chain raises an exception. + * Write a processing instruction. Pass the event on down the filter chain for further + * processing. + * + * @param target The PI target. + * @param data The PI data. + * @throws org.xml.sax.SAXException If there is an error writing the PI, or if a restlet further + * down the filter chain raises an exception. * @see org.xml.sax.ContentHandler#processingInstruction */ @Override - public void processingInstruction(String target, String data) - throws SAXException { + public void processingInstruction(String target, String data) throws SAXException { write(" - * This method is especially useful if the writer throws an exception before - * it is finished, and you want to reuse the writer for a new document. It - * is usually a good idea to invoke {@link #flush flush} before resetting - * the writer, to make sure that no output is lost. - *

- * - *

- * This method is invoked automatically by the {@link #startDocument - * startDocument} method before writing a new document. - *

- * - *

- * Note: this method will not clear the prefix or - * URI information in the writer or the selected output writer. - *

- * + * + *

This method is especially useful if the writer throws an exception before it is finished, + * and you want to reuse the writer for a new document. It is usually a good idea to invoke + * {@link #flush flush} before resetting the writer, to make sure that no output is lost. + * + *

This method is invoked automatically by the {@link #startDocument startDocument} method + * before writing a new document. + * + *

Note: this method will not clear the prefix or URI information + * in the writer or the selected output writer. + * * @see #flush */ public void reset() { if (isDataFormat()) { this.depth = 0; this.state = SEEN_NOTHING; - this.stateStack = new Stack(); + this.stateStack = new ArrayDeque<>(); } this.elementLevel = 0; @@ -1188,9 +968,8 @@ public void setDataFormat(boolean dataFormat) { /** * Set the current indent step. - * - * @param indentStep - * The new indent step (0 or less for no indentation). + * + * @param indentStep The new indent step (0 or less for no indentation). */ public void setIndentStep(int indentStep) { this.indentStep = indentStep; @@ -1198,9 +977,8 @@ public void setIndentStep(int indentStep) { /** * Set a new output destination for the document. - * - * @param writer - * The output destination, or null to use standard output. + * + * @param writer The output destination, or null to use standard output. * @see #flush */ public void setOutput(Writer writer) { @@ -1213,31 +991,26 @@ public void setOutput(Writer writer) { /** * Specify a preferred prefix for a Namespace URI. - *

- * Note that this method does not actually force the Namespace to be - * declared; to do that, use the {@link #forceNSDecl(java.lang.String) - * forceNSDecl} method as well. - *

- * - * @param uri - * The Namespace URI. - * @param prefix - * The preferred prefix, or "" to select the default Namespace. + * + *

Note that this method does not force the Namespace to be declared; to do that, use the + * {@link #forceNSDecl(java.lang.String) forceNSDecl} method as well. + * + * @param uri The Namespace URI. + * @param prefix The preferred prefix, or "" to select the default Namespace. * @see #getPrefix * @see #forceNSDecl(java.lang.String) - * @see #forceNSDecl(java.lang.String,java.lang.String) + * @see #forceNSDecl(java.lang.String, java.lang.String) */ public void setPrefix(String uri, String prefix) { this.prefixTable.put(uri, prefix); } /** - * Write the XML declaration at the beginning of the document. Pass the - * event on down the filter chain for further processing. - * - * @exception org.xml.sax.SAXException - * If there is an error writing the XML declaration, or if a - * restlet further down the filter chain raises an exception. + * Write the XML declaration at the beginning of the document. Pass the event on down the filter + * chain for further processing. + * + * @throws org.xml.sax.SAXException If there is an error writing the XML declaration, or if a + * restlet further down the filter chain raises an exception. * @see org.xml.sax.ContentHandler#startDocument */ @Override @@ -1248,70 +1021,54 @@ public void startDocument() throws SAXException { } /** - * Start a new element without a qname, attributes or a Namespace URI. - * - *

- * This method will provide an empty string for the Namespace URI, and empty - * string for the qualified name, and a default empty attribute list. It - * invokes #startElement(String, String, String, Attributes)} directly. - *

- * - * @param localName - * The element's local name. - * @exception org.xml.sax.SAXException - * If there is an error writing the start tag, or if a - * restlet further down the filter chain raises an exception. + * Start a new element without a qname, attributes, or a Namespace URI. + * + *

This method will provide an empty string for the Namespace URI, and empty string for the + * qualified name, and a default empty attribute list. It invokes #startElement(String, String, + * String, Attributes)} directly. + * + * @param localName The element's local name. + * @throws org.xml.sax.SAXException If there is an error writing the start tag, or if a restlet + * further down the filter chain raises an exception. * @see #startElement(String, String, String, Attributes) */ public void startElement(String localName) throws SAXException { - startElement("", localName, "", this.EMPTY_ATTS); + startElement("", localName, "", this.emptyAttributes); } /** * Start a new element without a qname or attributes. - * - *

- * This method will provide a default empty attribute list and an empty - * string for the qualified name. It invokes - * {@link #startElement(String, String, String, Attributes)} directly. - *

- * - * @param uri - * The element's Namespace URI. - * @param localName - * The element's local name. - * @exception org.xml.sax.SAXException - * If there is an error writing the start tag, or if a - * restlet further down the filter chain raises an exception. + * + *

This method will provide a default empty attribute list and an empty string for the + * qualified name. It invokes {@link #startElement(String, String, String, Attributes)} + * directly. + * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @throws org.xml.sax.SAXException If there is an error writing the start tag, or if a restlet + * further down the filter chain raises an exception. * @see #startElement(String, String, String, Attributes) */ public void startElement(String uri, String localName) throws SAXException { - startElement(uri, localName, "", this.EMPTY_ATTS); + startElement(uri, localName, "", this.emptyAttributes); } /** - * Write a start tag. Pass the event on down the filter chain for further - * processing. - * - * @param uri - * The Namespace URI, or the empty string if none is available. - * @param localName - * The element's local (unprefixed) name (required). - * @param qName - * The element's qualified (prefixed) name, or the empty string - * is none is available. This method will use the qName as a - * template for generating a prefix if necessary, but it is not - * guaranteed to use the same qName. - * @param atts - * The element's attribute list (must not be null). - * @exception org.xml.sax.SAXException - * If there is an error writing the start tag, or if a - * restlet further down the filter chain raises an exception. + * Write a start tag. Pass the event on down the filter chain for further processing. + * + * @param uri The Namespace URI, or the empty string if none is available. + * @param localName The element's local (unprefixed) name (required). + * @param qName The element's qualified (prefixed) name, or the empty string is none is + * available. This method will use the qName as a template for generating a prefix if + * necessary, but it is not guaranteed to use the same qName. + * @param atts The element's attribute list (must not be null). + * @throws org.xml.sax.SAXException If there is an error writing the start tag, or if a restlet + * further down the filter chain raises an exception. * @see org.xml.sax.ContentHandler#startElement */ @Override - public void startElement(String uri, String localName, String qName, - Attributes atts) throws SAXException { + public void startElement(String uri, String localName, String qName, Attributes atts) + throws SAXException { if (isDataFormat()) { this.stateStack.push(SEEN_ELEMENT); this.state = SEEN_NOTHING; @@ -1340,12 +1097,10 @@ public void startElement(String uri, String localName, String qName, /** * Write a raw character. - * - * @param c - * The character to write. - * @exception org.xml.sax.SAXException - * If there is an error writing the character, this method - * will throw an IOException wrapped in a SAXException. + * + * @param c The character to write. + * @throws org.xml.sax.SAXException If there is an error writing the character, this method will + * throw an IOException wrapped in a SAXException. */ private void write(char c) throws SAXException { try { @@ -1357,11 +1112,10 @@ private void write(char c) throws SAXException { /** * Write a raw string. - * + * * @param s - * @exception org.xml.sax.SAXException - * If there is an error writing the string, this method will - * throw an IOException wrapped in a SAXException + * @throws org.xml.sax.SAXException If there is an error writing the string, this method will + * throw an IOException wrapped in a SAXException */ private void write(String s) throws SAXException { try { @@ -1372,31 +1126,30 @@ private void write(String s) throws SAXException { } /** - * Write out an attribute list, escaping values. The names will have - * prefixes added to them. - * - * @param atts - * The attribute list to write. - * @exception org.xml.SAXException - * If there is an error writing the attribute list, this - * method will throw an IOException wrapped in a - * SAXException. + * Write out an attribute list, escaping values. The names will have prefixes added to them. + * + * @param attributes The attribute list to write. + * @throws org.xml.sax.SAXException If there is an error writing the attribute list, this method + * will throw an IOException wrapped in a SAXException. */ - private void writeAttributes(Attributes atts) throws SAXException { - final int len = atts.getLength(); + private void writeAttributes(Attributes attributes) throws SAXException { + final int len = attributes.getLength(); for (int i = 0; i < len; i++) { - if ("xmlns".equals(atts.getQName(i))) { + if ("xmlns".equals(attributes.getQName(i))) { // Redefines the default namespace. - forceNSDecl(atts.getValue(i)); - } else if (atts.getQName(i) != null - && atts.getQName(i).startsWith("xmlns")) { + forceNSDecl(attributes.getValue(i)); + } else if (attributes.getQName(i) != null + && attributes.getQName(i).startsWith("xmlns")) { // Defines the namespace using its prefix. - forceNSDecl(atts.getValue(i), atts.getLocalName(i)); + forceNSDecl(attributes.getValue(i), attributes.getLocalName(i)); } else { - final char ch[] = atts.getValue(i).toCharArray(); + final char[] ch = attributes.getValue(i).toCharArray(); write(' '); - writeName(atts.getURI(i), atts.getLocalName(i), - atts.getQName(i), false); + writeName( + attributes.getURI(i), + attributes.getLocalName(i), + attributes.getQName(i), + false); write("=\""); writeEsc(ch, 0, ch.length, true); write('"'); @@ -1406,69 +1159,57 @@ private void writeAttributes(Attributes atts) throws SAXException { /** * Write an array of data characters with escaping. - * - * @param ch - * The array of characters. - * @param start - * The starting position. - * @param length - * The number of characters to use. - * @param isAttVal - * true if this is an attribute value literal. - * @exception org.xml.SAXException - * If there is an error writing the characters, this method - * will throw an IOException wrapped in a SAXException. + * + * @param ch The array of characters. + * @param start The starting position. + * @param length The number of characters to use. + * @param isAttVal true if this is an attribute value literal. + * @throws org.xml.sax.SAXException If there is an error writing the characters, this method + * will throw an IOException wrapped in a SAXException. */ - private void writeEsc(char ch[], int start, int length, boolean isAttVal) - throws SAXException { + private void writeEsc(char[] ch, int start, int length, boolean isAttVal) throws SAXException { for (int i = start; i < start + length; i++) { switch (ch[i]) { - case '&': - write("&"); - break; - case '<': - write("<"); - break; - case '>': - write(">"); - break; - case '\"': - if (isAttVal) { - write("""); - } else { - write('\"'); - } - break; - default: - if (ch[i] > '\u007f') { - write("&#"); - write(Integer.toString(ch[i])); - write(';'); - } else { - write(ch[i]); - } + case '&': + write("&"); + break; + case '<': + write("<"); + break; + case '>': + write(">"); + break; + case '\"': + if (isAttVal) { + write("""); + } else { + write('\"'); + } + break; + default: + if (ch[i] > '\u007f') { + write("&#"); + write(Integer.toString(ch[i])); + write(';'); + } else { + write(ch[i]); + } } } } /** * Write an element or attribute name. - * - * @param uri - * The Namespace URI. - * @param localName - * The local name. - * @param qName - * The prefixed name, if available, or the empty string. - * @param isElement - * true if this is an element name, false if it is an attribute - * name. - * @exception org.xml.sax.SAXException - * This method will throw an IOException wrapped in a - * SAXException if there is an error writing the name. + * + * @param uri The Namespace URI. + * @param localName The local name. + * @param qName The prefixed name, if available, or the empty string. + * @param isElement true if this is an element name, false if it is an attribute name. + * @throws org.xml.sax.SAXException This method will throw an IOException wrapped in a + * SAXException if there is an error writing the name. */ - private void writeName(String uri, String localName, String qName, - boolean isElement) throws SAXException { + private void writeName(String uri, String localName, String qName, boolean isElement) + throws SAXException { final String prefix = doPrefix(uri, qName, isElement); if ((prefix != null) && !prefix.isEmpty()) { @@ -1480,22 +1221,19 @@ private void writeName(String uri, String localName, String qName, /** * Write out the list of Namespace declarations. - * - * @exception org.xml.sax.SAXException - * This method will throw an IOException wrapped in a - * SAXException if there is an error writing the Namespace - * declarations. + * + * @throws org.xml.sax.SAXException This method will throw an IOException wrapped in a + * SAXException if there is an error writing the Namespace declarations. */ private void writeNSDecls() throws SAXException { - final Enumeration prefixes = this.nsSupport - .getDeclaredPrefixes(); + final Enumeration prefixes = this.nsSupport.getDeclaredPrefixes(); while (prefixes.hasMoreElements()) { final String prefix = prefixes.nextElement(); String uri = this.nsSupport.getURI(prefix); if (uri == null) { uri = ""; } - final char ch[] = uri.toCharArray(); + final char[] ch = uri.toCharArray(); write(' '); if ("".equals(prefix)) { write("xmlns=\""); @@ -1508,5 +1246,4 @@ private void writeNSDecls() throws SAXException { write('\"'); } } - } diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/internal/AbstractXmlReader.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/internal/AbstractXmlReader.java index 1f8d6e1522..d0b7410849 100644 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/internal/AbstractXmlReader.java +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/internal/AbstractXmlReader.java @@ -1,27 +1,23 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml.internal; import java.util.HashMap; - import org.xml.sax.ContentHandler; import org.xml.sax.DTDHandler; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; -import org.xml.sax.SAXNotRecognizedException; -import org.xml.sax.SAXNotSupportedException; import org.xml.sax.XMLReader; /** * Abstract SAX XML Reader. - * + * * @author Warren Janssens */ public abstract class AbstractXmlReader implements XMLReader { @@ -44,12 +40,10 @@ public abstract class AbstractXmlReader implements XMLReader { /** The properties map. */ private final HashMap properties; - /** - * Default constructor. - */ - public AbstractXmlReader() { - this.features = new HashMap(); - this.properties = new HashMap(); + /** Default constructor. */ + protected AbstractXmlReader() { + this.features = new HashMap<>(); + this.properties = new HashMap<>(); this.contentHandler = null; this.entityResolver = null; this.errorHandler = null; @@ -58,7 +52,7 @@ public AbstractXmlReader() { /** * Return the content handler. - * + * * @return The content handler. * @see XMLReader#getContentHandler() */ @@ -68,7 +62,7 @@ public ContentHandler getContentHandler() { /** * Return the DTD handler. - * + * * @return The DTD handler. * @see XMLReader#getDTDHandler() */ @@ -78,7 +72,7 @@ public DTDHandler getDTDHandler() { /** * Return the entity resolver. - * + * * @return The entity resolver. * @see XMLReader#getEntityResolver() */ @@ -88,7 +82,7 @@ public EntityResolver getEntityResolver() { /** * Return the error handler. - * + * * @return The error handler. * @see XMLReader#getErrorHandler() */ @@ -98,36 +92,31 @@ public ErrorHandler getErrorHandler() { /** * Returns the feature by name. - * - * @param name - * The feature name. + * + * @param name The feature name. * @return The feature. * @see XMLReader#getFeature(String) */ - public boolean getFeature(String name) throws SAXNotRecognizedException, - SAXNotSupportedException { + public boolean getFeature(String name) { final Boolean result = features.get(name); - return result == null ? false : result.booleanValue(); + return result != null && result; } /** * Returns the property by name. - * - * @param name - * The property name. + * + * @param name The property name. * @return The property. * @see XMLReader#getProperty(String) */ - public Object getProperty(String name) throws SAXNotRecognizedException, - SAXNotSupportedException { + public Object getProperty(String name) { return properties.get(name); } /** * Sets the content handler. - * - * @param contentHandler - * The content handler. + * + * @param contentHandler The content handler. * @see XMLReader#setContentHandler(ContentHandler) */ public void setContentHandler(ContentHandler contentHandler) { @@ -136,9 +125,8 @@ public void setContentHandler(ContentHandler contentHandler) { /** * Sets the DTD handler. - * - * @param handler - * The DTD handler. + * + * @param handler The DTD handler. * @see XMLReader#setDTDHandler(DTDHandler) */ public void setDTDHandler(DTDHandler handler) { @@ -147,9 +135,8 @@ public void setDTDHandler(DTDHandler handler) { /** * Sets the entity resolver. - * - * @param entityResolver - * The entity resolver. + * + * @param entityResolver The entity resolver. * @see XMLReader#setEntityResolver(EntityResolver) */ public void setEntityResolver(EntityResolver entityResolver) { @@ -158,9 +145,8 @@ public void setEntityResolver(EntityResolver entityResolver) { /** * Sets the error handler. - * - * @param errorHandler - * The error handler. + * + * @param errorHandler The error handler. * @see XMLReader#setErrorHandler(ErrorHandler) */ public void setErrorHandler(ErrorHandler errorHandler) { @@ -169,30 +155,23 @@ public void setErrorHandler(ErrorHandler errorHandler) { /** * Sets a feature. - * - * @param name - * The feature name. - * @param value - * The feature value. + * + * @param name The feature name. + * @param value The feature value. * @see XMLReader#setFeature(String, boolean) */ - public void setFeature(String name, boolean value) - throws SAXNotRecognizedException, SAXNotSupportedException { + public void setFeature(String name, boolean value) { this.features.put(name, value); } /** * Sets a property. - * - * @param name - * The property name. - * @param value - * The property value. + * + * @param name The property name. + * @param value The property value. * @see XMLReader#setProperty(String, Object) */ - public void setProperty(String name, Object value) - throws SAXNotRecognizedException, SAXNotSupportedException { + public void setProperty(String name, Object value) { this.properties.put(name, value); } - } diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/internal/ContextResolver.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/internal/ContextResolver.java index 749d6341f6..9d79f1f7e2 100644 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/internal/ContextResolver.java +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/internal/ContextResolver.java @@ -1,22 +1,18 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml.internal; import java.io.IOException; import java.util.logging.Level; - import javax.xml.transform.Source; -import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; import javax.xml.transform.stream.StreamSource; - import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -25,7 +21,7 @@ /** * URI resolver based on a Restlet Context instance. - * + * * @author Jerome Louvel */ public class ContextResolver implements URIResolver { @@ -34,9 +30,8 @@ public class ContextResolver implements URIResolver { /** * Constructor. - * - * @param context - * The Restlet context. + * + * @param context The Restlet context. */ public ContextResolver(Context context) { this.context = context; @@ -44,11 +39,10 @@ public ContextResolver(Context context) { /** * Resolves a target reference into a Source document. - * - * @see javax.xml.transform.URIResolver#resolve(java.lang.String, - * java.lang.String) + * + * @see javax.xml.transform.URIResolver#resolve(java.lang.String, java.lang.String) */ - public Source resolve(String href, String base) throws TransformerException { + public Source resolve(String href, String base) { Source result = null; if (this.context != null) { @@ -64,18 +58,18 @@ public Source resolve(String href, String base) throws TransformerException { } String targetUri = targetRef.getTargetRef().toString(); - Response response = this.context.getClientDispatcher().handle( - new Request(Method.GET, targetUri)); + Response response = + this.context.getClientDispatcher().handle(new Request(Method.GET, targetUri)); - if (response.getStatus().isSuccess() - && response.isEntityAvailable()) { + if (response.getStatus().isSuccess() && response.isEntityAvailable()) { try { result = new StreamSource(response.getEntity().getStream()); result.setSystemId(targetUri); } catch (IOException e) { - this.context.getLogger().log(Level.WARNING, - "I/O error while getting the response stream", e); + this.context + .getLogger() + .log(Level.WARNING, "I/O error while getting the response stream", e); } } } diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/package-info.java b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/package-info.java new file mode 100644 index 0000000000..327e97594c --- /dev/null +++ b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/package-info.java @@ -0,0 +1,8 @@ +/** + * Support for XML and XSLT representations. + * + * @since Restlet 2.0 + * @see User Guide + * - XML extension + */ +package org.restlet.ext.xml; diff --git a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/package.html b/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/package.html deleted file mode 100644 index b8b4669cdb..0000000000 --- a/org.restlet.ext.xml/src/main/java/org/restlet/ext/xml/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - -Support for XML and XSLT representations. - -@since Restlet 2.0 -@see User Guide - XML extension - - \ No newline at end of file diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/ResolvingTransformerTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/ResolvingTransformerTestCase.java index d59e1feade..839d6482cc 100644 --- a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/ResolvingTransformerTestCase.java +++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/ResolvingTransformerTestCase.java @@ -1,39 +1,51 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; -import org.junit.jupiter.api.Test; -import org.restlet.*; -import org.restlet.data.*; -import org.restlet.representation.Representation; -import org.restlet.representation.StringRepresentation; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; import javax.xml.transform.stream.StreamSource; -import java.io.*; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; - -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import org.restlet.Application; +import org.restlet.Component; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.data.LocalReference; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.data.Reference; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; /** * ResolvingTransformerTestCase tests the resolving aspects of the - * Transformer/TransformerRepresentation to guarantee proper functioning of the - * xsl :import, :include and document() features. - * + * Transformer/TransformerRepresentation to guarantee proper functioning of the xsl :import, + * :include and document() features. + * * @author Marc Portier */ -public class ResolvingTransformerTestCase { +class ResolvingTransformerTestCase { static class AssertResolvingHelper { @@ -46,9 +58,7 @@ static class AssertResolvingHelper { this.resolver = resolver; } - /** - * Asserts that the testUri resolves into the expectedUri - */ + /** Asserts that the testUri resolves into the expectedUri */ void assertResolving(String message, String testUri, String testData) throws TransformerException, IOException { StringBuilder data = new StringBuilder(); @@ -75,7 +85,8 @@ void assertResolving(String message, String testUri, String testData) dataReader.close(); } else { // TODO support other source implementations (namely sax-source implementations) - fail("test implementation currently doesn't handle other source (e.g. sax) implementations"); + fail( + "test implementation currently doesn't handle other source (e.g., sax) implementations"); } assertEquals(testData, data.toString(), message); } @@ -99,8 +110,7 @@ public Restlet createInboundRoot() { @Override public void handle(Request request, Response response) { String remainder = request.getResourceRef().getRemainingPart(); - Representation answer = SimpleUriMapApplication.this.uriMap - .get(remainder); + Representation answer = SimpleUriMapApplication.this.uriMap.get(remainder); if (answer != null) { response.setEntity(answer); @@ -110,15 +120,14 @@ public void handle(Request request, Response response) { } } - private final static String MY_BASEPATH; + private static final String MY_BASEPATH; - private final static String MY_NAME; + private static final String MY_NAME; - private final static String MY_PATH; + private static final String MY_PATH; static { - MY_PATH = ResolvingTransformerTestCase.class.getName() - .replace('.', '/'); + MY_PATH = ResolvingTransformerTestCase.class.getName().replace('.', '/'); final int lastPos = MY_PATH.lastIndexOf('/'); MY_NAME = MY_PATH.substring(lastPos); MY_BASEPATH = MY_PATH.substring(0, lastPos); @@ -126,22 +135,23 @@ public void handle(Request request, Response response) { // testing purely the resolver, no active transforming context (i.e., xslt engine) in this test @Test - public void testResolving() throws Exception { + void testResolving() throws Exception { Component comp = new Component(); // create an xml input representation - Representation xml = new StringRepresentation( - "", MediaType.TEXT_XML); + Representation xml = + new StringRepresentation("", MediaType.TEXT_XML); // create a xsl template representation - Representation xslt = new StringRepresentation( - "" - + "" - + "", - MediaType.TEXT_XML); + Representation xslt = + new StringRepresentation( + "" + + "" + + "", + MediaType.TEXT_XML); - TransformRepresentation transRep = new TransformRepresentation( - comp.getContext(), xml, xslt); + TransformRepresentation transRep = + new TransformRepresentation(comp.getContext(), xml, xslt); // create a test-stream representation to be returned when the correct // code is presented @@ -177,7 +187,7 @@ public void testResolving() throws Exception { // functional test in the actual xslt engine context @Test - public void testTransform() throws Exception { + void testTransform() throws Exception { Component comp = new Component(); comp.getClients().add(Protocol.CLAP); @@ -205,30 +215,39 @@ public void testTransform() throws Exception { String xsl2xmlLink = "./3rd.xml"; // and "/three/3rd.xml" would // too... - Representation xml3 = new StringRepresentation("" + thirdDocData, MediaType.TEXT_XML); - Representation xslt3 = new StringRepresentation( - "" - + "" - + " " - + " " - + " " - + " " + "", - MediaType.TEXT_XML); + Representation xml3 = + new StringRepresentation( + "" + thirdDocData, MediaType.TEXT_XML); + Representation xslt3 = + new StringRepresentation( + "" + + "" + + " " + + " " + + " " + + " " + + "", + MediaType.TEXT_XML); SimpleUriMapApplication thirdLevel = new SimpleUriMapApplication(); thirdLevel.add("3rd.xsl", xslt3); thirdLevel.add("3rd.xml", xml3); comp.getInternalRouter().attach("/three/", thirdLevel); // xml In - Representation xmlIn = new StringRepresentation( - "drie"); + Representation xmlIn = + new StringRepresentation( + "drie"); // xslOne - Reference xsltOneRef = new LocalReference("clap://thread/" + MY_BASEPATH + "/xslt/one/1st.xsl"); - Representation xsltOne = comp.getContext().getClientDispatcher() - .handle(new Request(Method.GET, xsltOneRef)).getEntity(); - TransformRepresentation tr = new TransformRepresentation( - comp.getContext(), xmlIn, xsltOne); + Reference xsltOneRef = + new LocalReference("clap://thread/" + MY_BASEPATH + "/xslt/one/1st.xsl"); + Representation xsltOne = + comp.getContext() + .getClientDispatcher() + .handle(new Request(Method.GET, xsltOneRef)) + .getEntity(); + TransformRepresentation tr = new TransformRepresentation(comp.getContext(), xmlIn, xsltOne); // TODO transformer output should go to SAX! The sax-event-stream should // then be fed into a DOMBuilder @@ -239,9 +258,10 @@ public void testTransform() throws Exception { tr.write(out); String xmlOut = out.toString(); - String expectedResult = "1st2nd" - + thirdDocData + ""; - assertEquals(expectedResult, - xmlOut, "xslt result doesn't match expectations"); + String expectedResult = + "1st2nd" + + thirdDocData + + ""; + assertEquals(expectedResult, xmlOut, "xslt result doesn't match expectations"); } } diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformRepresentationTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformRepresentationTestCase.java index 95c0883610..8bd92bd79f 100644 --- a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformRepresentationTestCase.java +++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformRepresentationTestCase.java @@ -1,73 +1,77 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test case for the {@link TransformRepresentation} class. * * @author Jerome Louvel */ -public class TransformRepresentationTestCase { +class TransformRepresentationTestCase { final String output1 = "cust123"; final String output2 = "cust123"; // Create a source XML document - final Representation source = new StringRepresentation( - "" + "" - + "" + "" - + "23.45" + "" + "", - MediaType.TEXT_XML); + final Representation source = + new StringRepresentation( + "" + + "" + + "" + + "" + + "23.45" + + "" + + "", + MediaType.TEXT_XML); // Create a first transform XSLT sheet - final Representation xslt1 = new StringRepresentation( - "" - + "" - + "" - + "" - + "" - + "", MediaType.TEXT_XML); + final Representation xslt1 = + new StringRepresentation( + "" + + "" + + "" + + "" + + "" + + "", + MediaType.TEXT_XML); // Create a second transform XSLT sheet - final Representation xslt2 = new StringRepresentation( - "" - + "" - + "" - + "" - + "" + "", - MediaType.TEXT_XML); + final Representation xslt2 = + new StringRepresentation( + "" + + "" + + "" + + "" + + "" + + "", + MediaType.TEXT_XML); @Test - public void testSingleTransform() throws Exception { - TransformRepresentation tr1 = new TransformRepresentation(this.source, - this.xslt1); + void testSingleTransform() throws Exception { + TransformRepresentation tr1 = new TransformRepresentation(this.source, this.xslt1); final String result = tr1.getText(); assertEquals(this.output1, result); } @Test - public void testDoubleTransform() throws Exception { - TransformRepresentation tr1 = new TransformRepresentation(this.source, - this.xslt1); - TransformRepresentation tr2 = new TransformRepresentation(tr1, - this.xslt2); + void testDoubleTransform() throws Exception { + TransformRepresentation tr1 = new TransformRepresentation(this.source, this.xslt1); + TransformRepresentation tr2 = new TransformRepresentation(tr1, this.xslt2); final String result = tr2.getText(); assertEquals(this.output2, result); } - } diff --git a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformerTestCase.java b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformerTestCase.java index 5ee6fa6157..71589705ea 100644 --- a/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformerTestCase.java +++ b/org.restlet.ext.xml/src/test/java/org/restlet/ext/xml/TransformerTestCase.java @@ -1,32 +1,30 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.ext.xml; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; import org.junit.jupiter.api.Test; import org.restlet.Component; import org.restlet.data.MediaType; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - /** * Test case for the Transformer class. - * + * * @author Jerome Louvel */ -public class TransformerTestCase { +class TransformerTestCase { static class FailureTracker { final StringBuffer trackedMessages = new StringBuffer(); @@ -50,45 +48,53 @@ void trackFailure(String message, int index, Throwable e) { final String output = "cust12323.45"; // Create a source XML document - final Representation source = new StringRepresentation( - "" + "" - + "" + "" - + "23.45" + "" + "", - MediaType.TEXT_XML); + final Representation source = + new StringRepresentation( + "" + + "" + + "" + + "" + + "23.45" + + "" + + "", + MediaType.TEXT_XML); // Create a transform XSLT sheet - final Representation xslt = new StringRepresentation( - "" - + "" - + "" - + "" - + "" + "", - MediaType.TEXT_XML); + final Representation xslt = + new StringRepresentation( + "" + + "" + + "" + + "" + + "" + + "", + MediaType.TEXT_XML); @Test - public void parallelTestTransform() { + void parallelTestTransform() { Component comp = new Component(); - final TransformRepresentation tr = new TransformRepresentation( - comp.getContext(), this.source, this.xslt); + final TransformRepresentation tr = + new TransformRepresentation(comp.getContext(), this.source, this.xslt); final FailureTracker tracker = new FailureTracker(); final int testVolume = 500; final Thread[] parallelTransform = new Thread[testVolume]; for (int i = 0; i < parallelTransform.length; i++) { final int index = i; - parallelTransform[i] = new Thread() { - - @Override - public void run() { - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - tr.write(out); - final String result = out.toString(); - assertEquals(TransformerTestCase.this.output, result); - } catch (IOException e) { - tracker.trackFailure("Exception during write in thread ", index, e); - } - } - }; + parallelTransform[i] = + new Thread() { + + @Override + public void run() { + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + tr.write(out); + final String result = out.toString(); + assertEquals(TransformerTestCase.this.output, result); + } catch (IOException e) { + tracker.trackFailure("Exception during write in thread ", index, e); + } + } + }; } for (final Thread pt : parallelTransform) { @@ -99,11 +105,10 @@ public void run() { } @Test - public void testTransform() throws Exception { + void testTransform() throws Exception { final Transformer transformer = new Transformer(Transformer.MODE_REQUEST, this.xslt); final String result = transformer.transform(this.source).getText(); assertEquals(this.output, result); } - } diff --git a/org.restlet/src/main/java/org/restlet/Application.java b/org.restlet/src/main/java/org/restlet/Application.java index 29715c4108..611a01deef 100644 --- a/org.restlet/src/main/java/org/restlet/Application.java +++ b/org.restlet/src/main/java/org/restlet/Application.java @@ -1,14 +1,17 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Filter; +import java.util.logging.Level; import org.restlet.engine.Engine; import org.restlet.engine.application.ApplicationHelper; import org.restlet.engine.resource.AnnotationUtils; @@ -16,573 +19,570 @@ import org.restlet.routing.Router; import org.restlet.routing.VirtualHost; import org.restlet.security.Role; -import org.restlet.service.*; +import org.restlet.service.ConnectorService; +import org.restlet.service.ConnegService; +import org.restlet.service.ConverterService; +import org.restlet.service.DecoderService; +import org.restlet.service.EncoderService; +import org.restlet.service.MetadataService; +import org.restlet.service.RangeService; +import org.restlet.service.StatusService; +import org.restlet.service.TunnelService; import org.restlet.util.ServiceList; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.logging.Filter; -import java.util.logging.Level; - /** - * Restlet managing a coherent set of resources and services. Applications are - * guaranteed to receive calls with their base reference set relatively to the - * {@link VirtualHost} that served them. This class is both a descriptor able to - * create the root Restlet and the actual Restlet that can be attached to one or - * more VirtualHost instances.
+ * Restlet managing a coherent set of resources and services. Applications are guaranteed to receive + * calls with their base reference set relatively to the {@link VirtualHost} that served them. This + * class is both a descriptor able to create the root Restlet and the actual Restlet that can be + * attached to one or more VirtualHost instances.
*
- * Applications also have many useful services associated. Most are enabled by - * default and are available as properties that can be eventually overridden: + * Applications also have many useful services associated. Most are enabled by default and are + * available as properties that can be eventually overridden: + * *

    - *
  • "connectorService" to declare necessary client and server - * connectors.
  • - *
  • "converterService" to convert between regular objects and - * representations.
  • - *
  • "decoderService" to automatically decode or uncompress received entities. - *
  • - *
  • "encoderService" to automatically encode or compress sent entities - * (disabled by default).
  • - *
  • "metadataService" to provide access to metadata and their associated - * extension names.
  • - *
  • "rangeService" to automatically exposes ranges of response entities.
  • - *
  • "statusService" to provide common representations for exception - * status.
  • - *
  • "taskService" to run tasks asynchronously (disabled by default).
  • - *
  • "tunnelService" to tunnel method names or client preferences via query - * parameters.
  • + *
  • "connectorService" to declare necessary client and server connectors. + *
  • "converterService" to convert between regular objects and representations. + *
  • "decoderService" to automatically decode or uncompress received entities. + *
  • "encoderService" to automatically encode or compress sent entities (disabled by default). + *
  • "metadataService" to provide access to metadata and their associated extension names. + *
  • "rangeService" to automatically exposes ranges of response entities. + *
  • "statusService" to provide common representations for exception status. + *
  • "taskService" to run tasks asynchronously (disabled by default). + *
  • "tunnelService" to tunnel method names or client preferences via query parameters. *
- * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class Application extends Restlet { - private static final ThreadLocal CURRENT = new ThreadLocal<>(); - - /** - * This variable is stored internally as a thread local variable and updated - * each time a call enters an application. - * - * Warning: this method should only be used under duress. You should by default - * prefer getting the current application using methods such as - * {@link org.restlet.resource.Resource#getApplication()} - * - * @return The current context. - */ - public static Application getCurrent() { - return CURRENT.get(); - } - - /** - * Sets the context associated with the current thread. - * - * @param application The thread's context. - */ - public static void setCurrent(Application application) { - CURRENT.set(application); - } - - /** Indicates if the debugging mode is enabled. */ - private volatile boolean debugging; - - /** The helper provided by the implementation. */ - private volatile ApplicationHelper helper; - - /** The inbound root Restlet. */ - private volatile Restlet inboundRoot; - - /** The outbound root Restlet. */ - private volatile Restlet outboundRoot; - - /** The modifiable list of roles. */ - private final List roles; - - /** The list of services. */ - private final ServiceList services; - - /** - * Constructor. Note this constructor is convenient because you don't have to - * provide a context like for {@link #Application(Context)}. Therefore, the - * context will initially be null. It's only when you attach the application to - * a virtual host via one of its attach*() methods that a proper context will be - * set. - */ - public Application() { - this(null); - } - - /** - * Constructor. - * - * @param context The context to use based on parent component context. This - * context should be created using the - * {@link Context#createChildContext()} method to ensure a proper - * isolation with the other applications. - */ - public Application(Context context) { - super(context); - - if (Engine.getInstance() != null) { - this.helper = new ApplicationHelper(this); - this.helper.setContext(context); - } - - ConnegService connegService = new ConnegService(); - ConverterService converterService = new ConverterService(); - MetadataService metadataService = new MetadataService(); - - this.debugging = false; - this.outboundRoot = null; - this.inboundRoot = null; - this.roles = new CopyOnWriteArrayList(); - this.services = new ServiceList(context); - this.services.add(new TunnelService(true, true)); - this.services.add(new StatusService(true, converterService, metadataService, connegService)); - this.services.add(new DecoderService()); - this.services.add(new EncoderService(false)); - this.services.add(new RangeService()); - this.services.add(new ConnectorService()); - this.services.add(connegService); - this.services.add(converterService); - this.services.add(metadataService); - - this.services.add(new org.restlet.service.TaskService(false)); - } - - /** - * Creates a inbound root Restlet that will receive all incoming calls. In - * general, instances of Router, Filter or Finder classes will be used as - * initial application Restlet. The default implementation returns null by - * default. This method is intended to be overridden by subclasses. - * - * @return The inbound root Restlet. - */ - public Restlet createInboundRoot() { - return null; - } - - /** - * Creates a outbound root Restlet that will receive all outgoing calls from - * ClientResource. In general, instances of {@link Router} and {@link Filter} - * classes will be used. The default implementation returns a Restlet giving - * access to the the outbound service layer and finally to the - * {@link Context#getClientDispatcher()}. - *

- * This method is intended to be overridden by subclasses but in order to - * benefit from the outbound service filtering layer, the original outbound root - * must be careful attached again at the end of the user filtering layer. - * - * @return The outbound root Restlet. - */ - public Restlet createOutboundRoot() { - return getHelper().getFirstOutboundFilter(); - } - - /** - * Returns the connector service. The service is enabled by default. - * - * @return The connector service. - */ - public ConnectorService getConnectorService() { - return getServices().get(ConnectorService.class); - } - - /** - * Returns the content negotiation service. The service is enabled by default. - * - * @return The content negotiation service. - */ - public ConnegService getConnegService() { - return getServices().get(ConnegService.class); - } - - /** - * Returns the converter service. The service is enabled by default. - * - * @return The converter service. - */ - public ConverterService getConverterService() { - return getServices().get(ConverterService.class); - } - - /** - * Returns the decoder service. The service is enabled by default. - * - * @return The decoder service. - */ - public DecoderService getDecoderService() { - return getServices().get(DecoderService.class); - } - - /** - * Returns the encoder service. The service is disabled by default. - * - * @return The encoder service. - */ - public EncoderService getEncoderService() { - return getServices().get(EncoderService.class); - } - - /** - * Returns the helper provided by the implementation. - * - * @return The helper provided by the implementation. - */ - private ApplicationHelper getHelper() { - return this.helper; - } - - /** - * Returns the inbound root Restlet. - * - * @return The inbound root Restlet. - */ - public Restlet getInboundRoot() { - if (this.inboundRoot == null) { - synchronized (this) { - if (this.inboundRoot == null) { - this.inboundRoot = createInboundRoot(); - } - } - } - - return this.inboundRoot; - } - - /** - * Returns the metadata service. The service is enabled by default. - * - * @return The metadata service. - */ - public MetadataService getMetadataService() { - return getServices().get(MetadataService.class); - } - - /** - * Returns the outbound root Restlet. - * - * @return The outbound root Restlet. - */ - public Restlet getOutboundRoot() { - if (this.outboundRoot == null) { - synchronized (this) { - if (this.outboundRoot == null) { - this.outboundRoot = createOutboundRoot(); - } - } - } - - return this.outboundRoot; - } - - /** - * Returns the range service. - * - * @return The range service. - */ - public RangeService getRangeService() { - return getServices().get(RangeService.class); - } - - /** - * Returns the role associated to the given name. - * - * @param name The name of the role to find. - * @return The role matched or null. - */ - public Role getRole(String name) { - for (Role role : getRoles()) { - if (role.getName().equals(name)) { - return role; - } - } - - return null; - } - - /** - * Returns the modifiable list of roles. - * - * @return The modifiable list of roles. - */ - public List getRoles() { - return roles; - } - - /** - * Returns the modifiable list of services. - * - * @return The modifiable list of services. - */ - public ServiceList getServices() { - return services; - } - - /** - * Returns the status service. The service is enabled by default. - * - * @return The status service. - */ - public StatusService getStatusService() { - return getServices().get(StatusService.class); - } - - /** - * Returns the tunnel service. The service is enabled by default. - * - * @return The tunnel service. - */ - public TunnelService getTunnelService() { - return getServices().get(TunnelService.class); - } - - @Override - public void handle(Request request, Response response) { - super.handle(request, response); - - if (getHelper() != null) { - getHelper().handle(request, response); - } - } - - /** - * Indicates if the debugging mode is enabled. True by default. - * - * @return True if the debugging mode is enabled. - */ - public boolean isDebugging() { - return debugging; - } - - /** - * Sets the connector service. - * - * @param connectorService The connector service. - */ - public void setConnectorService(ConnectorService connectorService) { - getServices().set(connectorService); - } - - /** - * Sets the content negotiation service. - * - * @param connegService The content negotiation service. - */ - public void setConnegService(ConnegService connegService) { - getServices().set(connegService); - } - - @Override - public void setContext(Context context) { - super.setContext(context); - getHelper().setContext(context); - getServices().setContext(context); - } - - /** - * Sets the converter service. - * - * @param converterService The converter service. - */ - public void setConverterService(ConverterService converterService) { - getServices().set(converterService); - } - - /** - * Indicates if the debugging mode is enabled. - * - * @param debugging True if the debugging mode is enabled. - */ - public void setDebugging(boolean debugging) { - this.debugging = debugging; - } - - /** - * Sets the decoder service. - * - * @param decoderService The decoder service. - */ - public void setDecoderService(DecoderService decoderService) { - getServices().set(decoderService); - } - - /** - * Sets the encoder service. - * - * @param encoderService The encoder service. - */ - public void setEncoderService(EncoderService encoderService) { - getServices().set(encoderService); - } - - /** - * Sets the inbound root Resource class. - * - * @param inboundRootClass The inbound root Resource class. - */ - public synchronized void setInboundRoot(Class inboundRootClass) { - setInboundRoot(createFinder(inboundRootClass)); - } - - /** - * Sets the inbound root Restlet. - * - * @param inboundRoot The inbound root Restlet. - */ - public synchronized void setInboundRoot(Restlet inboundRoot) { - this.inboundRoot = inboundRoot; - - if ((inboundRoot != null) && (inboundRoot.getContext() == null)) { - inboundRoot.setContext(getContext()); - } - } - - /** - * Sets the metadata service. - * - * @param metadataService The metadata service. - */ - public void setMetadataService(MetadataService metadataService) { - getServices().set(metadataService); - } - - /** - * Sets the outbound root Resource class. - * - * @param outboundRootClass The client root {@link ServerResource} subclass. - */ - public synchronized void setOutboundRoot(Class outboundRootClass) { - setOutboundRoot(createFinder(outboundRootClass)); - } - - /** - * Sets the outbound root Restlet. - * - * @param outboundRoot The outbound root Restlet. - */ - public synchronized void setOutboundRoot(Restlet outboundRoot) { - this.outboundRoot = outboundRoot; - - if ((outboundRoot != null) && (outboundRoot.getContext() == null)) { - outboundRoot.setContext(getContext()); - } - } - - /** - * Sets the range service. - * - * @param rangeService The range service. - */ - public void setRangeService(RangeService rangeService) { - getServices().set(rangeService); - } - - /** - * Sets the modifiable list of roles. This method clears the current list and - * adds all entries in the parameter list. - * - * @param roles A list of roles. - */ - public void setRoles(List roles) { - synchronized (getRoles()) { - if (roles != getRoles()) { - getRoles().clear(); - - if (roles != null) { - getRoles().addAll(roles); - } - } - } - } - - /** - * Sets the status service. - * - * @param statusService The status service. - */ - public void setStatusService(StatusService statusService) { - getServices().set(statusService); - } - - /** - * Sets the task service. - * - * @param taskService The task service. - */ - public void setTaskService(org.restlet.service.TaskService taskService) { - getServices().set(taskService); - } - - /** - * Sets the tunnel service. - * - * @param tunnelService The tunnel service. - */ - public void setTunnelService(TunnelService tunnelService) { - getServices().set(tunnelService); - } - - /** - * Starts the application, all the enabled associated services then the inbound - * and outbound roots. - */ - @Override - public synchronized void start() throws Exception { - if (isStopped()) { - if (isDebugging()) { - getLogger().log(Level.INFO, "Starting " + getClass().getName() + " application in debug mode"); - } else { - getLogger().log(Level.INFO, "Starting " + getClass().getName() + " application"); - } - - if (getHelper() != null) { - getHelper().start(); - } - - getServices().start(); - - if (getInboundRoot() != null) { - getInboundRoot().start(); - } - - if (getOutboundRoot() != null) { - getOutboundRoot().start(); - } - - // Must be invoked as a last step - super.start(); - } - } - - /** - * Stops the application, the inbound and outbound roots then all the enabled - * associated services. Finally, it clears the internal cache of annotations. - */ - @Override - public synchronized void stop() throws Exception { - if (isStarted()) { - // Must be invoked as a first step - super.stop(); - - if (getOutboundRoot() != null) { - getOutboundRoot().stop(); - } - - if (getInboundRoot() != null) { - getInboundRoot().stop(); - } - - getServices().stop(); - - if (getHelper() != null) { - getHelper().stop(); - } - - // Clear the annotations cache - AnnotationUtils.getInstance().clearCache(); - } - } - + private static final ThreadLocal CURRENT = new ThreadLocal<>(); + + /** + * This variable is stored internally as a thread local variable and updated each time a call + * enters an application. + * + *

Warning: this method should only be used under duress. You should by default prefer + * getting the current application using methods such as {@link + * org.restlet.resource.Resource#getApplication()} + * + * @return The current context. + */ + public static Application getCurrent() { + return CURRENT.get(); + } + + /** + * Sets the context associated with the current thread. + * + * @param application The thread's context. + */ + public static void setCurrent(Application application) { + CURRENT.set(application); + } + + /** Indicates if the debugging mode is enabled. */ + private volatile boolean debugging; + + /** The helper provided by the implementation. */ + private volatile ApplicationHelper helper; + + /** The inbound root Restlet. */ + private volatile Restlet inboundRoot; + + /** The outbound root Restlet. */ + private volatile Restlet outboundRoot; + + /** The modifiable list of roles. */ + private final List roles; + + /** The list of services. */ + private final ServiceList services; + + /** + * Constructor. Note this constructor is convenient because you don't have to provide a context + * like for {@link #Application(Context)}. Therefore, the context will initially be null. It's + * only when you attach the application to a virtual host via one of its attach*() methods that + * a proper context will be set. + */ + public Application() { + this(null); + } + + /** + * Constructor. + * + * @param context The context to use based on parent component context. This context should be + * created using the {@link Context#createChildContext()} method to ensure proper isolation + * with the other applications. + */ + public Application(Context context) { + super(context); + + if (Engine.getInstance() != null) { + this.helper = new ApplicationHelper(this); + this.helper.setContext(context); + } + + ConnegService connegService = new ConnegService(); + ConverterService converterService = new ConverterService(); + MetadataService metadataService = new MetadataService(); + + this.debugging = false; + this.outboundRoot = null; + this.inboundRoot = null; + this.roles = new CopyOnWriteArrayList<>(); + this.services = new ServiceList(context); + this.services.add(new TunnelService(true, true)); + this.services.add( + new StatusService(true, converterService, metadataService, connegService)); + this.services.add(new DecoderService()); + this.services.add(new EncoderService(false)); + this.services.add(new RangeService()); + this.services.add(new ConnectorService()); + this.services.add(connegService); + this.services.add(converterService); + this.services.add(metadataService); + + this.services.add(new org.restlet.service.TaskService(false)); + } + + /** + * Creates an inbound root Restlet that will receive all incoming calls. In general, instances + * of Router, Filter, or Finder classes will be used as initial application Restlet. The default + * implementation returns null by default. This method is intended to be overridden by + * subclasses. + * + * @return The inbound root Restlet. + */ + public Restlet createInboundRoot() { + return null; + } + + /** + * Creates an outbound root Restlet that will receive all outgoing calls from ClientResource. In + * general, instances of {@link Router} and {@link Filter} classes will be used. The default + * implementation returns a Restlet giving access to the outbound service layer and finally to + * the {@link Context#getClientDispatcher()}. + * + *

This method is intended to be overridden by subclasses, but to benefit from the outbound + * service filtering layer, the original outbound root must be carefully attached again at the + * end of the user filtering layer. + * + * @return The outbound root Restlet. + */ + public Restlet createOutboundRoot() { + return getHelper().getFirstOutboundFilter(); + } + + /** + * Returns the connector service. The service is enabled by default. + * + * @return The connector service. + */ + public ConnectorService getConnectorService() { + return getServices().get(ConnectorService.class); + } + + /** + * Returns the content negotiation service. The service is enabled by default. + * + * @return The content negotiation service. + */ + public ConnegService getConnegService() { + return getServices().get(ConnegService.class); + } + + /** + * Returns the converter service. The service is enabled by default. + * + * @return The converter service. + */ + public ConverterService getConverterService() { + return getServices().get(ConverterService.class); + } + + /** + * Returns the decoder service. The service is enabled by default. + * + * @return The decoder service. + */ + public DecoderService getDecoderService() { + return getServices().get(DecoderService.class); + } + + /** + * Returns the encoder service. The service is disabled by default. + * + * @return The encoder service. + */ + public EncoderService getEncoderService() { + return getServices().get(EncoderService.class); + } + + /** + * Returns the helper provided by the implementation. + * + * @return The helper provided by the implementation. + */ + private ApplicationHelper getHelper() { + return this.helper; + } + + /** + * Returns the inbound root Restlet. + * + * @return The inbound root Restlet. + */ + public Restlet getInboundRoot() { + if (this.inboundRoot == null) { + synchronized (this) { + if (this.inboundRoot == null) { + this.inboundRoot = createInboundRoot(); + } + } + } + + return this.inboundRoot; + } + + /** + * Returns the metadata service. The service is enabled by default. + * + * @return The metadata service. + */ + public MetadataService getMetadataService() { + return getServices().get(MetadataService.class); + } + + /** + * Returns the outbound root Restlet. + * + * @return The outbound root Restlet. + */ + public Restlet getOutboundRoot() { + if (this.outboundRoot == null) { + synchronized (this) { + if (this.outboundRoot == null) { + this.outboundRoot = createOutboundRoot(); + } + } + } + + return this.outboundRoot; + } + + /** + * Returns the range service. + * + * @return The range service. + */ + public RangeService getRangeService() { + return getServices().get(RangeService.class); + } + + /** + * Returns the role associated with the given name. + * + * @param name The name of the role to find. + * @return The role matched or null. + */ + public Role getRole(String name) { + for (Role role : getRoles()) { + if (role.getName().equals(name)) { + return role; + } + } + + return null; + } + + /** + * Returns the modifiable list of roles. + * + * @return The modifiable list of roles. + */ + public List getRoles() { + return roles; + } + + /** + * Returns the modifiable list of services. + * + * @return The modifiable list of services. + */ + public ServiceList getServices() { + return services; + } + + /** + * Returns the status service. The service is enabled by default. + * + * @return The status service. + */ + public StatusService getStatusService() { + return getServices().get(StatusService.class); + } + + /** + * Returns the tunnel service. The service is enabled by default. + * + * @return The tunnel service. + */ + public TunnelService getTunnelService() { + return getServices().get(TunnelService.class); + } + + @Override + public void handle(Request request, Response response) { + super.handle(request, response); + + if (getHelper() != null) { + getHelper().handle(request, response); + } + } + + /** + * Indicates if the debugging mode is enabled. True by default. + * + * @return True if the debugging mode is enabled. + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Sets the connector service. + * + * @param connectorService The connector service. + */ + public void setConnectorService(ConnectorService connectorService) { + getServices().set(connectorService); + } + + /** + * Sets the content negotiation service. + * + * @param connegService The content negotiation service. + */ + public void setConnegService(ConnegService connegService) { + getServices().set(connegService); + } + + @Override + public void setContext(Context context) { + super.setContext(context); + getHelper().setContext(context); + getServices().setContext(context); + } + + /** + * Sets the converter service. + * + * @param converterService The converter service. + */ + public void setConverterService(ConverterService converterService) { + getServices().set(converterService); + } + + /** + * Indicates if the debugging mode is enabled. + * + * @param debugging True if the debugging mode is enabled. + */ + public void setDebugging(boolean debugging) { + this.debugging = debugging; + } + + /** + * Sets the decoder service. + * + * @param decoderService The decoder service. + */ + public void setDecoderService(DecoderService decoderService) { + getServices().set(decoderService); + } + + /** + * Sets the encoder service. + * + * @param encoderService The encoder service. + */ + public void setEncoderService(EncoderService encoderService) { + getServices().set(encoderService); + } + + /** + * Sets the inbound root Resource class. + * + * @param inboundRootClass The inbound root Resource class. + */ + public synchronized void setInboundRoot(Class inboundRootClass) { + setInboundRoot(createFinder(inboundRootClass)); + } + + /** + * Sets the inbound root Restlet. + * + * @param inboundRoot The inbound root Restlet. + */ + public synchronized void setInboundRoot(Restlet inboundRoot) { + this.inboundRoot = inboundRoot; + + if ((inboundRoot != null) && (inboundRoot.getContext() == null)) { + inboundRoot.setContext(getContext()); + } + } + + /** + * Sets the metadata service. + * + * @param metadataService The metadata service. + */ + public void setMetadataService(MetadataService metadataService) { + getServices().set(metadataService); + } + + /** + * Sets the outbound root Resource class. + * + * @param outboundRootClass The client root {@link ServerResource} subclass. + */ + public synchronized void setOutboundRoot(Class outboundRootClass) { + setOutboundRoot(createFinder(outboundRootClass)); + } + + /** + * Sets the outbound root Restlet. + * + * @param outboundRoot The outbound root Restlet. + */ + public synchronized void setOutboundRoot(Restlet outboundRoot) { + this.outboundRoot = outboundRoot; + + if ((outboundRoot != null) && (outboundRoot.getContext() == null)) { + outboundRoot.setContext(getContext()); + } + } + + /** + * Sets the range service. + * + * @param rangeService The range service. + */ + public void setRangeService(RangeService rangeService) { + getServices().set(rangeService); + } + + /** + * Sets the modifiable list of roles. This method clears the current list and adds all entries + * in the parameter list. + * + * @param roles A list of roles. + */ + public void setRoles(List roles) { + synchronized (getRoles()) { + if (roles != getRoles()) { + getRoles().clear(); + + if (roles != null) { + getRoles().addAll(roles); + } + } + } + } + + /** + * Sets the status service. + * + * @param statusService The status service. + */ + public void setStatusService(StatusService statusService) { + getServices().set(statusService); + } + + /** + * Sets the task service. + * + * @param taskService The task service. + */ + public void setTaskService(org.restlet.service.TaskService taskService) { + getServices().set(taskService); + } + + /** + * Sets the tunnel service. + * + * @param tunnelService The tunnel service. + */ + public void setTunnelService(TunnelService tunnelService) { + getServices().set(tunnelService); + } + + /** + * Starts the application, all the enabled associated services then the inbound and outbound + * roots. + */ + @Override + public synchronized void start() throws Exception { + if (isStopped()) { + if (isDebugging()) { + getLogger() + .log( + Level.INFO, + "Starting {0} application in debug mode", + getClass().getName()); + } else { + getLogger().log(Level.INFO, "Starting {0} application", getClass().getName()); + } + + if (getHelper() != null) { + getHelper().start(); + } + + getServices().start(); + + if (getInboundRoot() != null) { + getInboundRoot().start(); + } + + if (getOutboundRoot() != null) { + getOutboundRoot().start(); + } + + // Must be invoked as a last step + super.start(); + } + } + + /** + * Stops the application, the inbound and outbound roots, then all the enabled associated + * services. Finally, it clears the internal cache of annotations. + */ + @Override + public synchronized void stop() throws Exception { + if (isStarted()) { + // Must be invoked as a first step + super.stop(); + + if (getOutboundRoot() != null) { + getOutboundRoot().stop(); + } + + if (getInboundRoot() != null) { + getInboundRoot().stop(); + } + + getServices().stop(); + + if (getHelper() != null) { + getHelper().stop(); + } + + // Clear the annotations cache + AnnotationUtils.getInstance().clearCache(); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/Client.java b/org.restlet/src/main/java/org/restlet/Client.java index a5ed7819fa..79d008356b 100644 --- a/org.restlet/src/main/java/org/restlet/Client.java +++ b/org.restlet/src/main/java/org/restlet/Client.java @@ -1,35 +1,32 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; +import java.util.Arrays; +import java.util.List; import org.restlet.data.Protocol; import org.restlet.data.Status; import org.restlet.engine.Engine; import org.restlet.engine.RestletHelper; -import java.util.Arrays; -import java.util.List; - /** - * Connector acting as a generic client. It internally uses one of the available - * connector helpers registered with the Restlet engine.
+ * Connector acting as a generic client. It internally uses one of the available connector helpers + * registered with the Restlet engine.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables.
+ * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables.
*
- * For advanced cases, it is possible to obtained the wrapped - * {@link RestletHelper} instance that is used by this client to handle the - * calls via the "org.restlet.engine.helper" attribute stored in the - * {@link Context} object. - * + * For advanced cases, it is possible to obtain the wrapped {@link RestletHelper} instance that is + * used by this client to handle the calls via the "org.restlet.engine.helper" attribute stored in + * the {@link Context} object. + * * @author Jerome Louvel */ public class Client extends Connector { @@ -39,8 +36,8 @@ public class Client extends Connector { /** * Constructor. - * - * @param context The context. + * + * @param context The context. * @param protocols The connector protocols. */ public Client(Context context, List protocols) { @@ -49,19 +46,17 @@ public Client(Context context, List protocols) { /** * Constructor. - * - * @param context The context. - * @param protocols The connector protocols. + * + * @param context The context. + * @param protocols The connector protocols. * @param helperClass Optional helper class name. */ - public Client(Context context, List protocols, - String helperClass) { + public Client(Context context, List protocols, String helperClass) { super(context, protocols); if ((protocols != null) && !protocols.isEmpty()) { if (Engine.getInstance() != null) { - this.helper = Engine.getInstance().createHelper(this, - helperClass); + this.helper = Engine.getInstance().createHelper(this, helperClass); } else { this.helper = null; } @@ -70,25 +65,23 @@ public Client(Context context, List protocols, } if (context != null && this.helper != null) { - context.getAttributes().put("org.restlet.engine.helper", - this.helper); + context.getAttributes().put("org.restlet.engine.helper", this.helper); } } /** * Constructor. - * - * @param context The context. + * + * @param context The context. * @param protocols The connector protocols. */ public Client(Context context, Protocol... protocols) { - this(context, (protocols == null) ? null : Arrays.asList(protocols), - null); + this(context, (protocols == null) ? null : Arrays.asList(protocols), null); } /** * Constructor. - * + * * @param protocols The connector protocols. */ public Client(List protocols) { @@ -97,7 +90,7 @@ public Client(List protocols) { /** * Constructor. - * + * * @param protocols The connector protocols. */ public Client(Protocol... protocols) { @@ -106,7 +99,7 @@ public Client(Protocol... protocols) { /** * Constructor. - * + * * @param protocolName The connector protocol. */ public Client(String protocolName) { @@ -115,7 +108,7 @@ public Client(String protocolName) { /** * Returns the helper provided by the implementation. - * + * * @return The helper provided by the implementation. */ private RestletHelper getHelper() { @@ -129,16 +122,19 @@ public void handle(Request request, Response response) { if (getHelper() != null) { getHelper().handle(request, response); } else { - String sb = "No available client connector supports the required protocol: " + - "'" + request.getProtocol().getName() + "'." + - " Please add the JAR of a matching connector to your classpath."; + String sb = + "No available client connector supports the required protocol: " + + "'" + + request.getProtocol().getName() + + "'." + + " Please add the JAR of a matching connector to your classpath."; response.setStatus(Status.CONNECTOR_ERROR_INTERNAL, sb); } } /** * Indicates the underlying connector helper is available. - * + * * @return True if the underlying connector helper is available. */ @Override @@ -169,5 +165,4 @@ public synchronized void stop() throws Exception { } } } - } diff --git a/org.restlet/src/main/java/org/restlet/Component.java b/org.restlet/src/main/java/org/restlet/Component.java index b068b83841..45d7d659bc 100644 --- a/org.restlet/src/main/java/org/restlet/Component.java +++ b/org.restlet/src/main/java/org/restlet/Component.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import org.restlet.engine.Engine; import org.restlet.engine.component.ComponentHelper; import org.restlet.engine.component.InternalRouter; @@ -22,567 +23,549 @@ import org.restlet.util.ServerList; import org.restlet.util.ServiceList; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - /** - * Restlet managing a set of {@link Connector}s, {@link VirtualHost}s, - * {@link Service}s and {@link Application}s. Applications are expected to be - * directly attached to virtual hosts or to the internal router (see RIAP - * pseudo-protocol for usage). Components also expose several services: access - * logging and status setting.
+ * Restlet managing a set of {@link Connector}s, {@link VirtualHost}s, {@link Service}s and {@link + * Application}s. Applications are expected to be directly attached to virtual hosts or to the + * internal router (see RIAP pseudo-protocol for usage). Components also expose several services: + * access logging and status setting.
*
- * From an architectural point of view, here is the REST definition: "A - * component is an abstract unit of software instructions and internal state - * that provides a transformation of data via its interface." Roy T. - * Fielding
+ * From an architectural point of view, here is the REST definition: "A component is an abstract + * unit of software instructions and internal state that provides a transformation of data via its + * interface." Roy T. Fielding
*
- * Components also have useful services associated. They are all enabled by - * default and are available as properties that can be eventually overridden: + * Components also have useful services associated. They are all enabled by default and are + * available as properties that can be eventually overridden: + * *

    - *
  • "logService" to configure access logging.
  • - *
  • "statusService" to provide common representations for exception - * status.
  • - *
  • "taskService" to run tasks asynchronously.
  • + *
  • "logService" to configure access logging. + *
  • "statusService" to provide common representations for exception status. + *
  • "taskService" to run tasks asynchronously. *
- * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * - * @see Source - * dissertation - * + * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * + * @see Source + * dissertation * @author Jerome Louvel */ public class Component extends Restlet { - /** The modifiable list of client connectors. */ - private final ClientList clients; - - /** The default host. */ - private volatile VirtualHost defaultHost; - - /** The helper provided by the implementation. */ - private volatile ComponentHelper helper; - - /** The modifiable list of virtual hosts. */ - private final List hosts; - - /** - * The private internal router that can be addressed via the RIAP client - * connector. - */ - private volatile Router internalRouter; - - /** The modifiable list of security realms. */ - private final List realms; - - /** The modifiable list of server connectors. */ - private final ServerList servers; - - /** The list of services. */ - private final ServiceList services; - - /** - * Constructor. - */ - public Component() { - super(); - this.hosts = new CopyOnWriteArrayList(); - this.clients = new ClientList(null); - this.servers = new ServerList(null, this); - this.realms = new CopyOnWriteArrayList(); - this.services = new ServiceList(getContext()); - - if (Engine.getInstance() != null) { - // To be done before setting the helper... - this.services.add(new org.restlet.service.TaskService()); - - this.helper = new ComponentHelper(this); - Context childContext = getContext().createChildContext(); - this.defaultHost = new VirtualHost(childContext); - this.internalRouter = new InternalRouter(childContext); - this.services.add(new LogService()); - getLogService().setContext(childContext); - this.services.add(new StatusService()); - this.clients.setContext(childContext); - this.servers.setContext(childContext); - } - } - - /** - * Returns a modifiable list of client connectors. - * - * @return A modifiable list of client connectors. - */ - public ClientList getClients() { - return this.clients; - } - - /** - * Returns the default virtual host. - * - * @return The default virtual host. - */ - public VirtualHost getDefaultHost() { - return this.defaultHost; - } - - /** - * Returns the helper provided by the implementation. - * - * @return The helper provided by the implementation. - */ - private ComponentHelper getHelper() { - return this.helper; - } - - /** - * Returns the modifiable list of virtual hosts. Note that the order of virtual - * hosts in this list will be used to check the first one that matches. - * - * @return The modifiable list of virtual hosts. - */ - public List getHosts() { - return this.hosts; - } - - /** - * Returns the private internal router where Restlets like Applications can be - * attached. Those Restlets can be addressed via the - * {@link org.restlet.data.Protocol#RIAP} (Restlet Internal Access Protocol) - * client connector. This is used to manage private, internal and optimized - * access to local applications.
- *
- * The first use case is the modularization of a large application into modules - * or layers. This can also be achieved using the - * {@link Context#getServerDispatcher()} method, but the internal router is - * easily addressable via an URI scheme and can be fully private to the current - * Component.
- *
- * The second use case is the composition/mash-up of several representations via - * the org.restlet.ext.xml.Transformer class for example. For this you can - * leverage the XPath's document() function or the XSLT's include and import - * elements with RIAP URIs. - * - * @return The private internal router. - */ - public Router getInternalRouter() { - return this.internalRouter; - } - - /** - * Returns the global log service. On the first call, if no log service was - * defined via the {@link #setLogService(LogService)} method, then a default - * logger service is created. This service will be enabled by default and has a - * logger name composed the "org.restlet." prefix followed by the simple - * component class name (without packages), followed by the ".LogService" - * suffix. - * - * @return The global log service. - */ - public LogService getLogService() { - return getServices().get(LogService.class); - } - - /** - * Finds the realm with the given name. - * - * @param name The name. - * @return The realm found or null. - */ - public Realm getRealm(String name) { - if (name != null) { - for (Realm realm : getRealms()) { - if (name.equals(realm.getName())) { - return realm; - } - } - } - - return null; - } - - /** - * Returns the modifiable list of security realms. - * - * @return The modifiable list of security realms. - */ - public List getRealms() { - return realms; - } - - /** - * Returns the modifiable list of server connectors. - * - * @return The modifiable list of server connectors. - */ - public ServerList getServers() { - return this.servers; - } - - /** - * Returns the modifiable list of services. - * - * @return The modifiable list of services. - */ - public ServiceList getServices() { - return services; - } - - /** - * Returns a task service to run concurrent tasks. The service is enabled by - * default. - * - * @return A task service. - */ - public org.restlet.service.TaskService getTaskService() { - return getServices().get(org.restlet.service.TaskService.class); - } - - @Override - public void handle(Request request, Response response) { - super.handle(request, response); - - if (getHelper() != null) { - getHelper().handle(request, response); - } - } - - /** - * Sets the modifiable list of client connectors. This method clears the current - * list and adds all entries in the parameter list. - * - * @param clients A list of client connectors. - */ - public void setClients(ClientList clients) { - synchronized (getClients()) { - if (clients != getClients()) { - getClients().clear(); - - if (clients != null) { - getClients().addAll(clients); - } - } - } - } - - @Override - public void setContext(Context context) { - super.setContext(context); - getServices().setContext(context); - } - - /** - * Sets the default virtual host. - * - * @param defaultHost The default virtual host. - */ - public void setDefaultHost(VirtualHost defaultHost) { - this.defaultHost = defaultHost; - } - - /** - * Sets the modifiable list of virtual hosts. Note that the order of virtual - * hosts in this list will be used to check the first one that matches. This - * method clears the current list and adds all entries in the parameter list. - * - * @param hosts A list of virtual hosts. - */ - public void setHosts(List hosts) { - synchronized (getHosts()) { - if (hosts != getHosts()) { - getHosts().clear(); - - if (hosts != null) { - getHosts().addAll(hosts); - } - } - } - } - - /** - * Sets the private internal router were Restlets like Applications can be - * attached. - * - * @param internalRouter The private internal router. - * @see #getInternalRouter() - */ - public void setInternalRouter(Router internalRouter) { - this.internalRouter = internalRouter; - } - - /** - * Sets the global log service. - * - * @param logService The global log service. - */ - public void setLogService(LogService logService) { - getServices().set(logService); - } - - /** - * Sets the list of realms. This method clears the current list and adds all - * entries in the parameter list. - * - * @param realms A list of realms. - */ - public void setRealms(List realms) { - synchronized (getRealms()) { - if (realms != getRealms()) { - getRealms().clear(); - - if (realms != null) { - getRealms().addAll(realms); - } - } - } - } - - /** - * Sets the modifiable list of server connectors. This method clears the current - * list and adds all entries in the parameter list. - * - * @param servers A list of server connectors. - */ - public void setServers(ServerList servers) { - synchronized (getServers()) { - if (servers != getServers()) { - getServers().clear(); - - if (servers != null) { - getServers().addAll(servers); - } - } - } - } - - /** - * Sets the task service. - * - * @param taskService The task service. - */ - public void setTaskService(org.restlet.service.TaskService taskService) { - getServices().set(taskService); - } - - /** - * Starts the component. First it starts all the connectors (clients then - * servers), the routers, the services, the realms and then the component's - * internal helper. Finally, it calls the start method of the super class. - * - * @see #startClients() - * @see #startServers() - * @see #startRouters() - * @see #startServices() - * @see #startRealms() - * @see #startHelper() - */ - @Override - public synchronized void start() throws Exception { - if (isStopped()) { - startClients(); - startServers(); - startRouters(); - startServices(); - startRealms(); - startHelper(); - - // Must be invoked as a last step - super.start(); - } - } - - /** - * Starts the client connectors. - * - * @throws Exception - */ - protected synchronized void startClients() throws Exception { - if (this.clients != null) { - for (final Client client : this.clients) { - client.start(); - } - } - } - - /** - * Starts the internal helper allowing incoming requests to be served. - * - * @throws Exception - */ - protected synchronized void startHelper() throws Exception { - if (getHelper() != null) { - getHelper().start(); - } - } - - /** - * Starts the realms. - * - * @throws Exception - */ - protected synchronized void startRealms() throws Exception { - if (this.realms != null) { - for (Realm realm : this.realms) { - realm.start(); - } - } - } - - /** - * Starts the virtual hosts and the internal router. - * - * @throws Exception - */ - protected synchronized void startRouters() throws Exception { - if (this.internalRouter != null) { - this.internalRouter.start(); - } - - if (this.defaultHost != null) { - this.defaultHost.start(); - } - - for (VirtualHost host : getHosts()) { - host.start(); - } - } - - /** - * Starts the server connectors. - * - * @throws Exception - */ - protected synchronized void startServers() throws Exception { - if (this.servers != null) { - for (final Server server : this.servers) { - server.start(); - } - } - } - - /** - * Starts the associated services. - * - * @throws Exception - */ - protected synchronized void startServices() throws Exception { - getServices().start(); - } - - /** - * Stops the component. First it stops the component's internal helper, the - * realms, the services, the routers and then stops all the connectors (servers - * then clients) Finally it calls the stop method of the super class. - * - * @see #stopHelper() - * @see #stopRealms() - * @see #stopServices() - * @see #stopRouters() - * @see #stopServers() - * @see #stopClients() - */ - @Override - public synchronized void stop() throws Exception { - stopHelper(); - stopRealms(); - stopServices(); - stopRouters(); - stopServers(); - stopClients(); - super.stop(); - } - - /** - * Stops the client connectors. - * - * @throws Exception - */ - protected synchronized void stopClients() throws Exception { - if (this.clients != null) { - for (final Client client : this.clients) { - client.stop(); - } - } - } - - /** - * Stops the internal helper allowing incoming requests to be served. - * - * @throws Exception - */ - protected synchronized void stopHelper() throws Exception { - if (getHelper() != null) { - getHelper().stop(); - } - } - - /** - * Stops the realms. - * - * @throws Exception - */ - protected synchronized void stopRealms() throws Exception { - if (this.realms != null) { - for (Realm realm : this.realms) { - realm.stop(); - } - } - } - - /** - * Stops the virtual hosts and the internal router. - * - * @throws Exception - */ - protected synchronized void stopRouters() throws Exception { - for (VirtualHost host : getHosts()) { - host.stop(); - } - - if (this.defaultHost != null) { - this.defaultHost.stop(); - } - - if (this.internalRouter != null) { - this.internalRouter.stop(); - } - } - - /** - * Stops the server connectors. - * - * @throws Exception - */ - protected synchronized void stopServers() throws Exception { - if (this.servers != null) { - for (final Server server : this.servers) { - server.stop(); - } - } - } - - /** - * Stops the associated services. - * - * @throws Exception - */ - protected synchronized void stopServices() throws Exception { - getServices().stop(); - } - - /** - * Updates the component to take into account changes to the virtual hosts. This - * method doesn't stop the connectors or the applications or Restlets attached - * to the virtual hosts. It just updates the internal routes between the virtual - * hosts and the attached Restlets or applications. - * - * @throws Exception - */ - public synchronized void updateHosts() throws Exception { - getHelper().update(); - } + /** The modifiable list of client connectors. */ + private final ClientList clients; + + /** The default host. */ + private volatile VirtualHost defaultHost; + + /** The helper provided by the implementation. */ + private volatile ComponentHelper helper; + + /** The modifiable list of virtual hosts. */ + private final List hosts; + + /** The private internal router that can be addressed via the RIAP client connector. */ + private volatile Router internalRouter; + + /** The modifiable list of security realms. */ + private final List realms; + + /** The modifiable list of server connectors. */ + private final ServerList servers; + + /** The list of services. */ + private final ServiceList services; + + /** Constructor. */ + public Component() { + super(); + this.hosts = new CopyOnWriteArrayList(); + this.clients = new ClientList(null); + this.servers = new ServerList(null, this); + this.realms = new CopyOnWriteArrayList(); + this.services = new ServiceList(getContext()); + + if (Engine.getInstance() != null) { + // To be done before setting the helper... + this.services.add(new org.restlet.service.TaskService()); + + this.helper = new ComponentHelper(this); + Context childContext = getContext().createChildContext(); + this.defaultHost = new VirtualHost(childContext); + this.internalRouter = new InternalRouter(childContext); + this.services.add(new LogService()); + getLogService().setContext(childContext); + this.services.add(new StatusService()); + this.clients.setContext(childContext); + this.servers.setContext(childContext); + } + } + + /** + * Returns a modifiable list of client connectors. + * + * @return A modifiable list of client connectors. + */ + public ClientList getClients() { + return this.clients; + } + + /** + * Returns the default virtual host. + * + * @return The default virtual host. + */ + public VirtualHost getDefaultHost() { + return this.defaultHost; + } + + /** + * Returns the helper provided by the implementation. + * + * @return The helper provided by the implementation. + */ + private ComponentHelper getHelper() { + return this.helper; + } + + /** + * Returns the modifiable list of virtual hosts. Note that the order of virtual hosts in this + * list will be used to check the first one that matches. + * + * @return The modifiable list of virtual hosts. + */ + public List getHosts() { + return this.hosts; + } + + /** + * Returns the private internal router where Restlets like Applications can be attached. Those + * Restlets can be addressed via the {@link org.restlet.data.Protocol#RIAP} (Restlet Internal + * Access Protocol) client connector. This is used to manage private, internal, and optimized + * access to local applications.
+ *
+ * The first use case is the modularization of a large application into modules or layers. This + * can also be achieved using the {@link Context#getServerDispatcher()} method, but the internal + * router is easily addressable via a URI scheme and can be fully private to the current + * Component.
+ *
+ * The second use case is the composition/mash-up of several representations via the + * org.restlet.ext.xml.Transformer class, for example. For this you can leverage the XPath's + * document() function or the XSLT's include and import elements with RIAP URIs. + * + * @return The private internal router. + */ + public Router getInternalRouter() { + return this.internalRouter; + } + + /** + * Returns the global log service. On the first call, if no log service was defined via the + * {@link #setLogService(LogService)} method, then a default logger service is created. This + * service will be enabled by default and has a logger name composed the "org.restlet." Prefix + * followed by the simple component class name (without packages), followed by the ".LogService" + * suffix. + * + * @return The global log service. + */ + public LogService getLogService() { + return getServices().get(LogService.class); + } + + /** + * Finds the realm with the given name. + * + * @param name The name. + * @return The realm found or null. + */ + public Realm getRealm(String name) { + if (name != null) { + for (Realm realm : getRealms()) { + if (name.equals(realm.getName())) { + return realm; + } + } + } + + return null; + } + + /** + * Returns the modifiable list of security realms. + * + * @return The modifiable list of security realms. + */ + public List getRealms() { + return realms; + } + + /** + * Returns the modifiable list of server connectors. + * + * @return The modifiable list of server connectors. + */ + public ServerList getServers() { + return this.servers; + } + + /** + * Returns the modifiable list of services. + * + * @return The modifiable list of services. + */ + public ServiceList getServices() { + return services; + } + + /** + * Returns a task service to run concurrent tasks. The service is enabled by default. + * + * @return A task service. + */ + public org.restlet.service.TaskService getTaskService() { + return getServices().get(org.restlet.service.TaskService.class); + } + + @Override + public void handle(Request request, Response response) { + super.handle(request, response); + + if (getHelper() != null) { + getHelper().handle(request, response); + } + } + + /** + * Sets the modifiable list of client connectors. This method clears the current list and adds + * all entries in the parameter list. + * + * @param clients A list of client connectors. + */ + public void setClients(ClientList clients) { + synchronized (getClients()) { + if (clients != getClients()) { + getClients().clear(); + + if (clients != null) { + getClients().addAll(clients); + } + } + } + } + + @Override + public void setContext(Context context) { + super.setContext(context); + getServices().setContext(context); + } + + /** + * Sets the default virtual host. + * + * @param defaultHost The default virtual host. + */ + public void setDefaultHost(VirtualHost defaultHost) { + this.defaultHost = defaultHost; + } + + /** + * Sets the modifiable list of virtual hosts. Note that the order of virtual hosts in this list + * will be used to check the first one that matches. This method clears the current list and + * adds all entries in the parameter list. + * + * @param hosts A list of virtual hosts. + */ + public void setHosts(List hosts) { + synchronized (getHosts()) { + if (hosts != getHosts()) { + getHosts().clear(); + + if (hosts != null) { + getHosts().addAll(hosts); + } + } + } + } + + /** + * Sets the private internal router where Restlets like Applications can be attached. + * + * @param internalRouter The private internal router. + * @see #getInternalRouter() + */ + public void setInternalRouter(Router internalRouter) { + this.internalRouter = internalRouter; + } + + /** + * Sets the global log service. + * + * @param logService The global log service. + */ + public void setLogService(LogService logService) { + getServices().set(logService); + } + + /** + * Sets the list of realms. This method clears the current list and adds all entries in the + * parameter list. + * + * @param realms A list of realms. + */ + public void setRealms(List realms) { + synchronized (getRealms()) { + if (realms != getRealms()) { + getRealms().clear(); + + if (realms != null) { + getRealms().addAll(realms); + } + } + } + } + + /** + * Sets the modifiable list of server connectors. This method clears the current list and adds + * all entries in the parameter list. + * + * @param servers A list of server connectors. + */ + public void setServers(ServerList servers) { + synchronized (getServers()) { + if (servers != getServers()) { + getServers().clear(); + + if (servers != null) { + getServers().addAll(servers); + } + } + } + } + + /** + * Sets the task service. + * + * @param taskService The task service. + */ + public void setTaskService(org.restlet.service.TaskService taskService) { + getServices().set(taskService); + } + + /** + * Starts the component. First it starts all the connectors (clients then servers), the routers, + * the services, the realms, and then the component's internal helper. Finally, it calls the + * start method of the super class. + * + * @see #startClients() + * @see #startServers() + * @see #startRouters() + * @see #startServices() + * @see #startRealms() + * @see #startHelper() + */ + @Override + public synchronized void start() throws Exception { + if (isStopped()) { + startClients(); + startServers(); + startRouters(); + startServices(); + startRealms(); + startHelper(); + + // Must be invoked as a last step + super.start(); + } + } + + /** + * Starts the client connectors. + * + * @throws Exception + */ + protected synchronized void startClients() throws Exception { + if (this.clients != null) { + for (final Client client : this.clients) { + client.start(); + } + } + } + + /** + * Starts the internal helper allowing incoming requests to be served. + * + * @throws Exception + */ + protected synchronized void startHelper() throws Exception { + if (getHelper() != null) { + getHelper().start(); + } + } + + /** + * Starts the realms. + * + * @throws Exception + */ + protected synchronized void startRealms() throws Exception { + if (this.realms != null) { + for (Realm realm : this.realms) { + realm.start(); + } + } + } + + /** + * Starts the virtual hosts and the internal router. + * + * @throws Exception + */ + protected synchronized void startRouters() throws Exception { + if (this.internalRouter != null) { + this.internalRouter.start(); + } + + if (this.defaultHost != null) { + this.defaultHost.start(); + } + + for (VirtualHost host : getHosts()) { + host.start(); + } + } + + /** + * Starts the server connectors. + * + * @throws Exception + */ + protected synchronized void startServers() throws Exception { + if (this.servers != null) { + for (final Server server : this.servers) { + server.start(); + } + } + } + + /** + * Starts the associated services. + * + * @throws Exception + */ + protected synchronized void startServices() throws Exception { + getServices().start(); + } + + /** + * Stops the component. First, it stops the component's internal helper, the realms, the + * services, the routers and then stops all the connectors (servers then clients). Finally, it + * calls the stop method of the super class. + * + * @see #stopHelper() + * @see #stopRealms() + * @see #stopServices() + * @see #stopRouters() + * @see #stopServers() + * @see #stopClients() + */ + @Override + public synchronized void stop() throws Exception { + stopHelper(); + stopRealms(); + stopServices(); + stopRouters(); + stopServers(); + stopClients(); + super.stop(); + } + + /** + * Stops the client connectors. + * + * @throws Exception + */ + protected synchronized void stopClients() throws Exception { + if (this.clients != null) { + for (final Client client : this.clients) { + client.stop(); + } + } + } + + /** + * Stops the internal helper allowing incoming requests to be served. + * + * @throws Exception + */ + protected synchronized void stopHelper() throws Exception { + if (getHelper() != null) { + getHelper().stop(); + } + } + + /** + * Stops the realms. + * + * @throws Exception + */ + protected synchronized void stopRealms() throws Exception { + if (this.realms != null) { + for (Realm realm : this.realms) { + realm.stop(); + } + } + } + + /** + * Stops the virtual hosts and the internal router. + * + * @throws Exception + */ + protected synchronized void stopRouters() throws Exception { + for (VirtualHost host : getHosts()) { + host.stop(); + } + + if (this.defaultHost != null) { + this.defaultHost.stop(); + } + + if (this.internalRouter != null) { + this.internalRouter.stop(); + } + } + + /** + * Stops the server connectors. + * + * @throws Exception + */ + protected synchronized void stopServers() throws Exception { + if (this.servers != null) { + for (final Server server : this.servers) { + server.stop(); + } + } + } + + /** + * Stops the associated services. + * + * @throws Exception + */ + protected synchronized void stopServices() throws Exception { + getServices().stop(); + } + + /** + * Updates the component to take into account changes to the virtual hosts. This method doesn't + * stop the connectors or the applications or Restlets attached to the virtual hosts. It just + * updates the internal routes between the virtual hosts and the attached Restlets or + * applications. + * + * @throws Exception + */ + public synchronized void updateHosts() throws Exception { + getHelper().update(); + } } diff --git a/org.restlet/src/main/java/org/restlet/Connector.java b/org.restlet/src/main/java/org/restlet/Connector.java index a74598d402..6ca4f34750 100644 --- a/org.restlet/src/main/java/org/restlet/Connector.java +++ b/org.restlet/src/main/java/org/restlet/Connector.java @@ -1,105 +1,98 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; -import org.restlet.data.Protocol; - import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import org.restlet.data.Protocol; /** - * Restlet enabling communication between Components. "A connector is an - * abstract mechanism that mediates communication, coordination, or cooperation - * among components. Connectors enable communication between components by - * transferring data elements from one interface to another without changing the - * data." Roy T. Fielding
+ * Restlet enabling communication between Components. "A connector is an abstract mechanism that + * mediates communication, coordination, or cooperation among components. Connectors enable + * communication between components by transferring data elements from one interface to another + * without changing the data." Roy T. Fielding
*
- * "Encapsulate the activities of accessing resources and transferring resource - * representations. The connectors present an abstract interface for component - * communication, enhancing simplicity by providing a clean separation of - * concerns and hiding the underlying implementation of resources and - * communication mechanisms" Roy T. Fielding
+ * "Encapsulate the activities of accessing resources and transferring resource representations. The + * connectors present an abstract interface for component communication, enhancing simplicity by + * providing a clean separation of concerns and hiding the underlying implementation of resources + * and communication mechanisms" Roy T. Fielding
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * - * @see Source - * dissertation - * @see Source - * dissertation + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * + * @see Source + * dissertation + * @see Source + * dissertation * @author Jerome Louvel */ public abstract class Connector extends Restlet { - /** The list of protocols simultaneously supported. */ - private final List protocols; - - /** - * Constructor. - * - * @param context The context. - */ - public Connector(Context context) { - this(context, null); - } + /** The list of protocols simultaneously supported. */ + private final List protocols; - /** - * Constructor. - * - * @param context The context. - * @param protocols The supported protocols. - */ - public Connector(Context context, List protocols) { - super(context); + /** + * Constructor. + * + * @param context The context. + */ + protected Connector(Context context) { + this(context, null); + } - if (protocols == null) { - this.protocols = new CopyOnWriteArrayList(); - } else { - this.protocols = new CopyOnWriteArrayList(protocols); - } - } + /** + * Constructor. + * + * @param context The context. + * @param protocols The supported protocols. + */ + protected Connector(Context context, List protocols) { + super(context); - /** - * Returns the modifiable list of protocols simultaneously supported. - * - * @return The protocols simultaneously supported. - */ - public List getProtocols() { - return this.protocols; - } + if (protocols == null) { + this.protocols = new CopyOnWriteArrayList<>(); + } else { + this.protocols = new CopyOnWriteArrayList<>(protocols); + } + } - /** - * Indicates the underlying connector helper is available. - * - * @return True if the underlying connector helper is available. - */ - public abstract boolean isAvailable(); + /** + * Returns the modifiable list of protocols simultaneously supported. + * + * @return The protocols simultaneously supported. + */ + public List getProtocols() { + return this.protocols; + } - /** - * Sets the list of protocols simultaneously supported. This method clears the - * current list and adds all entries in the parameter list. - * - * @param protocols A list of protocols. - */ - public void setProtocols(List protocols) { - synchronized (getProtocols()) { - if (protocols != getProtocols()) { - getProtocols().clear(); + /** + * Indicates the underlying connector helper is available. + * + * @return True if the underlying connector helper is available. + */ + public abstract boolean isAvailable(); - if (protocols != null) { - getProtocols().addAll(protocols); - } - } - } - } + /** + * Sets the list of protocols simultaneously supported. This method clears the current list and + * adds all entries in the parameter list. + * + * @param protocols A list of protocols. + */ + public void setProtocols(List protocols) { + synchronized (getProtocols()) { + if (protocols != getProtocols()) { + getProtocols().clear(); + if (protocols != null) { + getProtocols().addAll(protocols); + } + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/Context.java b/org.restlet/src/main/java/org/restlet/Context.java index 497647e0e6..6afb3b08aa 100644 --- a/org.restlet/src/main/java/org/restlet/Context.java +++ b/org.restlet/src/main/java/org/restlet/Context.java @@ -1,366 +1,350 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; -import org.restlet.data.Parameter; -import org.restlet.engine.Engine; -import org.restlet.util.Series; - import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Logger; +import org.restlet.data.Parameter; +import org.restlet.engine.Engine; +import org.restlet.util.Series; /** - * Contextual data and services provided to a set of Restlets. The context is - * the means by which a Restlet may access the software environment within the - * framework. It is typically provided by the immediate parent Restlet - * (Application is the most common case).
+ * Contextual data and services provided to a set of Restlets. The context is the means by which a + * Restlet may access the software environment within the framework. It is typically provided by the + * immediate parent Restlet (Application is the most common case).
*
- * Concurrency note: attributes and parameters of a context are stored in - * concurrent collections that guarantee thread safe access and modification. If - * several threads concurrently access objects and modify these collections, - * they should synchronize on the lock of the Context instance. - * + * Concurrency note: attributes and parameters of a context are stored in concurrent collections + * that guarantee thread-safe access and modification. If several threads concurrently access + * objects and modify these collections, they should synchronize on the lock of the Context + * instance. + * * @author Jerome Louvel */ public class Context { - private static final ThreadLocal CURRENT = new ThreadLocal(); - - /** - * Returns the context associated to the current {@link Restlet}. The context - * can be the one of a {@link Component}, an {@link Application}, a - * {@link org.restlet.routing.Filter} or any other {@link Restlet} subclass.
- *
- * Warning: this method should only be used under duress. You should by default - * prefer obtaining the current context using methods such as - * {@link org.restlet.Restlet#getContext()} or - * {@link org.restlet.resource.Resource#getContext()}.
- *
- * This variable is stored internally as a thread local variable and updated - * each time a request is handled by a {@link Restlet} via the - * {@link Restlet#handle(org.restlet.Request, org.restlet.Response)} method. - * - * @return The current context. - */ - public static Context getCurrent() { - return CURRENT.get(); - } - - /** - * Returns the current context's logger. - * - * @return The current context's logger. - */ - public static Logger getCurrentLogger() { - return (Context.getCurrent() != null) ? Context.getCurrent().getLogger() : Engine.getLogger("org.restlet"); - } - - /** - * Sets the context to associated with the current thread. - * - * @param context The thread's context. - */ - public static void setCurrent(Context context) { - CURRENT.set(context); - } - - /** The client dispatcher. */ - private volatile Restlet clientDispatcher; - - /** The server dispatcher. */ - private volatile Restlet serverDispatcher; - - /** The modifiable attributes map. */ - private final ConcurrentMap attributes; - - /** The logger instance to use. */ - private volatile Logger logger; - - /** The modifiable series of parameters. */ - private final Series parameters; - - /** - * The enroler that can add the user roles based on Restlet default - * authorization model. - */ - private volatile org.restlet.security.Enroler defaultEnroler; - - /** - * The verifier that can check the validity of user/secret couples based on - * Restlet default authorization model. - */ - private volatile org.restlet.security.Verifier defaultVerifier; - - /** The executor service. */ - private volatile ScheduledExecutorService executorService; - - /** - * Constructor. Writes log messages to "org.restlet". - */ - public Context() { - this("org.restlet"); - } - - /** - * Constructor. - * - * @param logger The logger instance of use. - */ - public Context(Logger logger) { - this.attributes = new ConcurrentHashMap(); - this.logger = logger; - this.parameters = new Series(Parameter.class, new CopyOnWriteArrayList()); - this.clientDispatcher = null; - - this.defaultEnroler = null; - this.serverDispatcher = null; - this.defaultVerifier = null; - } - - /** - * Constructor. - * - * @param loggerName The name of the logger to use. - */ - public Context(String loggerName) { - this(Engine.getLogger(loggerName)); - } - - /** - * Creates a protected child context. This is especially useful for new - * application attached to their parent component, to ensure their isolation - * from the other applications. By default it creates a new context instance - * with empty or null properties, except the client and server dispatchers that - * are wrapped for isolation purpose. - * - * @return The child context. - */ - public Context createChildContext() { - return new org.restlet.engine.util.ChildContext(this); - } - - /** - * Returns a modifiable attributes map that can be used by developers to save - * information relative to the context. This is a convenient means to provide - * common objects to all the Restlets and Resources composing an - * Application.
- *
- * - * In addition, this map is a shared space between the developer and the Restlet - * implementation. For this purpose, all attribute names starting with - * "org.restlet" are reserved. Currently the following attributes are used: - * - * - * - * - * - * - * - * - * - * - * - * - *
list of currently used attributes
Attribute nameClass nameDescription
org.restlet.applicationorg.restlet.ApplicationThe parent application providing this context, if any.
- * - * @return The modifiable attributes map. - */ - public ConcurrentMap getAttributes() { - return this.attributes; - } - - /** - * Returns a request dispatcher to available client connectors. When you ask the - * dispatcher to handle a request, it will automatically select the appropriate - * client connector for your request, based on the request.protocol property or - * on the resource URI's scheme. This call is blocking and will return an - * updated response object. - * - * @return A request dispatcher to available client connectors. - */ - public Restlet getClientDispatcher() { - return this.clientDispatcher; - } - - /** - * Returns an enroler that can add the user roles based on authenticated user - * principals. - * - * @return An enroler. - */ - public org.restlet.security.Enroler getDefaultEnroler() { - return defaultEnroler; - } - - /** - * Returns a verifier that can check the validity of the credentials associated - * to a request. - * - * @return A verifier. - */ - public org.restlet.security.Verifier getDefaultVerifier() { - return this.defaultVerifier; - } - - /** - * Returns the executor service. - * - * @return The executor service. - */ - public ScheduledExecutorService getExecutorService() { - return this.executorService; - } - - /** - * Returns the logger. - * - * @return The logger. - */ - public Logger getLogger() { - return this.logger; - } - - /** - * Returns the modifiable series of parameters. A parameter is a pair composed - * of a name and a value and is typically used for configuration purpose, like - * Java properties. Note that multiple parameters with the same name can be - * declared and accessed. - * - * @return The modifiable series of parameters. - */ - public Series getParameters() { - return this.parameters; - } - - /** - * Returns a request dispatcher to component's virtual hosts. This is useful for - * application that want to optimize calls to other applications hosted in the - * same component or to the application itself.
- *
- * The processing is the same as what would have been done if the request came - * from one of the component's server connectors. It first must match one of the - * registered virtual hosts. Then it can be routed to one of the attached - * Restlets, typically an Application.
- *
- * Note that the RIAP pseudo protocol isn't supported by this dispatcher, you - * should instead rely on the {@link #getClientDispatcher()} method. - * - * @return A request dispatcher to the server connectors' router. - */ - public Restlet getServerDispatcher() { - return this.serverDispatcher; - } - - /** - * Sets the modifiable map of attributes. This method clears the current map and - * puts all entries in the parameter map. - * - * @param attributes A map of attributes. - */ - public void setAttributes(Map attributes) { - synchronized (getAttributes()) { - if (attributes != getAttributes()) { - getAttributes().clear(); - - if (attributes != null) { - getAttributes().putAll(attributes); - } - } - } - } - - /** - * Sets the client dispatcher. - * - * @param clientDispatcher The new client dispatcher. - */ - public void setClientDispatcher(Restlet clientDispatcher) { - this.clientDispatcher = clientDispatcher; - } - - /** - * Sets an enroler that can add the user roles based on authenticated user - * principals. - * - * @param enroler An enroler. - */ - public void setDefaultEnroler(org.restlet.security.Enroler enroler) { - this.defaultEnroler = enroler; - } - - /** - * Sets a local verifier that can check the validity of user/secret couples - * based on Restlet default authorization model. - * - * @param verifier A local verifier. - */ - public void setDefaultVerifier(org.restlet.security.Verifier verifier) { - this.defaultVerifier = verifier; - } - - /** - * Sets the executor service. - * - * @param executorService The executor service. - */ - public void setExecutorService(ScheduledExecutorService executorService) { - this.executorService = executorService; - } - - /** - * Sets the logger. - * - * @param logger The logger. - */ - public void setLogger(Logger logger) { - this.logger = logger; - } - - /** - * Sets the logger. - * - * @param loggerName The name of the logger to use. - */ - public void setLogger(String loggerName) { - setLogger(Engine.getLogger(loggerName)); - } - - /** - * Sets the modifiable series of parameters. This method clears the current - * series and adds all entries in the parameter series. - * - * @param parameters A series of parameters. - */ - public void setParameters(Series parameters) { - synchronized (getParameters()) { - if (parameters != getParameters()) { - getParameters().clear(); - - if (parameters != null) { - getParameters().addAll(parameters); - } - } - } - } - - /** - * Sets the server dispatcher. - * - * @param serverDispatcher The new server dispatcher. - */ - public void setServerDispatcher(Restlet serverDispatcher) { - this.serverDispatcher = serverDispatcher; - } - + private static final ThreadLocal CURRENT = new ThreadLocal(); + + /** + * Returns the context associated with the current {@link Restlet}. The context can be the one + * of a {@link Component}, an {@link Application}, a {@link org.restlet.routing.Filter} or any + * other {@link Restlet} subclass.
+ *
+ * Warning: this method should only be used under duress. You should by default prefer getting + * the current context using methods such as {@link org.restlet.Restlet#getContext()} or {@link + * org.restlet.resource.Resource#getContext()}.
+ *
+ * This variable is stored internally as a thread local variable and updated each time a request + * is handled by a {@link Restlet} via the {@link Restlet#handle(org.restlet.Request, + * org.restlet.Response)} method. + * + * @return The current context. + */ + public static Context getCurrent() { + return CURRENT.get(); + } + + /** + * Returns the current context's logger. + * + * @return The current context's logger. + */ + public static Logger getCurrentLogger() { + return (Context.getCurrent() != null) + ? Context.getCurrent().getLogger() + : Engine.getLogger("org.restlet"); + } + + /** + * Sets the context to associated with the current thread. + * + * @param context The thread's context. + */ + public static void setCurrent(Context context) { + CURRENT.set(context); + } + + /** The client dispatcher. */ + private volatile Restlet clientDispatcher; + + /** The server dispatcher. */ + private volatile Restlet serverDispatcher; + + /** The modifiable attributes map. */ + private final ConcurrentMap attributes; + + /** The logger instance to use. */ + private volatile Logger logger; + + /** The modifiable series of parameters. */ + private final Series parameters; + + /** The enroler that can add the user roles based on a Restlet default authorization model. */ + private volatile org.restlet.security.Enroler defaultEnroler; + + /** + * The verifier that can check the validity of user/secret couples based on a Restlet default + * authorization model. + */ + private volatile org.restlet.security.Verifier defaultVerifier; + + /** The executor service. */ + private volatile ScheduledExecutorService executorService; + + /** Constructor. Writes log messages to "org.restlet". */ + public Context() { + this("org.restlet"); + } + + /** + * Constructor. + * + * @param logger The logger instance of use. + */ + public Context(Logger logger) { + this.attributes = new ConcurrentHashMap<>(); + this.logger = logger; + this.parameters = new Series<>(Parameter.class, new CopyOnWriteArrayList<>()); + this.clientDispatcher = null; + + this.defaultEnroler = null; + this.serverDispatcher = null; + this.defaultVerifier = null; + } + + /** + * Constructor. + * + * @param loggerName The name of the logger to use. + */ + public Context(String loggerName) { + this(Engine.getLogger(loggerName)); + } + + /** + * Creates a protected child context. This is especially useful for new application attached to + * their parent component to ensure their isolation from the other applications. By default, it + * creates a new context instance with empty or null properties, except the client and server + * dispatchers that are wrapped for isolation purposes. + * + * @return The child context. + */ + public Context createChildContext() { + return new org.restlet.engine.util.ChildContext(this); + } + + /** + * Returns a modifiable attributes map that developers can use to save information relative to + * the context. This is a convenient means to provide common objects to all the Restlets and + * Resources composing an Application.
+ *
+ * In addition, this map is a shared space between the developer and the Restlet implementation. + * For this purpose, all attribute names starting with "org.restlet" are reserved. Currently, + * the following attributes are used: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
list of currently used attributes
Attribute nameClass nameDescription
org.restlet.applicationorg.restlet.ApplicationThe parent application providing this context, if any.
+ * + * @return The modifiable attributes map. + */ + public ConcurrentMap getAttributes() { + return this.attributes; + } + + /** + * Returns a request dispatcher to available client connectors. When you ask the dispatcher to + * handle a request, it will automatically select the appropriate client connector for your + * request, based on the request.protocol property or on the resource URI's scheme. This call is + * blocking and will return an updated response object. + * + * @return A request dispatcher to available client connectors. + */ + public Restlet getClientDispatcher() { + return this.clientDispatcher; + } + + /** + * Returns an enroler that can add the user roles based on authenticated user principals. + * + * @return An enroler. + */ + public org.restlet.security.Enroler getDefaultEnroler() { + return defaultEnroler; + } + + /** + * Returns a verifier that can check the validity of the credentials associated with a request. + * + * @return A verifier. + */ + public org.restlet.security.Verifier getDefaultVerifier() { + return this.defaultVerifier; + } + + /** + * Returns the executor service. + * + * @return The executor service. + */ + public ScheduledExecutorService getExecutorService() { + return this.executorService; + } + + /** + * Returns the logger. + * + * @return The logger. + */ + public Logger getLogger() { + return this.logger; + } + + /** + * Returns the modifiable series of parameters. A parameter is a pair composed of a name and a + * value and is typically used for configuration purpose, like Java properties. Note that + * multiple parameters with the same name can be declared and accessed. + * + * @return The modifiable series of parameters. + */ + public Series getParameters() { + return this.parameters; + } + + /** + * Returns a request dispatcher to the component's virtual hosts. This is useful for application + * that wants to optimize calls to other applications hosted in the same component or to the + * application itself.
+ *
+ * The processing is the same as what would have been done if the request came from one of the + * component's server connectors. It first must match one of the registered virtual hosts. Then + * it can be routed to one of the attached Restlets, typically an Application.
+ *
+ * Note that this dispatcher doesn't support the RIAP pseudo protocol, you should instead rely + * on the {@link #getClientDispatcher()} method. + * + * @return A request dispatcher to the server connectors' router. + */ + public Restlet getServerDispatcher() { + return this.serverDispatcher; + } + + /** + * Sets the modifiable map of attributes. This method clears the current map and puts all + * entries in the parameter map. + * + * @param attributes A map of attributes. + */ + public void setAttributes(Map attributes) { + synchronized (getAttributes()) { + if (attributes != getAttributes()) { + getAttributes().clear(); + + if (attributes != null) { + getAttributes().putAll(attributes); + } + } + } + } + + /** + * Sets the client dispatcher. + * + * @param clientDispatcher The new client dispatcher. + */ + public void setClientDispatcher(Restlet clientDispatcher) { + this.clientDispatcher = clientDispatcher; + } + + /** + * Sets an enroler that can add the user roles based on authenticated user principals. + * + * @param enroler An enroler. + */ + public void setDefaultEnroler(org.restlet.security.Enroler enroler) { + this.defaultEnroler = enroler; + } + + /** + * Sets a local verifier that can check the validity of user/secret couples based on a Restlet + * default authorization model. + * + * @param verifier A local verifier. + */ + public void setDefaultVerifier(org.restlet.security.Verifier verifier) { + this.defaultVerifier = verifier; + } + + /** + * Sets the executor service. + * + * @param executorService The executor service. + */ + public void setExecutorService(ScheduledExecutorService executorService) { + this.executorService = executorService; + } + + /** + * Sets the logger. + * + * @param logger The logger. + */ + public void setLogger(Logger logger) { + this.logger = logger; + } + + /** + * Sets the logger. + * + * @param loggerName The name of the logger to use. + */ + public void setLogger(String loggerName) { + setLogger(Engine.getLogger(loggerName)); + } + + /** + * Sets the modifiable series of parameters. This method clears the current series and adds all + * entries in the parameter series. + * + * @param parameters A series of parameters. + */ + public void setParameters(Series parameters) { + synchronized (getParameters()) { + if (parameters != getParameters()) { + getParameters().clear(); + + if (parameters != null) { + getParameters().addAll(parameters); + } + } + } + } + + /** + * Sets the server dispatcher. + * + * @param serverDispatcher The new server dispatcher. + */ + public void setServerDispatcher(Restlet serverDispatcher) { + this.serverDispatcher = serverDispatcher; + } } diff --git a/org.restlet/src/main/java/org/restlet/Message.java b/org.restlet/src/main/java/org/restlet/Message.java index 6834cbef17..44aded1c44 100644 --- a/org.restlet/src/main/java/org/restlet/Message.java +++ b/org.restlet/src/main/java/org/restlet/Message.java @@ -1,19 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; -import org.restlet.data.*; -import org.restlet.representation.Representation; -import org.restlet.representation.StringRepresentation; -import org.restlet.resource.ClientResource; -import org.restlet.util.Series; +import static org.restlet.engine.header.HeaderConstants.ATTRIBUTE_HEADERS; +import static org.restlet.representation.Representation.UNKNOWN_SIZE; import java.io.IOException; import java.util.Date; @@ -22,440 +18,437 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; - -import static org.restlet.engine.header.HeaderConstants.ATTRIBUTE_HEADERS; -import static org.restlet.representation.Representation.UNKNOWN_SIZE; +import org.restlet.data.CacheDirective; +import org.restlet.data.Header; +import org.restlet.data.MediaType; +import org.restlet.data.RecipientInfo; +import org.restlet.data.Warning; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.ClientResource; +import org.restlet.util.Series; /** * Generic message exchanged between components. - * + * * @author Jerome Louvel */ public abstract class Message { - /** The modifiable attributes map. */ - private volatile ConcurrentMap attributes; - - /** The caching directives. */ - private volatile List cacheDirectives; - - /** The date and time at which the message was originated. */ - private volatile Date date; - - /** The payload of the message. */ - private volatile Representation entity; - - /** The optional cached text. */ - private volatile String entityText; - - /** Callback invoked when an error occurs when sending the message. */ - private volatile Uniform onError; - - /** Callback invoked after sending the message. */ - private volatile Uniform onSent; - - /** The intermediary recipients info. */ - private volatile List recipientsInfo; - - /** The additional warnings information. */ - private volatile List warnings; - - /** - * Constructor. - */ - public Message() { - this(null); - } - - /** - * Constructor. - * - * @param entity The payload of the message. - */ - public Message(Representation entity) { - this.attributes = null; - this.cacheDirectives = null; - this.date = null; - this.entity = entity; - this.entityText = null; - this.onSent = null; - this.recipientsInfo = null; - this.warnings = null; - } - - /** - * If the entity is transient or its size unknown in advance but available, then - * the entity is wrapped with a - * {@link org.restlet.representation.BufferingRepresentation}.
- *
- * Be careful as this method could create potentially very large byte buffers in - * memory that could impact your application performance. - * - * @see org.restlet.representation.BufferingRepresentation - * @see ClientResource#setRequestEntityBuffering(boolean) - * @see ClientResource#setResponseEntityBuffering(boolean) - */ - public void bufferEntity() { - if ((getEntity() != null) && (getEntity().isTransient() || getEntity().getSize() == UNKNOWN_SIZE) - && getEntity().isAvailable()) { - setEntity(new org.restlet.representation.BufferingRepresentation(getEntity())); - } - } - - /** - * Asks the underlying connector to immediately flush the network buffers. - * - * @throws IOException - */ - public void flushBuffers() throws IOException { - } - - /** - * Returns the modifiable map of attributes that can be used by developers to - * save information relative to the message. Creates a new instance if no one - * has been set. This is an easier alternative to the creation of a wrapper - * instance around the whole message.
- *
- * - * In addition, this map is a shared space between the developer and the - * connectors. In this case, it is used to exchange information that is not - * uniform across all protocols and couldn't therefore be directly included in - * the API. For this purpose, all attribute names starting with "org.restlet" - * are reserved. Currently the following attributes are used: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
list of currently used attributes
Attribute nameClass nameDescription
org.restlet.http.headersorg.restlet.util.Series<org.restlet.engine.header.Header>Server HTTP connectors must provide all request headers and client HTTP - * connectors must provide all response headers, exactly as they were received. - * In addition, developers can also use this attribute to specify - * non-standard headers that should be added to the request or to the - * response.
org.restlet.https.clientCertificatesList<java.security.cert.Certificate>For requests received via a secure connector, indicates the ordered list - * of client certificates, if they are available and accessible.
- *
- * Most of the standard HTTP headers are directly supported via the Restlet API. - * Thus, adding such HTTP headers is forbidden because it could conflict with - * the connector's internal behavior, limit portability or prevent future - * optimizations. The other standard HTTP headers (that are not supported) can - * be added as attributes via the "org.restlet.http.headers" key.
- * - * @return The modifiable attributes map. - */ - public ConcurrentMap getAttributes() { - // Lazy initialization with double-check. - ConcurrentMap r = this.attributes; - if (r == null) { - synchronized (this) { - r = this.attributes; - if (r == null) { - this.attributes = r = new ConcurrentHashMap(); - } - } - } - - return this.attributes; - } - - /** - * Returns the cache directives.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Cache-Control" header. - * - * @return The cache directives. - */ - public List getCacheDirectives() { - // Lazy initialization with double-check. - List r = this.cacheDirectives; - if (r == null) { - synchronized (this) { - r = this.cacheDirectives; - if (r == null) { - this.cacheDirectives = r = new CopyOnWriteArrayList(); - } - } - } - return r; - } - - /** - * Returns the date and time at which the message was originated. - * - * @return The date and time at which the message was originated. - */ - public Date getDate() { - return date; - } - - /** - * Returns the entity representation. - * - * @return The entity representation. - */ - public Representation getEntity() { - return this.entity; - } - - /** - * Returns the entity as text. This method can be called several times and will - * always return the same text. Note that if the entity is large this method can - * result in important memory consumption. - * - * @return The entity as text. - */ - public String getEntityAsText() { - if (this.entityText == null) { - try { - this.entityText = (getEntity() == null) ? null : getEntity().getText(); - } catch (java.io.IOException e) { - Context.getCurrentLogger().log(java.util.logging.Level.FINE, "Unable to get the entity text.", e); - } - } - - return this.entityText; - } - - /** - * Returns the series of lower-level HTTP headers. Please not that this method - * should rarely be used as most HTTP headers are already surfaced by the - * Restlet API. The result series can be used to deal with HTTP extension - * headers. - * - * @return The HTTP headers. - */ - @SuppressWarnings("unchecked") - public Series

getHeaders() { - Series
headers = (Series
) getAttributes().get(ATTRIBUTE_HEADERS); - if (headers == null) { - headers = new Series
(Header.class); - getAttributes().put(ATTRIBUTE_HEADERS, headers); - } - return headers; - } - - /** - * Returns the callback invoked when an error occurs when sending the message. - * - * @return The callback invoked when an error occurs when sending the message. - */ - public Uniform getOnError() { - return onError; - } - - /** - * Returns the callback invoked after sending the message. - * - * @return The callback invoked after sending the message. - */ - public Uniform getOnSent() { - return onSent; - } - - /** - * Returns the intermediary recipient information.
- *
- * Note that when used with HTTP connectors, this property maps to the "Via" - * headers. - * - * @return The intermediary recipient information. - */ - public List getRecipientsInfo() { - // Lazy initialization with double-check. - List r = this.recipientsInfo; - if (r == null) { - synchronized (this) { - r = this.recipientsInfo; - if (r == null) { - this.recipientsInfo = r = new CopyOnWriteArrayList(); - } - } - } - return r; - } - - /** - * Returns the additional warnings information.
- *
- * Note that when used with HTTP connectors, this property maps to the "Warning" - * headers. - * - * @return The additional warnings information. - */ - public List getWarnings() { - // Lazy initialization with double-check. - List r = this.warnings; - if (r == null) { - synchronized (this) { - r = this.warnings; - if (r == null) { - this.warnings = r = new CopyOnWriteArrayList(); - } - } - } - return r; - } - - /** - * Indicates if the message was or will be exchanged confidentially, for example - * via a SSL-secured connection. - * - * @return True if the message is confidential. - */ - public abstract boolean isConfidential(); - - /** - * Indicates if a content is available and can be sent or received. Several - * conditions must be met: the content must exists and have some available data. - * - * @return True if a content is available and can be sent. - */ - public boolean isEntityAvailable() { - return (getEntity() != null) && getEntity().isAvailable(); - } - - /** - * Releases the message's entity if present. - * - * @see org.restlet.representation.Representation#release() - */ - public void release() { - if (getEntity() != null) { - getEntity().release(); - } - } - - /** - * Sets the modifiable map of attributes. This method clears the current map and - * puts all entries in the parameter map. - * - * @param attributes A map of attributes - */ - public void setAttributes(Map attributes) { - synchronized (getAttributes()) { - if (attributes != getAttributes()) { - getAttributes().clear(); - - if (attributes != null) { - getAttributes().putAll(attributes); - } - } - } - } - - /** - * Sets the cache directives. Note that when used with HTTP connectors, this - * property maps to the "Cache-Control" header. This method clears the current - * list and adds all entries in the parameter list. - * - * @param cacheDirectives The cache directives. - */ - public void setCacheDirectives(List cacheDirectives) { - synchronized (getCacheDirectives()) { - if (cacheDirectives != getCacheDirectives()) { - getCacheDirectives().clear(); - - if (cacheDirectives != null) { - getCacheDirectives().addAll(cacheDirectives); - } - } - } - } - - /** - * Sets the date and time at which the message was originated. - * - * @param date The date and time at which the message was originated. - */ - public void setDate(Date date) { - this.date = date; - } - - /** - * Sets the entity representation. - * - * @param entity The entity representation. - */ - public void setEntity(Representation entity) { - this.entity = entity; - } - - /** - * Sets a textual entity. - * - * @param value The represented string. - * @param mediaType The representation's media type. - */ - public void setEntity(String value, MediaType mediaType) { - setEntity(new StringRepresentation(value, mediaType)); - } - - /** - * Sets the callback invoked when an error occurs when sending the message. - * - * @param onError The callback invoked when an error occurs when sending the - * message. - */ - public void setOnError(Uniform onError) { - this.onError = onError; - } - - /** - * Sets the callback invoked after sending the message. - * - * @param onSentCallback The callback invoked after sending the message. - */ - public void setOnSent(Uniform onSentCallback) { - this.onSent = onSentCallback; - } - - /** - * Sets the modifiable list of intermediary recipients. Note that when used with - * HTTP connectors, this property maps to the "Via" headers. This method clears - * the current list and adds all entries in the parameter list. - * - * @param recipientsInfo A list of intermediary recipients. - */ - public void setRecipientsInfo(List recipientsInfo) { - synchronized (getRecipientsInfo()) { - if (recipientsInfo != getRecipientsInfo()) { - getRecipientsInfo().clear(); - - if (recipientsInfo != null) { - getRecipientsInfo().addAll(recipientsInfo); - } - } - } - } - - /** - * Sets the additional warnings information. Note that when used with HTTP - * connectors, this property maps to the "Warning" headers. This method clears - * the current list and adds all entries in the parameter list. - * - * @param warnings The warnings. - */ - public void setWarnings(List warnings) { - synchronized (getWarnings()) { - if (warnings != getWarnings()) { - getWarnings().clear(); - - if (warnings != null) { - getWarnings().addAll(warnings); - } - } - } - } - + /** The modifiable attributes map. */ + private volatile ConcurrentMap attributes; + + /** The caching directives. */ + private volatile List cacheDirectives; + + /** The date and time at which the message was originated. */ + private volatile Date date; + + /** The payload of the message. */ + private volatile Representation entity; + + /** The optional cached text. */ + private volatile String entityText; + + /** Callback invoked when an error occurs when sending the message. */ + private volatile Uniform onError; + + /** Callback invoked after sending the message. */ + private volatile Uniform onSent; + + /** The intermediary recipients' info. */ + private volatile List recipientsInfo; + + /** The additional warnings' information. */ + private volatile List warnings; + + /** Constructor. */ + protected Message() { + this(null); + } + + /** + * Constructor. + * + * @param entity The payload of the message. + */ + protected Message(Representation entity) { + this.attributes = null; + this.cacheDirectives = null; + this.date = null; + this.entity = entity; + this.entityText = null; + this.onSent = null; + this.recipientsInfo = null; + this.warnings = null; + } + + /** + * If the entity is transient or its size unknown in advance but available, then the entity is + * wrapped with a {@link org.restlet.representation.BufferingRepresentation}.
+ *
+ * Be careful as this method could create potentially very large byte buffers in memory that + * could impact your application performance. + * + * @see org.restlet.representation.BufferingRepresentation + * @see ClientResource#setRequestEntityBuffering(boolean) + * @see ClientResource#setResponseEntityBuffering(boolean) + */ + public void bufferEntity() { + if ((getEntity() != null) + && (getEntity().isTransient() || getEntity().getSize() == UNKNOWN_SIZE) + && getEntity().isAvailable()) { + setEntity(new org.restlet.representation.BufferingRepresentation(getEntity())); + } + } + + /** + * Asks the underlying connector to immediately flush the network buffers. + * + * @throws IOException + */ + public void flushBuffers() throws IOException {} + + /** + * Returns the modifiable map of attributes that developers can use to save information relative + * to the message. Creates a new instance if no one has been set. This is an easier alternative + * to the creation of a wrapper instance around the whole message.
+ *
+ * In addition, this map is a shared space between the developer and the connectors. In this + * case, it is used to exchange information that is not uniform across all protocols and + * couldn't therefore be directly included in the API. For this purpose, all attribute names + * starting with "org.restlet" are reserved. Currently the following attributes are used: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
list of currently used attributes
Attribute nameClass nameDescription
org.restlet.http.headersorg.restlet.util.Series<org.restlet.engine.header.Header>Server HTTP connectors must provide all request headers, and client HTTP + * connectors must provide all response headers, exactly as they were received. + * In addition, developers can also use this attribute to specify + * non-standard headers that should be added to the request or to the + * response.
org.restlet.https.clientCertificatesList<java.security.cert.Certificate>For requests received via a secure connector, indicates the ordered list + * of client certificates if they are available and accessible.
+ * + *
+ * Most of the standard HTTP headers are directly supported via the Restlet API. Thus, adding + * such HTTP headers is forbidden because it could conflict with the connector's internal + * behavior, limit portability, or prevent future optimizations. The other standard HTTP headers + * (that are not supported) can be added as attributes via the "org.restlet.http.headers" key. + *
+ * + * @return The modifiable attributes map. + */ + public ConcurrentMap getAttributes() { + // Lazy initialization with double-check. + ConcurrentMap r = this.attributes; + if (r == null) { + synchronized (this) { + r = this.attributes; + if (r == null) { + this.attributes = r = new ConcurrentHashMap<>(); + } + } + } + + return this.attributes; + } + + /** + * Returns the cache directives.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Cache-Control" header. + * + * @return The cache directives. + */ + public List getCacheDirectives() { + // Lazy initialization with double-check. + List r = this.cacheDirectives; + if (r == null) { + synchronized (this) { + r = this.cacheDirectives; + if (r == null) { + this.cacheDirectives = r = new CopyOnWriteArrayList<>(); + } + } + } + return r; + } + + /** + * Returns the date and time at which the message was originated. + * + * @return The date and time at which the message was originated. + */ + public Date getDate() { + return date; + } + + /** + * Returns the entity representation. + * + * @return The entity representation. + */ + public Representation getEntity() { + return this.entity; + } + + /** + * Returns the entity as text. This method can be called several times and will always return + * the same text. Note that if the entity is large, this method can result in important memory + * consumption. + * + * @return The entity as text. + */ + public String getEntityAsText() { + if (this.entityText == null) { + try { + this.entityText = (getEntity() == null) ? null : getEntity().getText(); + } catch (java.io.IOException e) { + Context.getCurrentLogger() + .log(java.util.logging.Level.FINE, "Unable to get the entity text.", e); + } + } + + return this.entityText; + } + + /** + * Returns the series of lower-level HTTP headers. Please note that this method should rarely be + * used as most HTTP headers are already surfaced by the Restlet API. The result series can be + * used to deal with HTTP extension headers. + * + * @return The HTTP headers. + */ + @SuppressWarnings("unchecked") + public Series
getHeaders() { + Series
headers = (Series
) getAttributes().get(ATTRIBUTE_HEADERS); + if (headers == null) { + headers = new Series
(Header.class); + getAttributes().put(ATTRIBUTE_HEADERS, headers); + } + return headers; + } + + /** + * Returns the callback invoked when an error occurs when sending the message. + * + * @return The callback invoked when an error occurs when sending the message. + */ + public Uniform getOnError() { + return onError; + } + + /** + * Returns the callback invoked after sending the message. + * + * @return The callback invoked after sending the message. + */ + public Uniform getOnSent() { + return onSent; + } + + /** + * Returns the intermediary recipient information.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Via" headers. + * + * @return The intermediary recipient information. + */ + public List getRecipientsInfo() { + // Lazy initialization with double-check. + List r = this.recipientsInfo; + if (r == null) { + synchronized (this) { + r = this.recipientsInfo; + if (r == null) { + this.recipientsInfo = r = new CopyOnWriteArrayList(); + } + } + } + return r; + } + + /** + * Returns the additional warnings information.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Warning" headers. + * + * @return The additional warnings' information. + */ + public List getWarnings() { + // Lazy initialization with double-check. + List r = this.warnings; + if (r == null) { + synchronized (this) { + r = this.warnings; + if (r == null) { + this.warnings = r = new CopyOnWriteArrayList(); + } + } + } + return r; + } + + /** + * Indicates if the message was or will be exchanged confidentially, for example, via a + * SSL-secured connection. + * + * @return True if the message is confidential. + */ + public abstract boolean isConfidential(); + + /** + * Indicates if a content is available and can be sent or received. Several conditions must be + * met: the content must exist and have some available data. + * + * @return True if a content is available and can be sent. + */ + public boolean isEntityAvailable() { + return (getEntity() != null) && getEntity().isAvailable(); + } + + /** + * Releases the message's entity if present. + * + * @see org.restlet.representation.Representation#release() + */ + public void release() { + if (getEntity() != null) { + getEntity().release(); + } + } + + /** + * Sets the modifiable map of attributes. This method clears the current map and puts all + * entries in the parameter map. + * + * @param attributes A map of attributes + */ + public void setAttributes(Map attributes) { + synchronized (getAttributes()) { + if (attributes != getAttributes()) { + getAttributes().clear(); + + if (attributes != null) { + getAttributes().putAll(attributes); + } + } + } + } + + /** + * Sets the cache directives. Note that when used with HTTP connectors, this property maps to + * the "Cache-Control" header. This method clears the current list and adds all entries in the + * parameter list. + * + * @param cacheDirectives The cache directives. + */ + public void setCacheDirectives(List cacheDirectives) { + synchronized (getCacheDirectives()) { + if (cacheDirectives != getCacheDirectives()) { + getCacheDirectives().clear(); + + if (cacheDirectives != null) { + getCacheDirectives().addAll(cacheDirectives); + } + } + } + } + + /** + * Sets the date and time at which the message was originated. + * + * @param date The date and time at which the message was originated. + */ + public void setDate(Date date) { + this.date = date; + } + + /** + * Sets the entity representation. + * + * @param entity The entity representation. + */ + public void setEntity(Representation entity) { + this.entity = entity; + } + + /** + * Sets a textual entity. + * + * @param value The represented string. + * @param mediaType The representation's media-type. + */ + public void setEntity(String value, MediaType mediaType) { + setEntity(new StringRepresentation(value, mediaType)); + } + + /** + * Sets the callback invoked when an error occurs when sending the message. + * + * @param onError The callback invoked when an error occurs when sending the message. + */ + public void setOnError(Uniform onError) { + this.onError = onError; + } + + /** + * Sets the callback invoked after sending the message. + * + * @param onSentCallback The callback invoked after sending the message. + */ + public void setOnSent(Uniform onSentCallback) { + this.onSent = onSentCallback; + } + + /** + * Sets the modifiable list of intermediary recipients. Note that when used with HTTP + * connectors, this property maps to the "Via" headers. This method clears the current list and + * adds all entries in the parameter list. + * + * @param recipientsInfo A list of intermediary recipients. + */ + public void setRecipientsInfo(List recipientsInfo) { + synchronized (getRecipientsInfo()) { + if (recipientsInfo != getRecipientsInfo()) { + getRecipientsInfo().clear(); + + if (recipientsInfo != null) { + getRecipientsInfo().addAll(recipientsInfo); + } + } + } + } + + /** + * Sets the additional warnings information. Note that when used with HTTP connectors, this + * property maps to the "Warning" headers. This method clears the current list and adds all + * entries in the parameter list. + * + * @param warnings The warnings. + */ + public void setWarnings(List warnings) { + synchronized (getWarnings()) { + if (warnings != getWarnings()) { + getWarnings().clear(); + + if (warnings != null) { + getWarnings().addAll(warnings); + } + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/Request.java b/org.restlet/src/main/java/org/restlet/Request.java index 458a0dae6e..f30028640f 100644 --- a/org.restlet/src/main/java/org/restlet/Request.java +++ b/org.restlet/src/main/java/org/restlet/Request.java @@ -1,915 +1,910 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; -import org.restlet.data.*; -import org.restlet.representation.Representation; -import org.restlet.util.Series; - import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; +import org.restlet.data.CacheDirective; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.CharacterSet; +import org.restlet.data.ClientInfo; +import org.restlet.data.Conditions; +import org.restlet.data.Cookie; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Preference; +import org.restlet.data.Protocol; +import org.restlet.data.Range; +import org.restlet.data.Reference; +import org.restlet.data.Tag; +import org.restlet.data.Warning; +import org.restlet.representation.Representation; +import org.restlet.util.Series; /** - * Generic request sent by client connectors. It is then received by server - * connectors and processed by {@link Restlet}s. This request can also be - * processed by a chain of Restlets, on both client and server sides. Requests - * are uniform across all types of connectors, protocols and components. - * + * Generic request sent by client connectors. It is then received by server connectors and processed + * by {@link Restlet}s. This request can also be processed by a chain of Restlets, on both client + * and server sides. Requests are uniform across all types of connectors, protocols, and components. + * * @see org.restlet.Response * @see org.restlet.Uniform * @author Jerome Louvel */ public class Request extends Message { - /** - * Returns the request associated to the current thread. This is reusing the - * {@link Response#getCurrent()} method.
- *
- * Warning: this method should only be used under duress. You should by default - * prefer obtaining the current context using methods such as - * {@link org.restlet.resource.Resource#getRequest()}. - * - * @return The thread's request. - */ - public static Request getCurrent() { - return (Response.getCurrent() == null) ? null : Response.getCurrent().getRequest(); - } - - /** - * Used when issuing a preflight CORS request to let the origin server knows - * what headers the client is willing to send in future request to this - * resource. - */ - private volatile Set accessControlRequestHeaders; - - /** - * Used when issuing a preflight CORS request to let the origin server knows - * what method the client is willing to send in future request to this resource. - */ - private volatile Method accessControlRequestMethod; - - /** The authentication response sent by a client to an origin server. */ - private volatile ChallengeResponse challengeResponse; - - /** The client-specific information. */ - private volatile ClientInfo clientInfo; - - /** The condition data. */ - private volatile Conditions conditions; - - /** The cookies provided by the client. */ - private volatile Series cookies; - - /** The host reference. */ - private volatile Reference hostRef; - - /** Indicates if the call is loggable. */ - private volatile boolean loggable; - - /** The maximum number of intermediaries. */ - private volatile int maxForwards; - - /** The method. */ - private volatile Method method; - - /** Callback invoked on response reception. */ - private volatile Uniform onResponse; - - /** The original reference. */ - private volatile Reference originalRef; - - /** The protocol. */ - private volatile Protocol protocol; - - /** The authentication response sent by a client to a proxy. */ - private volatile ChallengeResponse proxyChallengeResponse; - - /** The ranges to return from the target resource's representation. */ - private volatile List ranges; - - /** The referrer reference. */ - private volatile Reference referrerRef; - - /** The resource reference. */ - private volatile Reference resourceRef; - - /** The application root reference. */ - private volatile Reference rootRef; - - /** - * Constructor. - */ - public Request() { - this(null, (Reference) null, null); - } - - /** - * Constructor. - * - * @param method The call's method. - * @param resourceRef The resource reference. - */ - public Request(Method method, Reference resourceRef) { - this(method, resourceRef, null); - } - - /** - * Constructor. - * - * @param method The call's method. - * @param resourceRef The resource reference. - * @param entity The entity. - */ - public Request(Method method, Reference resourceRef, Representation entity) { - super(entity); - this.accessControlRequestHeaders = null; - this.accessControlRequestMethod = null; - this.challengeResponse = null; - this.clientInfo = null; - this.conditions = null; - this.cookies = null; - this.hostRef = null; - this.loggable = true; - this.maxForwards = -1; - this.method = method; - this.originalRef = null; - this.onResponse = null; - this.proxyChallengeResponse = null; - this.protocol = null; - this.ranges = null; - this.referrerRef = null; - this.resourceRef = resourceRef; - this.rootRef = null; - } - - /** - * Constructor. - * - * @param method The call's method. - * @param resourceUri The resource URI. - */ - public Request(Method method, String resourceUri) { - this(method, new Reference(resourceUri)); - } - - /** - * Constructor. - * - * @param method The call's method. - * @param resourceUri The resource URI. - * @param entity The entity. - */ - public Request(Method method, String resourceUri, Representation entity) { - this(method, new Reference(resourceUri), entity); - } - - /** - * Copy constructor. - * - * @param request The request to copy. - */ - public Request(Request request) { - this(request.getMethod(), new Reference(request.getResourceRef()), request.getEntity()); - challengeResponse = request.getChallengeResponse(); - - // Copy client info - ClientInfo rci = request.getClientInfo(); - clientInfo = new ClientInfo(); - - for (Preference o : rci.getAcceptedCharacterSets()) { - clientInfo.getAcceptedCharacterSets().add(o); - } - - for (Preference o : rci.getAcceptedEncodings()) { - clientInfo.getAcceptedEncodings().add(o); - } - - for (Preference o : rci.getAcceptedLanguages()) { - clientInfo.getAcceptedLanguages().add(o); - } - - for (Preference o : rci.getAcceptedMediaTypes()) { - clientInfo.getAcceptedMediaTypes().add(o); - } - - clientInfo.setAddress(rci.getAddress()); - clientInfo.setAgent(rci.getAgent()); - - for (String o : rci.getForwardedAddresses()) { - clientInfo.getForwardedAddresses().add(o); - } - - clientInfo.setFrom(rci.getFrom()); - clientInfo.setPort(rci.getPort()); - - clientInfo.setAgentAttributes(rci.getAgentAttributes()); - clientInfo.setAgentProducts(rci.getAgentProducts()); - clientInfo.setAuthenticated(rci.isAuthenticated()); - - for (org.restlet.data.Expectation o : rci.getExpectations()) { - clientInfo.getExpectations().add(o); - } - - for (java.security.Principal o : rci.getPrincipals()) { - clientInfo.getPrincipals().add(o); - } - - for (org.restlet.security.Role o : rci.getRoles()) { - clientInfo.getRoles().add(o); - } - - clientInfo.setUser(rci.getUser()); - - // Copy conditions - conditions = new Conditions(); - - for (Tag o : request.getConditions().getMatch()) { - conditions.getMatch().add(o); - } - - conditions.setModifiedSince(request.getConditions().getModifiedSince()); - - for (Tag o : request.getConditions().getNoneMatch()) { - conditions.getNoneMatch().add(o); - } - - conditions.setRangeDate(request.getConditions().getRangeDate()); - conditions.setRangeTag(request.getConditions().getRangeTag()); - conditions.setUnmodifiedSince(request.getConditions().getUnmodifiedSince()); - - for (Cookie o : request.getCookies()) { - getCookies().add(o); - } - - this.hostRef = request.getHostRef(); - this.maxForwards = request.getMaxForwards(); - this.originalRef = (request.getOriginalRef() == null) ? null : new Reference(request.getOriginalRef()); - this.onResponse = request.getOnResponse(); - this.proxyChallengeResponse = request.getProxyChallengeResponse(); - this.protocol = request.getProtocol(); - - for (Range o : request.getRanges()) { - getRanges().add(o); - } - - this.referrerRef = (request.getReferrerRef() == null) ? null : new Reference(request.getReferrerRef()); - this.rootRef = (request.getRootRef() == null) ? null : request.getRootRef(); - - for (Entry e : request.getAttributes().entrySet()) { - getAttributes().put(e.getKey(), e.getValue()); - } - - for (CacheDirective o : request.getCacheDirectives()) { - getCacheDirectives().add(o); - } - - this.setOnSent(request.getOnSent()); - - for (Warning o : request.getWarnings()) { - getWarnings().add(o); - } - - this.setDate(request.getDate()); - } - - /** - * Ask the connector to attempt to abort the related network connection, for - * example immediately closing the socket. - * - * @return True if the request was aborted. - */ - public boolean abort() { - return false; - } - - /** - * Asks the server connector to immediately commit the given response associated - * to this request, making it ready to be sent back to the client. Note that all - * server connectors don't necessarily support this feature. - * - * @param response The response to commit. - * - */ - public void commit(Response response) { - } - - /** - * Returns the modifiable set of headers the client is willing to send in future - * request to this resource. Used when issuing a preflight CORS request to let - * the origin server knows what headers will be sent later.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Headers" header. - * - * @return The headers the client is willing to send in future request to this - * resource. Useful for CORS support. - */ - public Set getAccessControlRequestHeaders() { - // Lazy initialization with double-check. - Set a = this.accessControlRequestHeaders; - if (a == null) { - synchronized (this) { - a = this.accessControlRequestHeaders; - if (a == null) { - this.accessControlRequestHeaders = a = new CopyOnWriteArraySet(); - } - } - } - return a; - } - - /** - * Returns the method the client is willing to use in future request to this - * resource. Used when issuing a preflight CORS request to let the origin server - * knows what method will be sent later.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Request-Method" header. - * - * @return The method the client is willing to send in future request to this - * resource. Useful for CORS support. - */ - public Method getAccessControlRequestMethod() { - return this.accessControlRequestMethod; - } - - /** - * Returns the authentication response sent by a client to an origin server. - * Note that when used with HTTP connectors, this property maps to the - * "Authorization" header. - * - * @return The authentication response sent by a client to an origin server. - */ - public ChallengeResponse getChallengeResponse() { - return this.challengeResponse; - } - - /** - * Returns the client-specific information. Creates a new instance if no one has - * been set. - * - * @return The client-specific information. - */ - public ClientInfo getClientInfo() { - // Lazy initialization with double-check. - ClientInfo c = this.clientInfo; - if (c == null) { - synchronized (this) { - c = this.clientInfo; - if (c == null) { - this.clientInfo = c = new ClientInfo(); - } - } - } - return c; - } - - /** - * Returns the modifiable conditions applying to this request. Creates a new - * instance if no one has been set. - * - * @return The conditions applying to this call. - */ - public Conditions getConditions() { - // Lazy initialization with double-check. - Conditions c = this.conditions; - if (c == null) { - synchronized (this) { - c = this.conditions; - if (c == null) { - this.conditions = c = new Conditions(); - } - } - } - return c; - } - - /** - * Returns the modifiable series of cookies provided by the client. Creates a - * new instance if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the "Cookie" - * header. - * - * @return The cookies provided by the client. - */ - public Series getCookies() { - // Lazy initialization with double-check. - Series c = this.cookies; - if (c == null) { - synchronized (this) { - c = this.cookies; - if (c == null) { - this.cookies = c = new Series(Cookie.class); - } - } - } - return c; - } - - /** - * Returns the host reference. This may be different from the resourceRef's - * host, for example, for URNs and other URIs that don't contain host - * information.
- *
- * Note that when used with HTTP connectors, this property maps to the "Host" - * header. - * - * @return The host reference. - */ - public Reference getHostRef() { - return this.hostRef; - } - - /** - * Returns the maximum number of intermediaries. - * - * @return The maximum number of intermediaries. - */ - public int getMaxForwards() { - return maxForwards; - } - - /** - * Returns the method. - * - * @return The method. - */ - public Method getMethod() { - return this.method; - } - - /** - * Returns the callback invoked on response reception. If the value is not null, - * then the associated request will be executed asynchronously. - * - * @return The callback invoked on response reception. - */ - public Uniform getOnResponse() { - return onResponse; - } - - /** - * Returns the original reference as requested by the client. Note that this - * property is not used during request routing. See the - * {@link #getResourceRef()} method for details. - * - * @return The original reference. - * @see #getResourceRef() - */ - public Reference getOriginalRef() { - return this.originalRef; - } - - /** - * Returns the protocol used or to be used, if known. - * - * @return The protocol used or to be used. - */ - public Protocol getProtocol() { - Protocol result = this.protocol; - - if ((result == null) && (getResourceRef() != null)) { - // Attempt to guess the protocol to use - // from the target reference scheme - result = getResourceRef().getSchemeProtocol(); - // Fallback: look at base reference scheme - if (result == null) { - result = (getResourceRef().getBaseRef() != null) ? getResourceRef().getBaseRef().getSchemeProtocol() - : null; - } - } - - return result; - } - - /** - * Returns the authentication response sent by a client to a proxy. Note that - * when used with HTTP connectors, this property maps to the - * "Proxy-Authorization" header. - * - * @return The authentication response sent by a client to a proxy. - */ - public ChallengeResponse getProxyChallengeResponse() { - return this.proxyChallengeResponse; - } - - /** - * Returns the ranges to return from the target resource's representation. Note - * that when used with HTTP connectors, this property maps to the "Range" - * header. - * - * @return The ranges to return. - */ - public List getRanges() { - // Lazy initialization with double-check. - List r = this.ranges; - if (r == null) { - synchronized (this) { - r = this.ranges; - if (r == null) { - this.ranges = r = new CopyOnWriteArrayList(); - } - } - } - return r; - } - - /** - * Returns the referrer reference if available. Note that when used with HTTP - * connectors, this property maps to the "Referer" header. - * - * @return The referrer reference. - */ - public Reference getReferrerRef() { - return this.referrerRef; - } - - /** - * Returns the reference of the target resource. This reference is especially - * important during routing, dispatching and resource finding. During such - * processing, its base reference is constantly updated to reflect the reference - * of the parent Restlet or resource and the remaining part of the URI that must - * be routed or analyzed. - * - * If you need to get the URI reference originally requested by the client, then - * you should use the {@link #getOriginalRef()} method instead. Also, note that - * beside the update of its base property, the resource reference can be - * modified during the request processing. - * - * For example, the {@link org.restlet.service.TunnelService} associated to an - * application can extract some special extensions or query parameters and - * replace them by semantically equivalent properties on the request object. - * Therefore, the resource reference can become different from the original - * reference. - * - * Finally, when sending out requests via a dispatcher such as - * {@link Context#getClientDispatcher()} or - * {@link Context#getServerDispatcher()}, if the reference contains URI template - * variables, those variables are automatically resolved using the request's - * attributes. - * - * @return The reference of the target resource. - * @see #getOriginalRef() - * @see #getHostRef() - */ - public Reference getResourceRef() { - return this.resourceRef; - } - - /** - * Returns the application root reference. - * - * @return The application root reference. - */ - public Reference getRootRef() { - return this.rootRef; - } - - /** - * Indicates if the request is asynchronous. The test consist in verifying that - * the {@link #getOnResponse()} method returns a callback object. - * - * @return True if the request is synchronous. - */ - public boolean isAsynchronous() { - return getOnResponse() != null; - } - - /** - * Implemented based on the {@link Protocol#isConfidential()} method for the - * request's protocol returned by {@link #getProtocol()}; - */ - @Override - public boolean isConfidential() { - return (getProtocol() == null) ? false : getProtocol().isConfidential(); - } - - /** - * Indicates if a content is available and can be sent. Several conditions must - * be met: the method must allow the sending of content, the content must exists - * and have some available data. - * - * @return True if a content is available and can be sent. - */ - @Override - public boolean isEntityAvailable() { - if ((Method.GET.equals(getMethod()) || Method.HEAD.equals(getMethod()))) { - return false; - } - - return super.isEntityAvailable(); - } - - /** - * Indicates if an associated response is expected. - * - * @return True if an associated response is expected. - */ - public boolean isExpectingResponse() { - return (getMethod() == null) ? false : getMethod().isReplying(); - } - - /** - * Indicates if the call is loggable - * - * @return True if the call is loggable - */ - public boolean isLoggable() { - return loggable; - } - - /** - * Indicates if the request is synchronous. The test consist in verifying that - * the {@link #getOnResponse()} method returns null. - * - * @return True if the request is synchronous. - */ - public boolean isSynchronous() { - return getOnResponse() == null; - } - - /** - * Sets the set of headers the client is willing to use in future request to - * this resource. Used when issuing a preflight CORS request to let the origin - * server knows what headers will be sent later.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Request-Method" header. - * - * @param accessControlRequestHeaders The set of headers the client is willing - * to send in future request to this - * resource. Useful for CORS support. - */ - public void setAccessControlRequestHeaders(Set accessControlRequestHeaders) { - synchronized (getAccessControlRequestHeaders()) { - if (accessControlRequestHeaders != this.accessControlRequestHeaders) { - this.accessControlRequestHeaders.clear(); - - if (accessControlRequestHeaders != null) { - this.accessControlRequestHeaders.addAll(accessControlRequestHeaders); - } - } - } - } - - /** - * Sets the method the client is willing to use in future request to this - * resource. Used when issuing a preflight CORS request to let the origin server - * knows what method will be sent later.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Request-Method" header. - * - * @param accessControlRequestMethod The method the client is willing to send in - * future request to this resource. Useful for - * CORS support. - */ - public void setAccessControlRequestMethod(Method accessControlRequestMethod) { - this.accessControlRequestMethod = accessControlRequestMethod; - } - - /** - * Sets the authentication response sent by a client to an origin server. Note - * that when used with HTTP connectors, this property maps to the - * "Authorization" header. - * - * @param challengeResponse The authentication response sent by a client to an - * origin server. - */ - public void setChallengeResponse(ChallengeResponse challengeResponse) { - this.challengeResponse = challengeResponse; - } - - /** - * Sets the client-specific information. - * - * @param clientInfo The client-specific information. - */ - public void setClientInfo(ClientInfo clientInfo) { - this.clientInfo = clientInfo; - } - - /** - * Sets the conditions applying to this request. - * - * @param conditions The conditions applying to this request. - */ - public void setConditions(Conditions conditions) { - this.conditions = conditions; - } - - /** - * Sets the modifiable series of cookies provided by the client. Note that when - * used with HTTP connectors, this property maps to the "Cookie" header. This - * method clears the current series and adds all entries in the parameter - * series. - * - * @param cookies A series of cookies provided by the client. - */ - public void setCookies(Series cookies) { - synchronized (getCookies()) { - if (cookies != getCookies()) { - if (getCookies() != null) { - getCookies().clear(); - } - - if (cookies != null) { - getCookies().addAll(cookies); - } - } - } - } - - /** - * Sets the host reference. Note that when used with HTTP connectors, this - * property maps to the "Host" header. - * - * @param hostRef The host reference. - */ - public void setHostRef(Reference hostRef) { - this.hostRef = hostRef; - } - - /** - * Sets the host reference using a URI string. Note that when used with HTTP - * connectors, this property maps to the "Host" header. - * - * @param hostUri The host URI. - */ - public void setHostRef(String hostUri) { - setHostRef(new Reference(hostUri)); - } - - /** - * Indicates if the call is loggable - * - * @param loggable True if the call is loggable - */ - public void setLoggable(boolean loggable) { - this.loggable = loggable; - } - - /** - * Sets the maximum number of intermediaries. - * - * @param maxForwards The maximum number of intermediaries. - */ - public void setMaxForwards(int maxForwards) { - this.maxForwards = maxForwards; - } - - /** - * Sets the method called. - * - * @param method The method called. - */ - public void setMethod(Method method) { - this.method = method; - } - - /** - * Sets the callback invoked on response reception. If the value is not null, - * then the associated request will be executed asynchronously. - * - * @param onResponseCallback The callback invoked on response reception. - */ - public void setOnResponse(Uniform onResponseCallback) { - this.onResponse = onResponseCallback; - } - - /** - * Sets the original reference requested by the client. - * - * @param originalRef The original reference. - * @see #getOriginalRef() - */ - public void setOriginalRef(Reference originalRef) { - this.originalRef = originalRef; - } - - /** - * Sets the protocol used or to be used. - * - * @param protocol The protocol used or to be used. - */ - public void setProtocol(Protocol protocol) { - this.protocol = protocol; - } - - /** - * Sets the authentication response sent by a client to a proxy. Note that when - * used with HTTP connectors, this property maps to the "Proxy-Authorization" - * header. - * - * @param challengeResponse The authentication response sent by a client to a - * proxy. - */ - public void setProxyChallengeResponse(ChallengeResponse challengeResponse) { - this.proxyChallengeResponse = challengeResponse; - } - - /** - * Sets the modifiable list of ranges to return from the target resource's - * representation. Note that when used with HTTP connectors, this property maps - * to the "Range" header. This method clears the current list and adds all - * entries in the parameter list. - * - * @param ranges A list of ranges. - */ - public void setRanges(List ranges) { - synchronized (getRanges()) { - if (ranges != getRanges()) { - getRanges().clear(); - - if (ranges != null) { - getRanges().addAll(ranges); - } - } - } - } - - /** - * Sets the referrer reference if available. Note that when used with HTTP - * connectors, this property maps to the "Referer" header. - * - * @param referrerRef The referrer reference. - */ - public void setReferrerRef(Reference referrerRef) { - this.referrerRef = referrerRef; - - // A referrer reference must not include a fragment. - if ((this.referrerRef != null) && (this.referrerRef.getFragment() != null)) { - this.referrerRef.setFragment(null); - } - } - - /** - * Sets the referrer reference if available using an URI string. Note that when - * used with HTTP connectors, this property maps to the "Referer" header. - * - * @param referrerUri The referrer URI. - * @see #setReferrerRef(Reference) - */ - public void setReferrerRef(String referrerUri) { - setReferrerRef(new Reference(referrerUri)); - } - - /** - * Sets the target resource reference. If the reference is relative, it will be - * resolved as an absolute reference. Also, the context's base reference will be - * reset. Finally, the reference will be normalized to ensure a consistent - * handling of the call. - * - * @param resourceRef The resource reference. - * @see #getResourceRef() - */ - public void setResourceRef(Reference resourceRef) { - this.resourceRef = resourceRef; - } - - /** - * Sets the target resource reference using an URI string. Note that the URI can - * be either absolute or relative to the context's base reference. - * - * @param resourceUri The resource URI. - * @see #setResourceRef(Reference) - */ - public void setResourceRef(String resourceUri) { - if (getResourceRef() != null) { - // Allow usage of URIs relative to the current base reference - setResourceRef(new Reference(getResourceRef().getBaseRef(), resourceUri)); - } else { - setResourceRef(new Reference(resourceUri)); - } - } - - /** - * Sets the application root reference. - * - * @param rootRef The application root reference. - */ - public void setRootRef(Reference rootRef) { - this.rootRef = rootRef; - } - - /** - * Displays a synthesis of the request like an HTTP request line. - * - * @return A synthesis of the request like an HTTP request line. - */ - public String toString() { - return ((getMethod() == null) ? "" : getMethod().toString()) + " " - + ((getResourceRef() == null) ? "" : getResourceRef().toString()) + " " - + ((getProtocol() == null) ? "" - : (getProtocol().getName() - + ((getProtocol().getVersion() == null) ? "" : "/" + getProtocol().getVersion()))); - } - + /** + * Returns the request associated with the current thread. This is reusing the {@link + * Response#getCurrent()} method.
+ *
+ * Warning: this method should only be used under duress. You should by default prefer getting + * the current context using methods such as {@link org.restlet.resource.Resource#getRequest()}. + * + * @return The thread's request. + */ + public static Request getCurrent() { + return (Response.getCurrent() == null) ? null : Response.getCurrent().getRequest(); + } + + /** + * Used when issuing a preflight CORS request to let the origin server knows what headers the + * client is willing to send in future request to this resource. + */ + private volatile Set accessControlRequestHeaders; + + /** + * Used when issuing a preflight CORS request to let the origin server knows what method the + * client is willing to send in a future request to this resource. + */ + private volatile Method accessControlRequestMethod; + + /** The authentication response sent by a client to an origin server. */ + private volatile ChallengeResponse challengeResponse; + + /** The client-specific information. */ + private volatile ClientInfo clientInfo; + + /** The condition data. */ + private volatile Conditions conditions; + + /** The cookies provided by the client. */ + private volatile Series cookies; + + /** The host reference. */ + private volatile Reference hostRef; + + /** Indicates if the call is loggable. */ + private volatile boolean loggable; + + /** The maximum number of intermediaries. */ + private volatile int maxForwards; + + /** The method. */ + private volatile Method method; + + /** Callback invoked on response reception. */ + private volatile Uniform onResponse; + + /** The original reference. */ + private volatile Reference originalRef; + + /** The protocol. */ + private volatile Protocol protocol; + + /** The authentication response sent by a client to a proxy. */ + private volatile ChallengeResponse proxyChallengeResponse; + + /** The ranges to return from the target resource's representation. */ + private volatile List ranges; + + /** The referrer reference. */ + private volatile Reference referrerRef; + + /** The resource reference. */ + private volatile Reference resourceRef; + + /** The application root reference. */ + private volatile Reference rootRef; + + /** Constructor. */ + public Request() { + this(null, (Reference) null, null); + } + + /** + * Constructor. + * + * @param method The call's method. + * @param resourceRef The resource reference. + */ + public Request(Method method, Reference resourceRef) { + this(method, resourceRef, null); + } + + /** + * Constructor. + * + * @param method The call's method. + * @param resourceRef The resource reference. + * @param entity The entity. + */ + public Request(Method method, Reference resourceRef, Representation entity) { + super(entity); + this.accessControlRequestHeaders = null; + this.accessControlRequestMethod = null; + this.challengeResponse = null; + this.clientInfo = null; + this.conditions = null; + this.cookies = null; + this.hostRef = null; + this.loggable = true; + this.maxForwards = -1; + this.method = method; + this.originalRef = null; + this.onResponse = null; + this.proxyChallengeResponse = null; + this.protocol = null; + this.ranges = null; + this.referrerRef = null; + this.resourceRef = resourceRef; + this.rootRef = null; + } + + /** + * Constructor. + * + * @param method The call's method. + * @param resourceUri The resource URI. + */ + public Request(Method method, String resourceUri) { + this(method, new Reference(resourceUri)); + } + + /** + * Constructor. + * + * @param method The call's method. + * @param resourceUri The resource URI. + * @param entity The entity. + */ + public Request(Method method, String resourceUri, Representation entity) { + this(method, new Reference(resourceUri), entity); + } + + /** + * Copy constructor. + * + * @param request The request to copy. + */ + public Request(Request request) { + this(request.getMethod(), new Reference(request.getResourceRef()), request.getEntity()); + challengeResponse = request.getChallengeResponse(); + + // Copy client info + ClientInfo rci = request.getClientInfo(); + clientInfo = new ClientInfo(); + + for (Preference o : rci.getAcceptedCharacterSets()) { + clientInfo.getAcceptedCharacterSets().add(o); + } + + for (Preference o : rci.getAcceptedEncodings()) { + clientInfo.getAcceptedEncodings().add(o); + } + + for (Preference o : rci.getAcceptedLanguages()) { + clientInfo.getAcceptedLanguages().add(o); + } + + for (Preference o : rci.getAcceptedMediaTypes()) { + clientInfo.getAcceptedMediaTypes().add(o); + } + + clientInfo.setAddress(rci.getAddress()); + clientInfo.setAgent(rci.getAgent()); + + for (String o : rci.getForwardedAddresses()) { + clientInfo.getForwardedAddresses().add(o); + } + + clientInfo.setFrom(rci.getFrom()); + clientInfo.setPort(rci.getPort()); + + clientInfo.setAgentAttributes(rci.getAgentAttributes()); + clientInfo.setAgentProducts(rci.getAgentProducts()); + clientInfo.setAuthenticated(rci.isAuthenticated()); + + for (org.restlet.data.Expectation o : rci.getExpectations()) { + clientInfo.getExpectations().add(o); + } + + for (java.security.Principal o : rci.getPrincipals()) { + clientInfo.getPrincipals().add(o); + } + + for (org.restlet.security.Role o : rci.getRoles()) { + clientInfo.getRoles().add(o); + } + + clientInfo.setUser(rci.getUser()); + + // Copy conditions + conditions = new Conditions(); + + for (Tag o : request.getConditions().getMatch()) { + conditions.getMatch().add(o); + } + + conditions.setModifiedSince(request.getConditions().getModifiedSince()); + + for (Tag o : request.getConditions().getNoneMatch()) { + conditions.getNoneMatch().add(o); + } + + conditions.setRangeDate(request.getConditions().getRangeDate()); + conditions.setRangeTag(request.getConditions().getRangeTag()); + conditions.setUnmodifiedSince(request.getConditions().getUnmodifiedSince()); + + for (Cookie o : request.getCookies()) { + getCookies().add(o); + } + + this.hostRef = request.getHostRef(); + this.maxForwards = request.getMaxForwards(); + this.originalRef = + (request.getOriginalRef() == null) ? null : new Reference(request.getOriginalRef()); + this.onResponse = request.getOnResponse(); + this.proxyChallengeResponse = request.getProxyChallengeResponse(); + this.protocol = request.getProtocol(); + + for (Range o : request.getRanges()) { + getRanges().add(o); + } + + this.referrerRef = + (request.getReferrerRef() == null) ? null : new Reference(request.getReferrerRef()); + this.rootRef = (request.getRootRef() == null) ? null : request.getRootRef(); + + for (Entry e : request.getAttributes().entrySet()) { + getAttributes().put(e.getKey(), e.getValue()); + } + + for (CacheDirective o : request.getCacheDirectives()) { + getCacheDirectives().add(o); + } + + this.setOnSent(request.getOnSent()); + + for (Warning o : request.getWarnings()) { + getWarnings().add(o); + } + + this.setDate(request.getDate()); + } + + /** + * Ask the connector to attempt to abort the related network connection, for example immediately + * closing the socket. + * + * @return True if the request was aborted. + */ + public boolean abort() { + return false; + } + + /** + * Asks the server connector to immediately commit the given response associated with this + * request, making it ready to be sent back to the client. Note that all server connectors don't + * necessarily support this feature. + * + * @param response The response to commit. + */ + public void commit(Response response) { + // No-op by default + } + + /** + * Returns the modifiable set of headers the client is willing to send in future request to this + * resource. Used when issuing a preflight CORS request to let the origin server know what + * headers will be sent later.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Headers" header. + * + * @return The headers the client is willing to send in future request to this resource. Useful + * for CORS support. + */ + public Set getAccessControlRequestHeaders() { + // Lazy initialization with double-check. + Set a = this.accessControlRequestHeaders; + if (a == null) { + synchronized (this) { + a = this.accessControlRequestHeaders; + if (a == null) { + this.accessControlRequestHeaders = a = new CopyOnWriteArraySet<>(); + } + } + } + return a; + } + + /** + * Returns the method the client is willing to use in future request to this resource. Used when + * issuing a preflight CORS request to let the origin server know what method will be sent + * later.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Request-Method" header. + * + * @return The method the client is willing to send in a future request to this resource. Useful + * for CORS support. + */ + public Method getAccessControlRequestMethod() { + return this.accessControlRequestMethod; + } + + /** + * Returns the authentication response sent by a client to an origin server. Note that when used + * with HTTP connectors, this property maps to the "Authorization" header. + * + * @return The authentication response sent by a client to an origin server. + */ + public ChallengeResponse getChallengeResponse() { + return this.challengeResponse; + } + + /** + * Returns the client-specific information. Creates a new instance if no one has been set. + * + * @return The client-specific information. + */ + public ClientInfo getClientInfo() { + // Lazy initialization with double-check. + ClientInfo c = this.clientInfo; + if (c == null) { + synchronized (this) { + c = this.clientInfo; + if (c == null) { + this.clientInfo = c = new ClientInfo(); + } + } + } + return c; + } + + /** + * Returns the modifiable conditions applying to this request. Creates a new instance if no one + * has been set. + * + * @return The conditions applying to this call. + */ + public Conditions getConditions() { + // Lazy initialization with double-check. + Conditions c = this.conditions; + if (c == null) { + synchronized (this) { + c = this.conditions; + if (c == null) { + this.conditions = c = new Conditions(); + } + } + } + return c; + } + + /** + * Returns the modifiable series of cookies provided by the client. Creates a new instance if no + * one has been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Cookie" header. + * + * @return The cookies provided by the client. + */ + public Series getCookies() { + // Lazy initialization with double-check. + Series c = this.cookies; + if (c == null) { + synchronized (this) { + c = this.cookies; + if (c == null) { + this.cookies = c = new Series<>(Cookie.class); + } + } + } + return c; + } + + /** + * Returns the host reference. This may be different from the resourceRef's host, for example, + * for URNs and other URIs that don't contain host information.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Host" header. + * + * @return The host reference. + */ + public Reference getHostRef() { + return this.hostRef; + } + + /** + * Returns the maximum number of intermediaries. + * + * @return The maximum number of intermediaries. + */ + public int getMaxForwards() { + return maxForwards; + } + + /** + * Returns the method. + * + * @return The method. + */ + public Method getMethod() { + return this.method; + } + + /** + * Returns the callback invoked on response reception. If the value is not null, then the + * associated request will be executed asynchronously. + * + * @return The callback invoked on response reception. + */ + public Uniform getOnResponse() { + return onResponse; + } + + /** + * Returns the original reference as requested by the client. Note that this property is not + * used during request routing. See the {@link #getResourceRef()} method for details. + * + * @return The original reference. + * @see #getResourceRef() + */ + public Reference getOriginalRef() { + return this.originalRef; + } + + /** + * Returns the protocol used or to be used, if known. + * + * @return The protocol used or to be used. + */ + public Protocol getProtocol() { + Protocol result = this.protocol; + + if ((result == null) && (getResourceRef() != null)) { + // Attempt to guess the protocol to use + // from the target reference scheme + result = getResourceRef().getSchemeProtocol(); + // Fallback: look at a base reference scheme + if (result == null) { + result = + (getResourceRef().getBaseRef() != null) + ? getResourceRef().getBaseRef().getSchemeProtocol() + : null; + } + } + + return result; + } + + /** + * Returns the authentication response sent by a client to a proxy. Note that when used with + * HTTP connectors, this property maps to the "Proxy-Authorization" header. + * + * @return The authentication response sent by a client to a proxy. + */ + public ChallengeResponse getProxyChallengeResponse() { + return this.proxyChallengeResponse; + } + + /** + * Returns the ranges to return from the target resource's representation. Note that when used + * with HTTP connectors, this property maps to the "Range" header. + * + * @return The ranges to return. + */ + public List getRanges() { + // Lazy initialization with double-check. + List r = this.ranges; + if (r == null) { + synchronized (this) { + r = this.ranges; + if (r == null) { + this.ranges = r = new CopyOnWriteArrayList<>(); + } + } + } + return r; + } + + /** + * Returns the referrer reference if available. Note that when used with HTTP connectors, this + * property maps to the "Referer" header. + * + * @return The referrer reference. + */ + public Reference getReferrerRef() { + return this.referrerRef; + } + + /** + * Returns the reference of the target resource. This reference is especially important during + * routing, dispatching, and resource finding. During such processing, its base reference is + * constantly updated to reflect the reference of the parent Restlet or resource and the + * remaining part of the URI that must be routed or analyzed. + * + *

If you need to get the URI reference originally requested by the client, then you should + * use the {@link #getOriginalRef()} method instead. Also, note that besides the update of its + * base property, the resource reference can be modified during the request processing. + * + *

For example, the {@link org.restlet.service.TunnelService} associated with an application + * can extract some special extensions or query parameters and replace them by semantically + * equivalent properties on the request object. Therefore, the resource reference can become + * different from the original reference. + * + *

Finally, when sending out requests via a dispatcher such as {@link + * Context#getClientDispatcher()} or {@link Context#getServerDispatcher()}, if the reference + * contains URI template variables, those variables are automatically resolved using the + * request's attributes. + * + * @return The reference of the target resource. + * @see #getOriginalRef() + * @see #getHostRef() + */ + public Reference getResourceRef() { + return this.resourceRef; + } + + /** + * Returns the application root reference. + * + * @return The application root reference. + */ + public Reference getRootRef() { + return this.rootRef; + } + + /** + * Indicates if the request is asynchronous. The test consist in verifying that the {@link + * #getOnResponse()} method returns a callback object. + * + * @return True if the request is synchronous. + */ + public boolean isAsynchronous() { + return getOnResponse() != null; + } + + /** + * Implemented based on the {@link Protocol#isConfidential()} method for the request's protocol + * returned by {@link #getProtocol()}; + */ + @Override + public boolean isConfidential() { + return getProtocol() != null && getProtocol().isConfidential(); + } + + /** + * Indicates if a content is available and can be sent. Several conditions must be met: the + * method must allow the sending of content, the content must exist and have some available + * data. + * + * @return True if a content is available and can be sent. + */ + @Override + public boolean isEntityAvailable() { + if ((Method.GET.equals(getMethod()) || Method.HEAD.equals(getMethod()))) { + return false; + } + + return super.isEntityAvailable(); + } + + /** + * Indicates if an associated response is expected. + * + * @return True if an associated response is expected. + */ + public boolean isExpectingResponse() { + return getMethod() != null && getMethod().isReplying(); + } + + /** + * Indicates if the call is loggable + * + * @return True if the call is loggable + */ + public boolean isLoggable() { + return loggable; + } + + /** + * Indicates if the request is synchronous. The test consist in verifying that the {@link + * #getOnResponse()} method returns null. + * + * @return True if the request is synchronous. + */ + public boolean isSynchronous() { + return getOnResponse() == null; + } + + /** + * Sets the set of headers the client is willing to use in future request to this resource. Used + * when issuing a preflight CORS request to let the origin server know what headers will be sent + * later.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Request-Method" header. + * + * @param accessControlRequestHeaders The set of headers the client is willing to send in a + * future request to this resource. Useful for CORS support. + */ + public void setAccessControlRequestHeaders(Set accessControlRequestHeaders) { + synchronized (getAccessControlRequestHeaders()) { + if (accessControlRequestHeaders != this.accessControlRequestHeaders) { + this.accessControlRequestHeaders.clear(); + + if (accessControlRequestHeaders != null) { + this.accessControlRequestHeaders.addAll(accessControlRequestHeaders); + } + } + } + } + + /** + * Sets the method the client is willing to use in future request to this resource. Used when + * issuing a preflight CORS request to let the origin server know what method will be sent + * later.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Request-Method" header. + * + * @param accessControlRequestMethod The method the client is willing to send in future request + * to this resource. Useful for CORS support. + */ + public void setAccessControlRequestMethod(Method accessControlRequestMethod) { + this.accessControlRequestMethod = accessControlRequestMethod; + } + + /** + * Sets the authentication response sent by a client to an origin server. Note that when used + * with HTTP connectors, this property maps to the "Authorization" header. + * + * @param challengeResponse The authentication response sent by a client to an origin server. + */ + public void setChallengeResponse(ChallengeResponse challengeResponse) { + this.challengeResponse = challengeResponse; + } + + /** + * Sets the client-specific information. + * + * @param clientInfo The client-specific information. + */ + public void setClientInfo(ClientInfo clientInfo) { + this.clientInfo = clientInfo; + } + + /** + * Sets the conditions applying to this request. + * + * @param conditions The conditions applying to this request. + */ + public void setConditions(Conditions conditions) { + this.conditions = conditions; + } + + /** + * Sets the modifiable series of cookies provided by the client. Note that when used with HTTP + * connectors, this property maps to the "Cookie" header. This method clears the current series + * and adds all entries in the parameter series. + * + * @param cookies A series of cookies provided by the client. + */ + public void setCookies(Series cookies) { + synchronized (getCookies()) { + if (cookies != getCookies()) { + if (getCookies() != null) { + getCookies().clear(); + } + + if (cookies != null) { + getCookies().addAll(cookies); + } + } + } + } + + /** + * Sets the host reference. Note that when used with HTTP connectors, this property maps to the + * "Host" header. + * + * @param hostRef The host reference. + */ + public void setHostRef(Reference hostRef) { + this.hostRef = hostRef; + } + + /** + * Sets the host reference using a URI string. Note that when used with HTTP connectors, this + * property maps to the "Host" header. + * + * @param hostUri The host URI. + */ + public void setHostRef(String hostUri) { + setHostRef(new Reference(hostUri)); + } + + /** + * Indicates if the call is loggable + * + * @param loggable True if the call is loggable + */ + public void setLoggable(boolean loggable) { + this.loggable = loggable; + } + + /** + * Sets the maximum number of intermediaries. + * + * @param maxForwards The maximum number of intermediaries. + */ + public void setMaxForwards(int maxForwards) { + this.maxForwards = maxForwards; + } + + /** + * Sets the method called. + * + * @param method The method called. + */ + public void setMethod(Method method) { + this.method = method; + } + + /** + * Sets the callback invoked on response reception. If the value is not null, then the + * associated request will be executed asynchronously. + * + * @param onResponseCallback The callback invoked on response reception. + */ + public void setOnResponse(Uniform onResponseCallback) { + this.onResponse = onResponseCallback; + } + + /** + * Sets the original reference requested by the client. + * + * @param originalRef The original reference. + * @see #getOriginalRef() + */ + public void setOriginalRef(Reference originalRef) { + this.originalRef = originalRef; + } + + /** + * Sets the protocol used or to be used. + * + * @param protocol The protocol used or to be used. + */ + public void setProtocol(Protocol protocol) { + this.protocol = protocol; + } + + /** + * Sets the authentication response sent by a client to a proxy. Note that when used with HTTP + * connectors, this property maps to the "Proxy-Authorization" header. + * + * @param challengeResponse The authentication response sent by a client to a proxy. + */ + public void setProxyChallengeResponse(ChallengeResponse challengeResponse) { + this.proxyChallengeResponse = challengeResponse; + } + + /** + * Sets the modifiable list of ranges to return from the target resource's representation. Note + * that when used with HTTP connectors, this property maps to the "Range" header. This method + * clears the current list and adds all entries in the parameter list. + * + * @param ranges A list of ranges. + */ + public void setRanges(List ranges) { + synchronized (getRanges()) { + if (ranges != getRanges()) { + getRanges().clear(); + + if (ranges != null) { + getRanges().addAll(ranges); + } + } + } + } + + /** + * Sets the referrer reference if available. Note that when used with HTTP connectors, this + * property maps to the "Referer" header. + * + * @param referrerRef The referrer reference. + */ + public void setReferrerRef(Reference referrerRef) { + this.referrerRef = referrerRef; + + // A referrer reference must not include a fragment. + if ((this.referrerRef != null) && (this.referrerRef.getFragment() != null)) { + this.referrerRef.setFragment(null); + } + } + + /** + * Sets the referrer reference if available using a URI string. Note that when used with HTTP + * connectors, this property maps to the "Referer" header. + * + * @param referrerUri The referrer URI. + * @see #setReferrerRef(Reference) + */ + public void setReferrerRef(String referrerUri) { + setReferrerRef(new Reference(referrerUri)); + } + + /** + * Sets the target resource reference. If the reference is relative, it will be resolved as an + * absolute reference. Also, the context's base reference will be reset. Finally, the reference + * will be normalized to ensure a consistent handling of the call. + * + * @param resourceRef The resource reference. + * @see #getResourceRef() + */ + public void setResourceRef(Reference resourceRef) { + this.resourceRef = resourceRef; + } + + /** + * Sets the target resource reference using a URI string. Note that the URI can be either + * absolute or relative to the context's base reference. + * + * @param resourceUri The resource URI. + * @see #setResourceRef(Reference) + */ + public void setResourceRef(String resourceUri) { + if (getResourceRef() != null) { + // Allow usage of URIs relative to the current base reference + setResourceRef(new Reference(getResourceRef().getBaseRef(), resourceUri)); + } else { + setResourceRef(new Reference(resourceUri)); + } + } + + /** + * Sets the application root reference. + * + * @param rootRef The application root reference. + */ + public void setRootRef(Reference rootRef) { + this.rootRef = rootRef; + } + + /** + * Displays a synthesis of the request like an HTTP request line. + * + * @return A synthesis of the request like an HTTP request line. + */ + public String toString() { + return ((getMethod() == null) ? "" : getMethod().toString()) + + " " + + ((getResourceRef() == null) ? "" : getResourceRef().toString()) + + " " + + ((getProtocol() == null) + ? "" + : (getProtocol().getName() + + ((getProtocol().getVersion() == null) + ? "" + : "/" + getProtocol().getVersion()))); + } } diff --git a/org.restlet/src/main/java/org/restlet/Response.java b/org.restlet/src/main/java/org/restlet/Response.java index 7d85e33162..581f02e71e 100644 --- a/org.restlet/src/main/java/org/restlet/Response.java +++ b/org.restlet/src/main/java/org/restlet/Response.java @@ -1,1024 +1,993 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; -import org.restlet.data.*; -import org.restlet.util.Series; - import java.io.IOException; import java.util.Date; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; +import org.restlet.data.AuthenticationInfo; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.CookieSetting; +import org.restlet.data.Dimension; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.data.ServerInfo; +import org.restlet.data.Status; +import org.restlet.util.Series; /** - * Generic response sent by server connectors. It is then received by client - * connectors. Responses are uniform across all types of connectors, protocols - * and components. - * + * Generic response sent by server connectors. It is then received by client connectors. Responses + * are uniform across all types of connectors, protocols, and components. + * * @see org.restlet.Request * @see org.restlet.Uniform * @author Jerome Louvel */ public class Response extends Message { - private static final ThreadLocal CURRENT = new ThreadLocal(); - - /** - * Returns the response associated to the current thread. - * - * Warning: this method should only be used under duress. You should by default - * prefer obtaining the current context using methods such as - * {@link org.restlet.resource.Resource#getResponse()}. - * - * This variable is stored internally as a thread local variable and updated - * each time a call is handled by a Restlet via the - * {@link Restlet#handle(org.restlet.Request, org.restlet.Response)} method. - * - * @return The current context. - */ - public static Response getCurrent() { - return CURRENT.get(); - } - - /** - * Sets the response associated with the current thread. - * - * @param response The thread's response. - */ - public static void setCurrent(Response response) { - CURRENT.set(response); - } - - /** - * When used as part of a response to a preflight CORS request, this indicates - * whether or not the actual request can be made using credentials. - */ - private volatile Boolean accessControlAllowCredentials; - - /** - * When used as part of a response to a preflight CORS request, this lists the - * headers allowed by the actual request on the current resource. - */ - private volatile Set accessControlAllowHeaders; - - /** - * When used as part of a response to a preflight CORS request, this lists the - * methods allowed by the actual request on the current resource. - */ - private volatile Set accessControlAllowMethods; - - /** - * When used in the context of CORS support, it specifies the URI an origin - * server allows for the requested resource. Use "*" as a wildcard character. - */ - private volatile String accessControlAllowOrigin; - - /** - * The whitelist of headers an origin server allows for the requested resource. - */ - private volatile Set accessControlExposeHeaders; - - /** - * When used in the context of CORS support, it indicates how long the results - * of a preflight request can be cached in a preflight result cache. - */ - private volatile int accessControlMaxAge; - - /** - * Estimated amount of time since a response was generated or revalidated by the - * origin server. - */ - private volatile int age; - - /** The set of methods allowed on the requested resource. */ - private volatile Set allowedMethods; - - /** - * The authentication information sent by an origin server to a client in the - * case of a successful authentication attempt. - */ - private volatile AuthenticationInfo authenticationInfo; - - /** Indicates if the response should be automatically committed. */ - private volatile boolean autoCommitting; - - /** The authentication requests sent by an origin server to a client. */ - private volatile List challengeRequests; - - /** Indicates if the response has been committed. */ - private volatile boolean committed; - - /** The cookie settings provided by the server. */ - private volatile Series cookieSettings; - - /** The set of dimensions on which the response entity may vary. */ - private volatile Set dimensions; - - /** The reference used for redirections or creations. */ - private volatile Reference locationRef; - - /** The authentication requests sent by a proxy to a client. */ - private volatile List proxyChallengeRequests; - - /** The associated request. */ - private volatile Request request; - - /** - * Indicates how long the service is expected to be unavailable to the - * requesting client. - */ - private volatile Date retryAfter; - - /** The server-specific information. */ - private volatile ServerInfo serverInfo; - - /** The status. */ - private volatile Status status; - - /** - * Constructor. - * - * @param request The request associated to this response. - */ - public Response(Request request) { - this.age = 0; - this.accessControlAllowCredentials = null; - this.accessControlAllowHeaders = null; - this.accessControlAllowMethods = null; - this.accessControlAllowOrigin = null; - this.accessControlExposeHeaders = null; - this.allowedMethods = null; - this.autoCommitting = true; - this.challengeRequests = null; - this.cookieSettings = null; - this.committed = false; - this.dimensions = null; - this.locationRef = null; - this.proxyChallengeRequests = null; - this.request = request; - this.retryAfter = null; - this.serverInfo = null; - this.status = Status.SUCCESS_OK; - } - - /** - * Ask the connector to abort the related network connection, for example - * immediately closing the socket. - */ - public void abort() { - getRequest().abort(); - } - - /** - * Asks the server connector to immediately commit the given response, making it - * ready to be sent back to the client. Note that all server connectors don't - * necessarily support this feature.
- *
- * When the response is in autoCommit mode (see related property), then calling - * this method isn't necessary. Also, be aware that committing the response - * doesn't necessarily means that is will be immediately be written on the - * network as some buffering can occurs. If you want to ensure that response - * buffers are flushed,
- *
- * Note that this calls back {@link Request#commit(Response)} on the parent - * request which holds the link with the underlying network connection. - */ - public void commit() { - getRequest().commit(this); - } - - /** - * Asks the server connector to immediately flush the network buffers. Note that - * this calls back {@link Request#flushBuffers()} on the parent request which - * holds the link with the underlying network connection. - * - * @throws IOException - */ - @Override - public void flushBuffers() throws IOException { - getRequest().flushBuffers(); - } - - /** - * When used as part of a response to a preflight CORS request, this indicates - * whether or not the actual request can be made using credentials.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Credentials" header. - * - * @return True if the requested resource allows credential. - */ - public Boolean getAccessControlAllowCredentials() { - return this.accessControlAllowCredentials; - } - - /** - * Returns the modifiable set of headers allowed by the actual request on the - * current resource when used as part of a response to a preflight CORS - * request.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Headers" header. - * - * @return The set of headers allowed by the actual request on the current - * resource. - */ - public Set getAccessControlAllowHeaders() { - // Lazy initialization with double-check. - Set a = this.accessControlAllowHeaders; - if (a == null) { - synchronized (this) { - a = this.accessControlAllowHeaders; - if (a == null) { - this.accessControlAllowHeaders = a = new CopyOnWriteArraySet(); - } - } - } - return a; - } - - /** - * Returns the modifiable set of methods allowed by the actual request on the - * current resource when used as part of a response to a preflight CORS - * request
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Methods" header. - * - * @return The set of methods allowed by the actual request on the current - * resource. - */ - public Set getAccessControlAllowMethods() { - // Lazy initialization with double-check. - Set a = this.accessControlAllowMethods; - if (a == null) { - synchronized (this) { - a = this.accessControlAllowMethods; - if (a == null) { - this.accessControlAllowMethods = a = new CopyOnWriteArraySet(); - } - } - } - return a; - } - - /** - * When used in the context of CORS support, it returns the URI an origin server - * allows for the requested resource. Use "*" as a wildcard character.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Origin" header. - * - * @return The origin allowed by the requested resource. - */ - public String getAccessControlAllowOrigin() { - return this.accessControlAllowOrigin; - } - - /** - * Returns a modifiable whitelist of headers an origin server allows for the - * requested resource.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Expose-Headers" header. - * - * @return The set of headers an origin server allows for the requested - * resource. - */ - public Set getAccessControlExposeHeaders() { - // Lazy initialization with double-check. - Set a = this.accessControlExposeHeaders; - if (a == null) { - synchronized (this) { - a = this.accessControlExposeHeaders; - if (a == null) { - this.accessControlExposeHeaders = a = new CopyOnWriteArraySet(); - } - } - } - return a; - } - - /** - * Indicates how long the results of a preflight CORS request can be cached in a - * preflight result cache.
- * In case of a negative value, the results of a preflight request is not meant - * to be cached.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Max-Age" header. - * - * @return Indicates how long the results of a preflight request can be cached - * in a preflight result cache. - */ - public int getAccessControlMaxAge() { - return accessControlMaxAge; - } - - /** - * Returns the estimated amount of time since a response was generated or - * revalidated by the origin server. Origin servers should leave the 0 default - * value. Only caches are expected to set this property.
- *
- * Note that when used with HTTP connectors, this property maps to the "Age" - * header. - * - * @return The response age. - */ - public int getAge() { - return age; - } - - /** - * Returns the modifiable set of methods allowed on the requested resource. This - * property only has to be updated when a status CLIENT_ERROR_METHOD_NOT_ALLOWED - * is set. Creates a new instance if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the "Allow" - * header. - * - * @return The set of allowed methods. - */ - public Set getAllowedMethods() { - // Lazy initialization with double-check. - Set a = this.allowedMethods; - if (a == null) { - synchronized (this) { - a = this.allowedMethods; - if (a == null) { - this.allowedMethods = a = new CopyOnWriteArraySet(); - } - } - } - return a; - } - - /** - * Returns information sent by an origin server related to an successful - * authentication attempt. If none is available, null is returned.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Authentication-Info" header. - * - * @return The authentication information provided by the server. - */ - public AuthenticationInfo getAuthenticationInfo() { - return this.authenticationInfo; - } - - /** - * Returns the list of authentication requests sent by an origin server to a - * client. If none is available, an empty list is returned.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "WWW-Authenticate" header. - * - * @return The list of authentication requests. - */ - public List getChallengeRequests() { - // Lazy initialization with double-check. - List cr = this.challengeRequests; - if (cr == null) { - synchronized (this) { - cr = this.challengeRequests; - if (cr == null) { - this.challengeRequests = cr = new CopyOnWriteArrayList(); - } - } - } - return cr; - } - - /** - * Returns the modifiable series of cookie settings provided by the server. - * Creates a new instance if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Set-Cookie" and "Set-Cookie2" headers. - * - * @return The cookie settings provided by the server. - */ - public Series getCookieSettings() { - // Lazy initialization with double-check. - Series c = this.cookieSettings; - if (c == null) { - synchronized (this) { - c = this.cookieSettings; - if (c == null) { - this.cookieSettings = c = new Series(CookieSetting.class); - } - } - } - return c; - } - - /** - * Returns the modifiable set of selecting dimensions on which the response - * entity may vary. If some server-side content negotiation is done, this set - * should be properly updated, other it can be left empty. Creates a new - * instance if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the "Vary" - * header. - * - * @return The set of dimensions on which the response entity may vary. - */ - public Set getDimensions() { - if (this.dimensions == null) { - this.dimensions = new CopyOnWriteArraySet(); - } - return this.dimensions; - } - - /** - * Returns the location reference. This is the reference that the client should - * follow for redirections or resource creations.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Location" header. - * - * @return The redirection reference. - */ - public Reference getLocationRef() { - return this.locationRef; - } - - /** - * Returns the list of authentication requests sent by an origin server to a - * client. If none is available, an empty list is returned.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Proxy-Authenticate" header. - * - * @return The list of authentication requests. - */ - public List getProxyChallengeRequests() { - // Lazy initialization with double-check. - List cr = this.proxyChallengeRequests; - if (cr == null) { - synchronized (this) { - cr = this.proxyChallengeRequests; - if (cr == null) { - this.proxyChallengeRequests = cr = new CopyOnWriteArrayList(); - } - } - } - return cr; - } - - /** - * Returns the associated request - * - * @return The associated request - */ - public Request getRequest() { - return this.request; - } - - /** - * Indicates how long the service is expected to be unavailable to the - * requesting client. Default value is null.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Retry-After" header. - * - * @return Date after with a retry attempt could occur. - */ - public Date getRetryAfter() { - return retryAfter; - } - - /** - * Returns the server-specific information. Creates a new instance if no one has - * been set. - * - * @return The server-specific information. - */ - public ServerInfo getServerInfo() { - // Lazy initialization with double-check. - ServerInfo s = this.serverInfo; - if (s == null) { - synchronized (this) { - s = this.serverInfo; - if (s == null) { - this.serverInfo = s = new ServerInfo(); - } - } - } - return s; - } - - /** - * Returns the status. - * - * @return The status. - */ - public Status getStatus() { - return this.status; - } - - /** - * Indicates if the response should be automatically committed. When processing - * a request on the server-side, setting this property to 'false' let you ask to - * the server connector to wait before sending the response back to the client - * when the initial calling thread returns. This will let you do further updates - * to the response and manually calling {@link #commit()} later on, using - * another thread. - * - * @return True if the response should be automatically committed. - */ - public boolean isAutoCommitting() { - return autoCommitting; - } - - /** - * Indicates if the response has already been committed. - * - * @return True if the response has already been committed. - */ - public boolean isCommitted() { - return committed; - } - - @Override - public boolean isConfidential() { - return getRequest().isConfidential(); - } - - /** - * Indicates if the response is final or provisional. It relies on the - * {@link Status#isInformational()} method. - * - * @return True if the response is final. - */ - public boolean isFinal() { - return !getStatus().isInformational(); - } - - /** - * Indicates if the response is provisional or final. It relies on the - * {@link Status#isInformational()} method. - * - * @return True if the response is provisional. - */ - public boolean isProvisional() { - return getStatus().isInformational(); - } - - /** - * Permanently redirects the client to a target URI. The client is expected to - * reuse the same method for the new request. - * - * @param targetRef The target URI reference. - */ - public void redirectPermanent(Reference targetRef) { - setLocationRef(targetRef); - setStatus(Status.REDIRECTION_PERMANENT); - } - - /** - * Permanently redirects the client to a target URI. The client is expected to - * reuse the same method for the new request.
- *
- * If you pass a relative target URI, it will be resolved with the current base - * reference of the request's resource reference (see - * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}. - * - * @param targetUri The target URI. - */ - public void redirectPermanent(String targetUri) { - setLocationRef(targetUri); - setStatus(Status.REDIRECTION_PERMANENT); - } - - /** - * Redirects the client to a different URI that SHOULD be retrieved using a GET - * method on that resource. This method exists primarily to allow the output of - * a POST-activated script to redirect the user agent to a selected resource. - * The new URI is not a substitute reference for the originally requested - * resource. - * - * @param targetRef The target reference. - */ - public void redirectSeeOther(Reference targetRef) { - setLocationRef(targetRef); - setStatus(Status.REDIRECTION_SEE_OTHER); - } - - /** - * Redirects the client to a different URI that SHOULD be retrieved using a GET - * method on that resource. This method exists primarily to allow the output of - * a POST-activated script to redirect the user agent to a selected resource. - * The new URI is not a substitute reference for the originally requested - * resource.
- *
- * If you pass a relative target URI, it will be resolved with the current base - * reference of the request's resource reference (see - * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}. - * - * @param targetUri The target URI. - */ - public void redirectSeeOther(String targetUri) { - setLocationRef(targetUri); - setStatus(Status.REDIRECTION_SEE_OTHER); - } - - /** - * Temporarily redirects the client to a target URI. The client is expected to - * reuse the same method for the new request. - * - * @param targetRef The target reference. - */ - public void redirectTemporary(Reference targetRef) { - setLocationRef(targetRef); - setStatus(Status.REDIRECTION_TEMPORARY); - } - - /** - * Temporarily redirects the client to a target URI. The client is expected to - * reuse the same method for the new request.
- *
- * If you pass a relative target URI, it will be resolved with the current base - * reference of the request's resource reference (see - * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}. - * - * @param targetUri The target URI. - */ - public void redirectTemporary(String targetUri) { - setLocationRef(targetUri); - setStatus(Status.REDIRECTION_TEMPORARY); - } - - /** - * When used as part of a response to a preflight CORS request, indicates - * whether or not the actual request can be made using credentials.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Credentials" header. - * - * @param accessControlAllowCredentials True if the requested resource allows - * credential. - */ - public void setAccessControlAllowCredentials(Boolean accessControlAllowCredentials) { - this.accessControlAllowCredentials = accessControlAllowCredentials; - } - - /** - * When used as part of a response to a preflight CORS request, indicates how - * long (in seconds) the results of a preflight request can be cached in a - * preflight result cache.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Max-Age" header.
- * In case of negative value, the header is not set. - * - * @param accessControlMaxAge How long the results of a preflight request can be - * cached in a preflight result cache. - */ - public void setAccessControlMaxAge(int accessControlMaxAge) { - this.accessControlMaxAge = accessControlMaxAge; - } - - /** - * Sets the set of headers allowed by the actual request on the current resource - * when used as part of a response to a preflight CORS request.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Headers" header. - * - * @param accessControlAllowHeaders The set of headers allowed by the actual - * request on the current resource. - */ - public void setAccessControlAllowHeaders(Set accessControlAllowHeaders) { - synchronized (getAccessControlAllowHeaders()) { - if (accessControlAllowHeaders != this.accessControlAllowHeaders) { - this.accessControlAllowHeaders.clear(); - - if (accessControlAllowHeaders != null) { - this.accessControlAllowHeaders.addAll(accessControlAllowHeaders); - } - } - } - } - - /** - * Sets the set of methods allowed by the actual request on the current resource - * when used as part of a response to a preflight CORS request.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Methods" header. - * - * @param accessControlAllowMethods The set of methods allowed by the actual - * request on the current resource. - */ - public void setAccessControlAllowMethods(Set accessControlAllowMethods) { - synchronized (getAccessControlAllowMethods()) { - if (accessControlAllowMethods != this.accessControlAllowMethods) { - this.accessControlAllowMethods.clear(); - - if (accessControlAllowMethods != null) { - this.accessControlAllowMethods.addAll(accessControlAllowMethods); - } - } - } - } - - /** - * When used in the context of CORS support, it sets the URI an origin server - * allows for the requested resource. Use "*" as a wildcard character.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Origin" header. - * - * @param accessControlAllowOrigin The origin allowed by the requested resource. - */ - public void setAccessControlAllowOrigin(String accessControlAllowOrigin) { - // TODO Add some input validation here. - this.accessControlAllowOrigin = accessControlAllowOrigin; - } - - /** - * Sets the list of headers an origin server allows for the requested - * resource.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Expose-Headers" header. - * - * @param accessControlExposeHeaders The set of headers an origin server allows - * for the requested resource. - */ - public void setAccessControlExposeHeaders(Set accessControlExposeHeaders) { - synchronized (getAccessControlExposeHeaders()) { - if (accessControlExposeHeaders != this.accessControlExposeHeaders) { - this.accessControlExposeHeaders.clear(); - - if (accessControlExposeHeaders != null) { - this.accessControlExposeHeaders.addAll(accessControlExposeHeaders); - } - } - } - } - - /** - * Sets the estimated amount of time since a response was generated or - * revalidated by the origin server. Origin servers should leave the 0 default - * value. Only caches are expected to set this property.
- *
- * Note that when used with HTTP connectors, this property maps to the "Age" - * header. - * - * @param age The response age. - */ - public void setAge(int age) { - this.age = age; - } - - /** - * Sets the set of methods allowed on the requested resource. The set instance - * set must be thread-safe (use {@link CopyOnWriteArraySet} for example.
- *
- * Note that when used with HTTP connectors, this property maps to the "Allow" - * header. - * - * @param allowedMethods The set of methods allowed on the requested resource. - */ - public void setAllowedMethods(Set allowedMethods) { - synchronized (getAllowedMethods()) { - if (allowedMethods != this.allowedMethods) { - this.allowedMethods.clear(); - - if (allowedMethods != null) { - this.allowedMethods.addAll(allowedMethods); - } - } - } - } - - /** - * Sets the authentication information sent by an origin server to a client - * after a successful authentication attempt.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Authentication-Info" header. - * - * @param authenticationInfo The data returned by the server in response to - * successful authentication. - */ - public void setAuthenticationInfo(AuthenticationInfo authenticationInfo) { - this.authenticationInfo = authenticationInfo; - } - - /** - * Indicates if the response should be automatically committed. - * - * @param autoCommitting True if the response should be automatically committed - */ - public void setAutoCommitting(boolean autoCommitting) { - this.autoCommitting = autoCommitting; - } - - /** - * Sets the list of authentication requests sent by an origin server to a - * client. Note that when used with HTTP connectors, this property maps to the - * "WWW-Authenticate" header. This method clears the current list and adds all - * entries in the parameter list. - * - * @param challengeRequests A list of authentication requests sent by an origin - * server to a client. - */ - public void setChallengeRequests(List challengeRequests) { - synchronized (getChallengeRequests()) { - if (challengeRequests != getChallengeRequests()) { - getChallengeRequests().clear(); - - if (challengeRequests != null) { - getChallengeRequests().addAll(challengeRequests); - } - } - } - } - - /** - * Indicates if the response has already been committed. - * - * @param committed True if the response has already been committed. - */ - public void setCommitted(boolean committed) { - this.committed = committed; - } - - /** - * Sets the modifiable series of cookie settings provided by the server. Note - * that when used with HTTP connectors, this property maps to the "Set-Cookie" - * and "Set-Cookie2" headers. This method clears the current series and adds all - * entries in the parameter series. - * - * @param cookieSettings A series of cookie settings provided by the server. - */ - public void setCookieSettings(Series cookieSettings) { - synchronized (getCookieSettings()) { - if (cookieSettings != getCookieSettings()) { - getCookieSettings().clear(); - - if (cookieSettings != null) { - getCookieSettings().addAll(cookieSettings); - } - } - } - } - - /** - * Sets the set of dimensions on which the response entity may vary. Note that - * when used with HTTP connectors, this property maps to the "Vary" header. This - * method clears the current set and adds all entries in the parameter set. - * - * @param dimensions The set of dimensions on which the response entity may - * vary. - */ - public void setDimensions(Set dimensions) { - synchronized (getDimensions()) { - if (dimensions != getDimensions()) { - getDimensions().clear(); - - if (dimensions != null) { - getDimensions().addAll(dimensions); - } - } - } - } - - /** - * Sets the reference that the client should follow for redirections or resource - * creations. Note that when used with HTTP connectors, this property maps to - * the "Location" header. - * - * @param locationRef The reference to set. - */ - public void setLocationRef(Reference locationRef) { - this.locationRef = locationRef; - } - - /** - * Sets the reference that the client should follow for redirections or resource - * creations. If you pass a relative location URI, it will be resolved with the - * current base reference of the request's resource reference (see - * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Location" header. - * - * @param locationUri The URI to set. - * @see #setLocationRef(Reference) - */ - public void setLocationRef(String locationUri) { - Reference baseRef = null; - - if (getRequest().getResourceRef() != null) { - if (getRequest().getResourceRef().getBaseRef() != null) { - baseRef = getRequest().getResourceRef().getBaseRef(); - } else { - baseRef = getRequest().getResourceRef(); - } - } - - setLocationRef(new Reference(baseRef, locationUri).getTargetRef()); - } - - /** - * Sets the modifiable list of authentication requests sent by a proxy to a - * client. The list instance set must be thread-safe (use - * {@link CopyOnWriteArrayList} for example. Note that when used with HTTP - * connectors, this property maps to the "Proxy-Authenticate" header. This - * method clears the current list and adds all entries in the parameter list. - * - * @param proxyChallengeRequests A list of authentication requests sent by a - * proxy to a client. - */ - public void setProxyChallengeRequests(List proxyChallengeRequests) { - synchronized (getProxyChallengeRequests()) { - if (proxyChallengeRequests != getProxyChallengeRequests()) { - getProxyChallengeRequests().clear(); - - if (proxyChallengeRequests != null) { - getProxyChallengeRequests().addAll(proxyChallengeRequests); - } - } - } - } - - /** - * Sets the associated request. - * - * @param request The associated request - */ - public void setRequest(Request request) { - this.request = request; - } - - /** - * Indicates how long the service is expected to be unavailable to the - * requesting client. Default value is null.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Retry-After" header. - * - * @param retryAfter Date after with a retry attempt could occur. - */ - public void setRetryAfter(Date retryAfter) { - this.retryAfter = retryAfter; - } - - /** - * Sets the server-specific information. - * - * @param serverInfo The server-specific information. - */ - public void setServerInfo(ServerInfo serverInfo) { - this.serverInfo = serverInfo; - } - - /** - * Sets the status. - * - * @param status The status to set. - */ - public void setStatus(Status status) { - this.status = status; - } - - /** - * Sets the status. - * - * @param status The status to set (code and reason phrase). - * @param description The longer status description. - */ - public void setStatus(Status status, String description) { - setStatus(new Status(status, description)); - } - - /** - * Sets the status. - * - * @param status The status to set. - * @param throwable The related error or exception. - */ - public void setStatus(Status status, Throwable throwable) { - setStatus(new Status(status, throwable)); - } - - /** - * Sets the status. - * - * @param status The status to set. - * @param throwable The related error or exception. - * @param message The status message. - */ - public void setStatus(Status status, Throwable throwable, String message) { - setStatus(new Status(status, throwable, message)); - } - - /** - * Displays a synthesis of the response like an HTTP status line. - * - * @return A synthesis of the response like an HTTP status line. - */ - public String toString() { - return ((getRequest() == null) ? "?" : getRequest().getProtocol()) + " - " + getStatus(); - } + private static final ThreadLocal CURRENT = new ThreadLocal<>(); + + /** + * Returns the response associated with the current thread. + * + *

Warning: this method should only be used under duress. You should by default prefer + * obtaining the current context using methods such as {@link + * org.restlet.resource.Resource#getResponse()}. + * + *

This variable is stored internally as a thread local variable and updated each time a call + * is handled by a Restlet via the {@link Restlet#handle(org.restlet.Request, + * org.restlet.Response)} method. + * + * @return The current context. + */ + public static Response getCurrent() { + return CURRENT.get(); + } + + /** + * Sets the response associated with the current thread. + * + * @param response The thread's response. + */ + public static void setCurrent(Response response) { + CURRENT.set(response); + } + + /** + * When used as part of a response to a preflight CORS request, this indicates whether the + * actual request can be made using credentials. + */ + private volatile Boolean accessControlAllowCredentials; + + /** + * When used as part of a response to a preflight CORS request, this lists the headers allowed + * by the actual request on the current resource. + */ + private volatile Set accessControlAllowHeaders; + + /** + * When used as part of a response to a preflight CORS request, this lists the methods allowed + * by the actual request on the current resource. + */ + private volatile Set accessControlAllowMethods; + + /** + * When used in the context of CORS support, it specifies the URI an origin server allows for + * the requested resource. Use "*" as a wildcard character. + */ + private volatile String accessControlAllowOrigin; + + /** The whitelist of headers an origin server allows for the requested resource. */ + private volatile Set accessControlExposeHeaders; + + /** + * When used in the context of CORS support, it indicates how long the results of a preflight + * request can be cached in a preflight result cache. + */ + private volatile int accessControlMaxAge; + + /** + * Estimated amount of time since a response was generated or revalidated by the origin server. + */ + private volatile int age; + + /** The set of methods allowed on the requested resource. */ + private volatile Set allowedMethods; + + /** + * The authentication information sent by an origin server to a client in the case of a + * successful authentication attempt. + */ + private volatile AuthenticationInfo authenticationInfo; + + /** Indicates if the response should be automatically committed. */ + private volatile boolean autoCommitting; + + /** The authentication requests sent by an origin server to a client. */ + private volatile List challengeRequests; + + /** Indicates if the response has been committed. */ + private volatile boolean committed; + + /** The cookie settings provided by the server. */ + private volatile Series cookieSettings; + + /** The set of dimensions on which the response entity may vary. */ + private volatile Set dimensions; + + /** The reference used for redirections or creations. */ + private volatile Reference locationRef; + + /** The authentication requests sent by a proxy to a client. */ + private volatile List proxyChallengeRequests; + + /** The associated request. */ + private volatile Request request; + + /** Indicates how long the service is expected to be unavailable to the requesting client. */ + private volatile Date retryAfter; + + /** The server-specific information. */ + private volatile ServerInfo serverInfo; + + /** The status. */ + private volatile Status status; + + /** + * Constructor. + * + * @param request The request associated with this response. + */ + public Response(Request request) { + this.age = 0; + this.accessControlAllowCredentials = null; + this.accessControlAllowHeaders = null; + this.accessControlAllowMethods = null; + this.accessControlAllowOrigin = null; + this.accessControlExposeHeaders = null; + this.allowedMethods = null; + this.autoCommitting = true; + this.challengeRequests = null; + this.cookieSettings = null; + this.committed = false; + this.dimensions = null; + this.locationRef = null; + this.proxyChallengeRequests = null; + this.request = request; + this.retryAfter = null; + this.serverInfo = null; + this.status = Status.SUCCESS_OK; + } + + /** + * Ask the connector to abort the related network connection, for example immediately closing + * the socket. + */ + public void abort() { + getRequest().abort(); + } + + /** + * Asks the server connector to immediately commit the given response, making it ready to be + * sent back to the client. Note that all server connectors don't necessarily support this + * feature.
+ *
+ * When the response is in autoCommit mode (see related property), then calling this method + * isn't necessary. Also, be aware that committing the response doesn't necessarily mean that it + * will immediately be written on the network as some buffering can occur. If you want to ensure + * that response buffers are flushed.
+ *
+ * Note that this calls back {@link Request#commit(Response)} on the parent request which holds + * the link with the underlying network connection. + */ + public void commit() { + getRequest().commit(this); + } + + /** + * Asks the server connector to immediately flush the network buffers. Note that this calls back + * {@link Request#flushBuffers()} on the parent request which holds the link with the underlying + * network connection. + * + * @throws IOException + */ + @Override + public void flushBuffers() throws IOException { + getRequest().flushBuffers(); + } + + /** + * When used as part of a response to a preflight CORS request, this indicates whether the + * actual request can be made using credentials.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Credentials" header. + * + * @return True if the requested resource allows credential. + */ + public Boolean getAccessControlAllowCredentials() { + return this.accessControlAllowCredentials; + } + + /** + * Returns the modifiable set of headers allowed by the actual request on the current resource + * when used as part of a response to a preflight CORS request.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Headers" header. + * + * @return The set of headers allowed by the actual request on the current resource. + */ + public Set getAccessControlAllowHeaders() { + // Lazy initialization with double-check. + Set a = this.accessControlAllowHeaders; + if (a == null) { + synchronized (this) { + a = this.accessControlAllowHeaders; + if (a == null) { + this.accessControlAllowHeaders = a = new CopyOnWriteArraySet(); + } + } + } + return a; + } + + /** + * Returns the modifiable set of methods allowed by the actual request on the current resource + * when used as part of a response to a preflight CORS request
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Methods" header. + * + * @return The set of methods allowed by the actual request on the current resource. + */ + public Set getAccessControlAllowMethods() { + // Lazy initialization with double-check. + Set a = this.accessControlAllowMethods; + if (a == null) { + synchronized (this) { + a = this.accessControlAllowMethods; + if (a == null) { + this.accessControlAllowMethods = a = new CopyOnWriteArraySet(); + } + } + } + return a; + } + + /** + * When used in the context of CORS support, it returns the URI an origin server allows for the + * requested resource. Use "*" as a wildcard character.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Origin" header. + * + * @return The origin allowed by the requested resource. + */ + public String getAccessControlAllowOrigin() { + return this.accessControlAllowOrigin; + } + + /** + * Returns a modifiable whitelist of headers an origin server allows for the requested resource. + *
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Expose-Headers" header. + * + * @return The set of headers an origin server allows for the requested resource. + */ + public Set getAccessControlExposeHeaders() { + // Lazy initialization with double-check. + Set a = this.accessControlExposeHeaders; + if (a == null) { + synchronized (this) { + a = this.accessControlExposeHeaders; + if (a == null) { + this.accessControlExposeHeaders = a = new CopyOnWriteArraySet(); + } + } + } + return a; + } + + /** + * Indicates how long the results of a preflight CORS request can be cached in a preflight + * result cache.
+ * In case of a negative value, the results of a preflight request are not meant to be cached. + *
+ * Note that when used with HTTP connectors, this property maps to the "Access-Control-Max-Age" + * header. + * + * @return Indicates how long the results of a preflight request can be cached in a preflight + * result cache. + */ + public int getAccessControlMaxAge() { + return accessControlMaxAge; + } + + /** + * Returns the estimated amount of time since a response was generated or revalidated by the + * origin server. Origin servers should leave the 0 default value. Only caches are expected to + * set this property.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Age" header. + * + * @return The response age. + */ + public int getAge() { + return age; + } + + /** + * Returns the modifiable set of methods allowed on the requested resource. This property only + * has to be updated when a status CLIENT_ERROR_METHOD_NOT_ALLOWED is set. Creates a new + * instance if no one has been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Allow" header. + * + * @return The set of allowed methods. + */ + public Set getAllowedMethods() { + // Lazy initialization with double-check. + Set a = this.allowedMethods; + if (a == null) { + synchronized (this) { + a = this.allowedMethods; + if (a == null) { + this.allowedMethods = a = new CopyOnWriteArraySet(); + } + } + } + return a; + } + + /** + * Returns information sent by an origin server related to a successful authentication attempt. + * If none is available, null is returned.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Authentication-Info" + * header. + * + * @return The authentication information provided by the server. + */ + public AuthenticationInfo getAuthenticationInfo() { + return this.authenticationInfo; + } + + /** + * Returns the list of authentication requests sent by an origin server to a client. If none is + * available, an empty list is returned.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "WWW-Authenticate" + * header. + * + * @return The list of authentication requests. + */ + public List getChallengeRequests() { + // Lazy initialization with double-check. + List cr = this.challengeRequests; + if (cr == null) { + synchronized (this) { + cr = this.challengeRequests; + if (cr == null) { + this.challengeRequests = cr = new CopyOnWriteArrayList(); + } + } + } + return cr; + } + + /** + * Returns the modifiable series of cookie settings provided by the server. Creates a new + * instance if no one has been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Set-Cookie" and + * "Set-Cookie2" headers. + * + * @return The cookie settings provided by the server. + */ + public Series getCookieSettings() { + // Lazy initialization with double-check. + Series c = this.cookieSettings; + if (c == null) { + synchronized (this) { + c = this.cookieSettings; + if (c == null) { + this.cookieSettings = c = new Series<>(CookieSetting.class); + } + } + } + return c; + } + + /** + * Returns the modifiable set of selecting dimensions on which the response entity may vary. If + * some server-side content negotiation is done, this set should be properly updated, other it + * can be left empty. Creates a new instance if no one has been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Vary" header. + * + * @return The set of dimensions on which the response entity may vary. + */ + public Set getDimensions() { + if (this.dimensions == null) { + this.dimensions = new CopyOnWriteArraySet(); + } + return this.dimensions; + } + + /** + * Returns the location reference. This is the reference that the client should follow for + * redirections or resource creations.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Location" header. + * + * @return The redirection reference. + */ + public Reference getLocationRef() { + return this.locationRef; + } + + /** + * Returns the list of authentication requests sent by an origin server to a client. If none is + * available, an empty list is returned.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Proxy-Authenticate" + * header. + * + * @return The list of authentication requests. + */ + public List getProxyChallengeRequests() { + // Lazy initialization with double-check. + List cr = this.proxyChallengeRequests; + if (cr == null) { + synchronized (this) { + cr = this.proxyChallengeRequests; + if (cr == null) { + this.proxyChallengeRequests = cr = new CopyOnWriteArrayList(); + } + } + } + return cr; + } + + /** + * Returns the associated request + * + * @return The associated request + */ + public Request getRequest() { + return this.request; + } + + /** + * Indicates how long the service is expected to be unavailable to the requesting client. The + * default value is null.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Retry-After" header. + * + * @return Date after with a retry attempt could occur. + */ + public Date getRetryAfter() { + return retryAfter; + } + + /** + * Returns the server-specific information. Creates a new instance if no one has been set. + * + * @return The server-specific information. + */ + public ServerInfo getServerInfo() { + // Lazy initialization with double-check. + ServerInfo s = this.serverInfo; + if (s == null) { + synchronized (this) { + s = this.serverInfo; + if (s == null) { + this.serverInfo = s = new ServerInfo(); + } + } + } + return s; + } + + /** + * Returns the status. + * + * @return The status. + */ + public Status getStatus() { + return this.status; + } + + /** + * Indicates if the response should be automatically committed. When processing a request on the + * server-side, setting this property to 'false' let you ask to the server connector to wait + * before sending the response back to the client when the initial calling thread returns. This + * will let you do further updates to the response and manually calling {@link #commit()} later + * on, using another thread. + * + * @return True if the response should be automatically committed. + */ + public boolean isAutoCommitting() { + return autoCommitting; + } + + /** + * Indicates if the response has already been committed. + * + * @return True if the response has already been committed. + */ + public boolean isCommitted() { + return committed; + } + + @Override + public boolean isConfidential() { + return getRequest().isConfidential(); + } + + /** + * Indicates if the response is final or provisional. It relies on the {@link + * Status#isInformational()} method. + * + * @return True if the response is final. + */ + public boolean isFinal() { + return !getStatus().isInformational(); + } + + /** + * Indicates if the response is provisional or final. It relies on the {@link + * Status#isInformational()} method. + * + * @return True if the response is provisional. + */ + public boolean isProvisional() { + return getStatus().isInformational(); + } + + /** + * Permanently redirects the client to a target URI. The client is expected to reuse the same + * method for the new request. + * + * @param targetRef The target URI reference. + */ + public void redirectPermanent(Reference targetRef) { + setLocationRef(targetRef); + setStatus(Status.REDIRECTION_PERMANENT); + } + + /** + * Permanently redirects the client to a target URI. The client is expected to reuse the same + * method for the new request.
+ *
+ * If you pass a relative target URI, it will be resolved with the current base reference of the + * request's resource reference (see {@link Request#getResourceRef()} and {@link + * Reference#getBaseRef()}. + * + * @param targetUri The target URI. + */ + public void redirectPermanent(String targetUri) { + setLocationRef(targetUri); + setStatus(Status.REDIRECTION_PERMANENT); + } + + /** + * Redirects the client to a different URI that SHOULD be retrieved using a GET method on that + * resource. This method exists primarily to allow the output of a POST-activated script to + * redirect the user agent to a selected resource. The new URI is not a substitute reference for + * the originally requested resource. + * + * @param targetRef The target reference. + */ + public void redirectSeeOther(Reference targetRef) { + setLocationRef(targetRef); + setStatus(Status.REDIRECTION_SEE_OTHER); + } + + /** + * Redirects the client to a different URI that SHOULD be retrieved using a GET method on that + * resource. This method exists primarily to allow the output of a POST-activated script to + * redirect the user agent to a selected resource. The new URI is not a substitute reference for + * the originally requested resource.
+ *
+ * If you pass a relative target URI, it will be resolved with the current base reference of the + * request's resource reference (see {@link Request#getResourceRef()} and {@link + * Reference#getBaseRef()}. + * + * @param targetUri The target URI. + */ + public void redirectSeeOther(String targetUri) { + setLocationRef(targetUri); + setStatus(Status.REDIRECTION_SEE_OTHER); + } + + /** + * Temporarily redirects the client to a target URI. The client is expected to reuse the same + * method for the new request. + * + * @param targetRef The target reference. + */ + public void redirectTemporary(Reference targetRef) { + setLocationRef(targetRef); + setStatus(Status.REDIRECTION_TEMPORARY); + } + + /** + * Temporarily redirects the client to a target URI. The client is expected to reuse the same + * method for the new request.
+ *
+ * If you pass a relative target URI, it will be resolved with the current base reference of the + * request's resource reference (see {@link Request#getResourceRef()} and {@link + * Reference#getBaseRef()}. + * + * @param targetUri The target URI. + */ + public void redirectTemporary(String targetUri) { + setLocationRef(targetUri); + setStatus(Status.REDIRECTION_TEMPORARY); + } + + /** + * When used as part of a response to a preflight CORS request, indicates whether or not the + * actual request can be made using credentials.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Credentials" header. + * + * @param accessControlAllowCredentials True if the requested resource allows credential. + */ + public void setAccessControlAllowCredentials(Boolean accessControlAllowCredentials) { + this.accessControlAllowCredentials = accessControlAllowCredentials; + } + + /** + * When used as part of a response to a preflight CORS request, indicates how long (in seconds) + * the results of a preflight request can be cached in a preflight result cache.
+ * Note that when used with HTTP connectors, this property maps to the "Access-Control-Max-Age" + * header.
+ * In case of negative value, the header is not set. + * + * @param accessControlMaxAge How long the results of a preflight request can be cached in a + * preflight result cache. + */ + public void setAccessControlMaxAge(int accessControlMaxAge) { + this.accessControlMaxAge = accessControlMaxAge; + } + + /** + * Sets the set of headers allowed by the actual request on the current resource when used as + * part of a response to a preflight CORS request.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Headers" header. + * + * @param accessControlAllowHeaders The set of headers allowed by the actual request on the + * current resource. + */ + public void setAccessControlAllowHeaders(Set accessControlAllowHeaders) { + synchronized (getAccessControlAllowHeaders()) { + if (accessControlAllowHeaders != this.accessControlAllowHeaders) { + this.accessControlAllowHeaders.clear(); + + if (accessControlAllowHeaders != null) { + this.accessControlAllowHeaders.addAll(accessControlAllowHeaders); + } + } + } + } + + /** + * Sets the set of methods allowed by the actual request on the current resource when used as + * part of a response to a preflight CORS request.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Methods" header. + * + * @param accessControlAllowMethods The set of methods allowed by the actual request on the + * current resource. + */ + public void setAccessControlAllowMethods(Set accessControlAllowMethods) { + synchronized (getAccessControlAllowMethods()) { + if (accessControlAllowMethods != this.accessControlAllowMethods) { + this.accessControlAllowMethods.clear(); + + if (accessControlAllowMethods != null) { + this.accessControlAllowMethods.addAll(accessControlAllowMethods); + } + } + } + } + + /** + * When used in the context of CORS support, it sets the URI an origin server allows for the + * requested resource. Use "*" as a wildcard character.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Origin" header. + * + * @param accessControlAllowOrigin The origin allowed by the requested resource. + */ + public void setAccessControlAllowOrigin(String accessControlAllowOrigin) { + // TODO Add some input validation here. + this.accessControlAllowOrigin = accessControlAllowOrigin; + } + + /** + * Sets the list of headers an origin server allows for the requested resource.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Expose-Headers" header. + * + * @param accessControlExposeHeaders The set of headers an origin server allows for the + * requested resource. + */ + public void setAccessControlExposeHeaders(Set accessControlExposeHeaders) { + synchronized (getAccessControlExposeHeaders()) { + if (accessControlExposeHeaders != this.accessControlExposeHeaders) { + this.accessControlExposeHeaders.clear(); + + if (accessControlExposeHeaders != null) { + this.accessControlExposeHeaders.addAll(accessControlExposeHeaders); + } + } + } + } + + /** + * Sets the estimated amount of time since a response was generated or revalidated by the origin + * server. Origin servers should leave the 0 default value. Only caches are expected to set this + * property.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Age" header. + * + * @param age The response age. + */ + public void setAge(int age) { + this.age = age; + } + + /** + * Sets the set of methods allowed on the requested resource. The set instance set must be + * thread-safe (use {@link CopyOnWriteArraySet} for example.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Allow" header. + * + * @param allowedMethods The set of methods allowed on the requested resource. + */ + public void setAllowedMethods(Set allowedMethods) { + synchronized (getAllowedMethods()) { + if (allowedMethods != this.allowedMethods) { + this.allowedMethods.clear(); + + if (allowedMethods != null) { + this.allowedMethods.addAll(allowedMethods); + } + } + } + } + + /** + * Sets the authentication information sent by an origin server to a client after a successful + * authentication attempt.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Authentication-Info" + * header. + * + * @param authenticationInfo The data returned by the server in response to successful + * authentication. + */ + public void setAuthenticationInfo(AuthenticationInfo authenticationInfo) { + this.authenticationInfo = authenticationInfo; + } + + /** + * Indicates if the response should be automatically committed. + * + * @param autoCommitting True if the response should be automatically committed + */ + public void setAutoCommitting(boolean autoCommitting) { + this.autoCommitting = autoCommitting; + } + + /** + * Sets the list of authentication requests sent by an origin server to a client. Note that when + * used with HTTP connectors, this property maps to the "WWW-Authenticate" header. This method + * clears the current list and adds all entries in the parameter list. + * + * @param challengeRequests A list of authentication requests sent by an origin server to a + * client. + */ + public void setChallengeRequests(List challengeRequests) { + synchronized (getChallengeRequests()) { + if (challengeRequests != getChallengeRequests()) { + getChallengeRequests().clear(); + + if (challengeRequests != null) { + getChallengeRequests().addAll(challengeRequests); + } + } + } + } + + /** + * Indicates if the response has already been committed. + * + * @param committed True if the response has already been committed. + */ + public void setCommitted(boolean committed) { + this.committed = committed; + } + + /** + * Sets the modifiable series of cookie settings provided by the server. Note that when used + * with HTTP connectors, this property maps to the "Set-Cookie" and "Set-Cookie2" headers. This + * method clears the current series and adds all entries in the parameter series. + * + * @param cookieSettings A series of cookie settings provided by the server. + */ + public void setCookieSettings(Series cookieSettings) { + synchronized (getCookieSettings()) { + if (cookieSettings != getCookieSettings()) { + getCookieSettings().clear(); + + if (cookieSettings != null) { + getCookieSettings().addAll(cookieSettings); + } + } + } + } + + /** + * Sets the set of dimensions on which the response entity may vary. Note that when used with + * HTTP connectors, this property maps to the "Vary" header. This method clears the current set + * and adds all entries in the parameter set. + * + * @param dimensions The set of dimensions on which the response entity may vary. + */ + public void setDimensions(Set dimensions) { + synchronized (getDimensions()) { + if (dimensions != getDimensions()) { + getDimensions().clear(); + + if (dimensions != null) { + getDimensions().addAll(dimensions); + } + } + } + } + + /** + * Sets the reference that the client should follow for redirections or resource creations. Note + * that when used with HTTP connectors, this property maps to the "Location" header. + * + * @param locationRef The reference to set. + */ + public void setLocationRef(Reference locationRef) { + this.locationRef = locationRef; + } + + /** + * Sets the reference that the client should follow for redirections or resource creations. If + * you pass a relative location URI, it will be resolved with the current base reference of the + * request's resource reference (see {@link Request#getResourceRef()} and {@link + * Reference#getBaseRef()}.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Location" header. + * + * @param locationUri The URI to set. + * @see #setLocationRef(Reference) + */ + public void setLocationRef(String locationUri) { + Reference baseRef = null; + + if (getRequest().getResourceRef() != null) { + if (getRequest().getResourceRef().getBaseRef() != null) { + baseRef = getRequest().getResourceRef().getBaseRef(); + } else { + baseRef = getRequest().getResourceRef(); + } + } + + setLocationRef(new Reference(baseRef, locationUri).getTargetRef()); + } + + /** + * Sets the modifiable list of authentication requests sent by a proxy to a client. The list + * instance set must be thread-safe (use {@link CopyOnWriteArrayList} for example. Note that + * when used with HTTP connectors, this property maps to the "Proxy-Authenticate" header. This + * method clears the current list and adds all entries in the parameter list. + * + * @param proxyChallengeRequests A list of authentication requests sent by a proxy to a client. + */ + public void setProxyChallengeRequests(List proxyChallengeRequests) { + synchronized (getProxyChallengeRequests()) { + if (proxyChallengeRequests != getProxyChallengeRequests()) { + getProxyChallengeRequests().clear(); + + if (proxyChallengeRequests != null) { + getProxyChallengeRequests().addAll(proxyChallengeRequests); + } + } + } + } + + /** + * Sets the associated request. + * + * @param request The associated request + */ + public void setRequest(Request request) { + this.request = request; + } + + /** + * Indicates how long the service is expected to be unavailable to the requesting client. + * Default value is null.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Retry-After" header. + * + * @param retryAfter Date after with a retry attempt could occur. + */ + public void setRetryAfter(Date retryAfter) { + this.retryAfter = retryAfter; + } + + /** + * Sets the server-specific information. + * + * @param serverInfo The server-specific information. + */ + public void setServerInfo(ServerInfo serverInfo) { + this.serverInfo = serverInfo; + } + + /** + * Sets the status. + * + * @param status The status to set. + */ + public void setStatus(Status status) { + this.status = status; + } + + /** + * Sets the status. + * + * @param status The status to set (code and reason phrase). + * @param description The longer status description. + */ + public void setStatus(Status status, String description) { + setStatus(new Status(status, description)); + } + + /** + * Sets the status. + * + * @param status The status to set. + * @param throwable The related error or exception. + */ + public void setStatus(Status status, Throwable throwable) { + setStatus(new Status(status, throwable)); + } + + /** + * Sets the status. + * + * @param status The status to set. + * @param throwable The related error or exception. + * @param message The status message. + */ + public void setStatus(Status status, Throwable throwable, String message) { + setStatus(new Status(status, throwable, message)); + } + + /** + * Displays a synthesis of the response like an HTTP status line. + * + * @return A synthesis of the response like an HTTP status line. + */ + public String toString() { + return ((getRequest() == null) ? "?" : getRequest().getProtocol()) + " - " + getStatus(); + } } diff --git a/org.restlet/src/main/java/org/restlet/Restlet.java b/org.restlet/src/main/java/org/restlet/Restlet.java index 038dfedfc5..086f557eb8 100644 --- a/org.restlet/src/main/java/org/restlet/Restlet.java +++ b/org.restlet/src/main/java/org/restlet/Restlet.java @@ -1,427 +1,415 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; -import org.restlet.data.Status; -import org.restlet.engine.Engine; - import java.util.logging.Level; import java.util.logging.Logger; +import org.restlet.data.Status; +import org.restlet.engine.Engine; /** - * Uniform class that provides a context and life cycle support. It has many - * subclasses that focus on specific ways to process calls. The context property - * is typically provided by a parent Component as a way to encapsulate access to - * shared features such as logging and client connectors.
+ * Uniform class that provides a context and life cycle support. It has many subclasses that focus + * on specific ways to process calls. The context property is typically provided by a parent + * Component as a way to encapsulate access to shared features such as logging and client + * connectors.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public abstract class Restlet implements Uniform { - /** Error message. */ - private static final String UNABLE_TO_START = "Unable to start the Restlet"; - - /** - * Indicates that a Restlet's context has changed. - * - * @param restlet The Restlet with a changed context. - * @param context The new context. - */ - private static void fireContextChanged(Restlet restlet, Context context) { - if (context != null) { - if (context instanceof org.restlet.engine.util.ChildContext childContext) { - - if (childContext.getChild() == null) { - childContext.setChild(restlet); - } - } else if (!(restlet instanceof Component) - && (context instanceof org.restlet.engine.component.ComponentContext)) { - context.getLogger().severe( - "For security reasons, don't pass the component context to child Restlets anymore. Use the Context#createChildContext() method instead. " - + restlet.getClass()); - } - } - } - - /** The author(s). */ - private volatile String author; - - /** The context. */ - private volatile Context context; - - /** The description. */ - private volatile String description; - - /** Finder class to instantiate. */ - private volatile Class finderClass; - - /** The display name. */ - private volatile String name; - - /** The owner(s). */ - private volatile String owner; - - /** Indicates if the Restlet was started. */ - private volatile boolean started; - - /** - * Constructor with null context. - */ - public Restlet() { - this(null); - } - - /** - * Constructor with the Restlet's context which can be the parent's application - * context, but shouldn't be the parent Component's context for security - * reasons. - * - * @see Context#createChildContext() - * - * @param context The context of the Restlet. - * - */ - public Restlet(Context context) { - this.context = context; - this.started = false; - this.name = toString(); - this.description = null; - this.author = null; - this.owner = null; - - this.finderClass = null; - if (Engine.getInstance() == null) { - Context.getCurrentLogger().severe("Unable to fully initialize the Restlet. No Restlet engine available."); - throw new RuntimeException("Unable to fully initialize the Restlet. No Restlet engine available."); - } - - fireContextChanged(this, context); - } - - /** - * Creates a new finder instance based on the "targetClass" property. If none is - * define, the {@link Application#createFinder(Class)} method is invoked if - * available, otherwise the - * {@link org.restlet.resource.Finder#createFinder(Class, Class, Context, Logger)} - * method is called with the {@link org.restlet.resource.Finder} class as - * parameter. - * - * @param resourceClass The target {@link org.restlet.resource.ServerResource} - * class to find. - * @return The new finder instance. - * @see org.restlet.resource.Finder#createFinder(Class, Class, Context, Logger) - */ - public org.restlet.resource.Finder createFinder( - Class resourceClass) { - org.restlet.resource.Finder result = null; - - if (getFinderClass() != null) { - result = org.restlet.resource.Finder.createFinder(resourceClass, getFinderClass(), getContext(), - getLogger()); - } else if ((getApplication() != null) && (getApplication() != this)) { - result = getApplication().createFinder(resourceClass); - } else { - result = org.restlet.resource.Finder.createFinder(resourceClass, org.restlet.resource.Finder.class, - getContext(), getLogger()); - } - - return result; - } - - /** - * Attempts to {@link #stop()} the Restlet if it is still started. - */ - @Override - protected void finalize() throws Throwable { - if (isStarted()) { - stop(); - } - super.finalize(); - } - - /** - * Returns the parent application if it exists, or null. - * - * @return The parent application if it exists, or null. - */ - public Application getApplication() { - return Application.getCurrent(); - } - - /** - * Returns the author(s). - * - * @return The author(s). - */ - public String getAuthor() { - return this.author; - } - - /** - * Returns the context. - * - * @return The context. - */ - public Context getContext() { - return this.context; - } - - /** - * Returns the description. - * - * @return The description - */ - public String getDescription() { - return this.description; - } - - /** - * Returns the finder class used to instantiate resource classes. By default, it - * returns the {@link org.restlet.resource.Finder} class. This property is - * leveraged by {@link Application#setOutboundRoot(Class)} and - * {@link Application#setInboundRoot(Class)} methods. - * - * @return the finder class to instantiate. - */ - public Class getFinderClass() { - return finderClass; - } - - /** - * Returns the context's logger. - * - * @return The context's logger. - */ - public Logger getLogger() { - Logger result = null; - Context context = getContext(); - - if (context == null) { - context = Context.getCurrent(); - } - - if (context != null) { - result = context.getLogger(); - } - - if (result == null) { - result = Engine.getLogger(this, "org.restlet"); - } - - return result; - } - - /** - * Returns the display name. - * - * @return The display name. - */ - public String getName() { - return this.name; - } - - /** - * Returns the owner(s). - * - * @return The owner(s). - */ - public String getOwner() { - return this.owner; - } - - /** - * Handles a call. Creates an empty {@link Response} object and then invokes - * {@link #handle(Request, Response)}. - * - * @param request The request to handle. - * @return The returned response. - */ - public final Response handle(Request request) { - Response response = new Response(request); - handle(request, response); - return response; - } - - /** - * Handles a call. The default behavior is to initialize the Restlet by setting - * the current context using the {@link Context#setCurrent(Context)} method and - * by attempting to start it, unless it was already started. If an exception is - * thrown during the start action, then the response status is set to - * {@link Status#SERVER_ERROR_INTERNAL}. - *

- * Subclasses overriding this method should make sure that they call - * super.handle(request, response) before adding their own logic. - * - * @param request The request to handle. - * @param response The response to update. - */ - public void handle(Request request, Response response) { - // Associate the response to the current thread - Response.setCurrent(response); - - // Associate the context to the current thread - if (getContext() != null) { - Context.setCurrent(getContext()); - } - - // Check if the Restlet was started - if (isStopped()) { - try { - start(); - } catch (Exception e) { - // Occurred while starting the Restlet - if (getContext() != null) { - getContext().getLogger().log(Level.WARNING, UNABLE_TO_START, e); - } else { - Context.getCurrentLogger().log(Level.WARNING, UNABLE_TO_START, e); - } - - response.setStatus(Status.SERVER_ERROR_INTERNAL); - } - - if (!isStarted()) { - // No exception raised but the Restlet somehow couldn't be - // started - getContext().getLogger().log(Level.WARNING, UNABLE_TO_START); - response.setStatus(Status.SERVER_ERROR_INTERNAL); - } - } - } - - /** - * Handles a call. - * - * @param request The request to handle. - * @param response The response to update. - * @param onResponseCallback The callback invoked upon response reception. - */ - public final void handle(Request request, Response response, Uniform onResponseCallback) { - request.setOnResponse(onResponseCallback); - handle(request, response); - } - - /** - * Handles a call. - * - * @param request The request to handle. - * @param onReceivedCallback The callback invoked upon request reception. - */ - public final void handle(Request request, Uniform onReceivedCallback) { - Response response = new Response(request); - handle(request, response, onReceivedCallback); - } - - /** - * Indicates if the Restlet is started. - * - * @return True if the Restlet is started. - */ - public boolean isStarted() { - return this.started; - } - - /** - * Indicates if the Restlet is stopped. - * - * @return True if the Restlet is stopped. - */ - public boolean isStopped() { - return !this.started; - } - - /** - * Sets the author(s). - * - * @param author The author(s). - */ - public void setAuthor(String author) { - this.author = author; - } - - /** - * Sets the context. - * - * @param context The context. - */ - public void setContext(Context context) { - this.context = context; - fireContextChanged(this, context); - } - - /** - * Sets the description. - * - * @param description The description. - */ - public void setDescription(String description) { - this.description = description; - } - - /** - * Sets the finder class to instantiate. This property is leveraged by - * {@link Application#setOutboundRoot(Class)} and - * {@link Application#setInboundRoot(Class)} methods. - * - * @param finderClass The finder class to instantiate. - */ - public void setFinderClass(Class finderClass) { - this.finderClass = finderClass; - } - - /** - * Sets the display name. - * - * @param name The display name. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Sets the owner(s). - * - * @param owner The owner(s). - */ - public void setOwner(String owner) { - this.owner = owner; - } - - /** - * Starts the Restlet. By default its only sets "started" internal property to - * true. - * - * WARNING: this method must be called at the end of the starting process by - * subclasses otherwise concurrent threads could enter into the call handling - * logic too early. - * - * @throws Exception - */ - public synchronized void start() throws Exception { - this.started = true; - } - - /** - * Stops the Restlet. By default its only sets "started" internal property to - * false. - * - * WARNING: this method must be called at the beginning of the stopping process - * by subclasses otherwise concurrent threads could continue to (improperly) - * handle calls. - * - * @throws Exception - */ - public synchronized void stop() throws Exception { - this.started = false; - } - + /** Error message. */ + private static final String UNABLE_TO_START = "Unable to start the Restlet"; + + /** + * Indicates that a Restlet's context has changed. + * + * @param restlet The Restlet with a changed context. + * @param context The new context. + */ + private static void fireContextChanged(Restlet restlet, Context context) { + if (context != null) { + if (context instanceof org.restlet.engine.util.ChildContext childContext) { + + if (childContext.getChild() == null) { + childContext.setChild(restlet); + } + } else if (!(restlet instanceof Component) + && (context instanceof org.restlet.engine.component.ComponentContext)) { + context.getLogger() + .severe( + "For security reasons, don't pass the component context to child Restlets anymore. Use the Context#createChildContext() method instead. " + + restlet.getClass()); + } + } + } + + /** The author(s). */ + private volatile String author; + + /** The context. */ + private volatile Context context; + + /** The description. */ + private volatile String description; + + /** Finder class to instantiate. */ + private volatile Class finderClass; + + /** The display name. */ + private volatile String name; + + /** The owner(s). */ + private volatile String owner; + + /** Indicates if the Restlet was started. */ + private volatile boolean started; + + /** Constructor with null context. */ + protected Restlet() { + this(null); + } + + /** + * Constructor with the Restlet's context which can be the parent's application context, but + * shouldn't be the parent Component's context for security reasons. + * + * @see Context#createChildContext() + * @param context The context of the Restlet. + */ + protected Restlet(Context context) { + this.context = context; + this.started = false; + this.name = toString(); + this.description = null; + this.author = null; + this.owner = null; + + this.finderClass = null; + if (Engine.getInstance() == null) { + Context.getCurrentLogger() + .severe("Unable to fully initialize the Restlet. No Restlet engine available."); + throw new RuntimeException( + "Unable to fully initialize the Restlet. No Restlet engine available."); + } + + fireContextChanged(this, context); + } + + /** + * Creates a new finder instance based on the "targetClass" property. If none is define, the + * {@link Application#createFinder(Class)} method is invoked if available, otherwise the {@link + * org.restlet.resource.Finder#createFinder(Class, Class, Context, Logger)} method is called + * with the {@link org.restlet.resource.Finder} class as parameter. + * + * @param resourceClass The target {@link org.restlet.resource.ServerResource} class to find. + * @return The new finder instance. + * @see org.restlet.resource.Finder#createFinder(Class, Class, Context, Logger) + */ + public org.restlet.resource.Finder createFinder( + Class resourceClass) { + org.restlet.resource.Finder result = null; + + if (getFinderClass() != null) { + result = + org.restlet.resource.Finder.createFinder( + resourceClass, getFinderClass(), getContext(), getLogger()); + } else if ((getApplication() != null) && (getApplication() != this)) { + result = getApplication().createFinder(resourceClass); + } else { + result = + org.restlet.resource.Finder.createFinder( + resourceClass, + org.restlet.resource.Finder.class, + getContext(), + getLogger()); + } + + return result; + } + + /** Attempts to {@link #stop()} the Restlet if it is still started. */ + @Override + protected void finalize() throws Throwable { + if (isStarted()) { + stop(); + } + super.finalize(); + } + + /** + * Returns the parent application if it exists, or null. + * + * @return The parent application if it exists, or null. + */ + public Application getApplication() { + return Application.getCurrent(); + } + + /** + * Returns the author(s). + * + * @return The author(s). + */ + public String getAuthor() { + return this.author; + } + + /** + * Returns the context. + * + * @return The context. + */ + public Context getContext() { + return this.context; + } + + /** + * Returns the description. + * + * @return The description + */ + public String getDescription() { + return this.description; + } + + /** + * Returns the finder class used to instantiate resource classes. By default, it returns the + * {@link org.restlet.resource.Finder} class. This property is leveraged by {@link + * Application#setOutboundRoot(Class)} and {@link Application#setInboundRoot(Class)} methods. + * + * @return the finder class to instantiate. + */ + public Class getFinderClass() { + return finderClass; + } + + /** + * Returns the context's logger. + * + * @return The context's logger. + */ + public Logger getLogger() { + Logger result = null; + Context context = getContext(); + + if (context == null) { + context = Context.getCurrent(); + } + + if (context != null) { + result = context.getLogger(); + } + + if (result == null) { + result = Engine.getLogger(this, "org.restlet"); + } + + return result; + } + + /** + * Returns the display name. + * + * @return The display name. + */ + public String getName() { + return this.name; + } + + /** + * Returns the owner(s). + * + * @return The owner(s). + */ + public String getOwner() { + return this.owner; + } + + /** + * Handles a call. Creates an empty {@link Response} object and then invokes {@link + * #handle(Request, Response)}. + * + * @param request The request to handle. + * @return The returned response. + */ + public final Response handle(Request request) { + Response response = new Response(request); + handle(request, response); + return response; + } + + /** + * Handles a call. The default behavior is to initialize the Restlet by setting the current + * context using the {@link Context#setCurrent(Context)} method and by attempting to start it, + * unless it was already started. If an exception is thrown during the start action, then the + * response status is set to {@link Status#SERVER_ERROR_INTERNAL}. + * + *

Subclasses overriding this method should make sure that they call super.handle(request, + * response) before adding their own logic. + * + * @param request The request to handle. + * @param response The response to update. + */ + public void handle(Request request, Response response) { + // Associate the response to the current thread + Response.setCurrent(response); + + // Associate the context to the current thread + if (getContext() != null) { + Context.setCurrent(getContext()); + } + + // Check if the Restlet was started + if (isStopped()) { + try { + start(); + } catch (Exception e) { + // Occurred while starting the Restlet + if (getContext() != null) { + getContext().getLogger().log(Level.WARNING, UNABLE_TO_START, e); + } else { + Context.getCurrentLogger().log(Level.WARNING, UNABLE_TO_START, e); + } + + response.setStatus(Status.SERVER_ERROR_INTERNAL); + } + + if (!isStarted()) { + // No exception raised but the Restlet somehow couldn't be + // started + getContext().getLogger().log(Level.WARNING, UNABLE_TO_START); + response.setStatus(Status.SERVER_ERROR_INTERNAL); + } + } + } + + /** + * Handles a call. + * + * @param request The request to handle. + * @param response The response to update. + * @param onResponseCallback The callback invoked upon response reception. + */ + public final void handle(Request request, Response response, Uniform onResponseCallback) { + request.setOnResponse(onResponseCallback); + handle(request, response); + } + + /** + * Handles a call. + * + * @param request The request to handle. + * @param onReceivedCallback The callback invoked upon request reception. + */ + public final void handle(Request request, Uniform onReceivedCallback) { + Response response = new Response(request); + handle(request, response, onReceivedCallback); + } + + /** + * Indicates if the Restlet is started. + * + * @return True if the Restlet is started. + */ + public boolean isStarted() { + return this.started; + } + + /** + * Indicates if the Restlet is stopped. + * + * @return True if the Restlet is stopped. + */ + public boolean isStopped() { + return !this.started; + } + + /** + * Sets the author(s). + * + * @param author The author(s). + */ + public void setAuthor(String author) { + this.author = author; + } + + /** + * Sets the context. + * + * @param context The context. + */ + public void setContext(Context context) { + this.context = context; + fireContextChanged(this, context); + } + + /** + * Sets the description. + * + * @param description The description. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Sets the finder class to instantiate. This property is leveraged by {@link + * Application#setOutboundRoot(Class)} and {@link Application#setInboundRoot(Class)} methods. + * + * @param finderClass The finder class to instantiate. + */ + public void setFinderClass(Class finderClass) { + this.finderClass = finderClass; + } + + /** + * Sets the display name. + * + * @param name The display name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the owner(s). + * + * @param owner The owner(s). + */ + public void setOwner(String owner) { + this.owner = owner; + } + + /** + * Starts the Restlet. By default, it only sets the "started" internal property to true. + * + *

WARNING: this method must be called at the end of the starting process by subclasses, + * otherwise concurrent threads could enter into the call handling logic too early. + * + * @throws Exception + */ + public synchronized void start() throws Exception { + this.started = true; + } + + /** + * Stops the Restlet. By default, it only sets the "started" internal property to false. + * + *

WARNING: this method must be called at the beginning of the stopping process by + * subclasses, otherwise concurrent threads could continue to (improperly) handle calls. + * + * @throws Exception + */ + public synchronized void stop() throws Exception { + this.started = false; + } } diff --git a/org.restlet/src/main/java/org/restlet/Server.java b/org.restlet/src/main/java/org/restlet/Server.java index 0bdd923aac..21db1d8b16 100644 --- a/org.restlet/src/main/java/org/restlet/Server.java +++ b/org.restlet/src/main/java/org/restlet/Server.java @@ -1,494 +1,489 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; +import java.util.Arrays; +import java.util.List; import org.restlet.data.Protocol; import org.restlet.engine.Engine; import org.restlet.engine.RestletHelper; import org.restlet.resource.ServerResource; -import java.util.Arrays; -import java.util.List; - /** - * Connector acting as a generic server. It internally uses one of the available - * connector helpers registered with the Restlet engine.
+ * Connector acting as a generic server. It internally uses one of the available connector helpers + * registered with the Restlet engine.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables.
+ * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables.
*
- * For advanced cases, it is possible to obtained the wrapped - * {@link RestletHelper} instance that is used by this client to handle the - * calls via the "org.restlet.engine.helper" attribute stored in the - * {@link Context} object. - * + * For advanced cases, it is possible to obtain the wrapped {@link RestletHelper} instance that is + * used by this client to handle the calls via the "org.restlet.engine.helper" attribute stored in + * the {@link Context} object. + * * @author Jerome Louvel */ public class Server extends Connector { - /** The listening address if specified. */ - private volatile String address; - - /** The helper provided by the implementation. */ - private final RestletHelper helper; - - /** The next Restlet. */ - private volatile Restlet next; - - /** The listening port if specified. */ - private volatile int port; - - /** - * Constructor. - * - * @param context The context. - * @param protocols The connector protocols. - * @param port The listening port. - * @param next The next Restlet. - */ - public Server(Context context, List protocols, int port, Restlet next) { - this(context, protocols, null, port, next); - } - - /** - * Constructor. - * - * @param context The context. - * @param protocols The connector protocols. - * @param address The optional listening IP address (useful if multiple IP - * addresses available). You can also use a domain name as an - * alias for the IP address to listen to. - * @param port The listening port. - * @param next The next Restlet. - */ - public Server(Context context, List protocols, String address, int port, Restlet next) { - this(context, protocols, address, port, next, null); - } - - /** - * Constructor. - * - * @param context The context. - * @param protocols The connector protocols. - * @param address The optional listening IP address (useful if multiple IP - * addresses available). You can also use a domain name as an - * alias for the IP address to listen to. - * @param port The listening port. - * @param next The next Restlet. - * @param helperClass Optional helper class name. - */ - public Server(Context context, List protocols, String address, int port, Restlet next, - String helperClass) { - super(context, protocols); - this.address = address; - this.port = port; - this.next = next; - - if (Engine.getInstance() != null) { - this.helper = Engine.getInstance().createHelper(this, helperClass); - } else { - this.helper = null; - } - - if (context != null && this.helper != null) { - context.getAttributes().put("org.restlet.engine.helper", this.helper); - } - } - - /** - * Constructor. Note that it uses the protocol's default port. - * - * @param context The parent context. - * @param protocol The connector protocol. - */ - public Server(Context context, Protocol protocol) { - this(context, protocol, (protocol == null) ? -1 : protocol.getDefaultPort()); - } - - /** - * Constructor. - * - * @param context The context. - * @param protocol The connector protocol. - * @param nextClass The next server resource. - */ - public Server(Context context, Protocol protocol, Class nextClass) { - this(context, protocol); - setNext(createFinder(nextClass)); - } - - /** - * Constructor. - * - * @param context The parent context. - * @param protocol The connector protocol. - * @param port The listening port. - */ - public Server(Context context, Protocol protocol, int port) { - this(context, protocol, port, (Restlet) null); - } - - /** - * Constructor. - * - * @param context The context. - * @param protocol The connector protocol. - * @param port The listening port. - * @param nextClass The next server resource. - */ - public Server(Context context, Protocol protocol, int port, Class nextClass) { - this(context, protocol, port); - setNext(createFinder(nextClass)); - } - - /** - * Constructor. - * - * @param context The context. - * @param protocol The connector protocol. - * @param port The listening port. - * @param next The next Restlet. - */ - public Server(Context context, Protocol protocol, int port, Restlet next) { - this(context, protocol, null, port, next); - } - - /** - * Constructor using the protocol's default port. - * - * @param context The context. - * @param protocol The connector protocol. - * @param next The next Restlet. - */ - public Server(Context context, Protocol protocol, Restlet next) { - this(context, protocol, null, (protocol == null) ? -1 : protocol.getDefaultPort(), next); - } - - /** - * Constructor. - * - * @param context The context. - * @param protocol The connector protocol. - * @param address The optional listening IP address (useful if multiple IP - * addresses available). You can also use a domain name as an - * alias for the IP address to listen to. - * @param port The listening port. - * @param next The next Restlet. - */ - public Server(Context context, Protocol protocol, String address, int port, Restlet next) { - this(context, (protocol == null) ? null : Arrays.asList(protocol), address, port, next); - } - - /** - * Constructor. - * - * @param protocols The connector protocols. - * @param port The listening port. - * @param next The next Restlet. - */ - public Server(List protocols, int port, Restlet next) { - this(null, protocols, port, next); - } - - /** - * Constructor. - * - * @param protocols The connector protocols. - * @param address The optional listening IP address (useful if multiple IP - * addresses available). You can also use a domain name as an - * alias for the IP address to listen to. - * @param port The listening port. - * @param next The next Restlet. - */ - public Server(List protocols, String address, int port, Restlet next) { - this(null, protocols, address, port, next); - } - - /** - * Constructor. - * - * @param protocol The connector protocol. - */ - public Server(Protocol protocol) { - this(null, protocol, (Restlet) null); - } - - /** - * Constructor using the protocol's default port. - * - * @param protocol The connector protocol. - * @param nextClass The next server resource. - */ - public Server(Protocol protocol, Class nextClass) { - this(null, protocol); - setNext(createFinder(nextClass)); - } - - /** - * Constructor. - * - * @param protocol The connector protocol. - * @param port The listening port. - */ - public Server(Protocol protocol, int port) { - this(null, protocol, port, (Restlet) null); - } - - /** - * Constructor. - * - * @param protocol The connector protocol. - * @param port The listening port. - * @param nextClass The next server resource. - */ - public Server(Protocol protocol, int port, Class nextClass) { - this(protocol, port); - setNext(createFinder(nextClass)); - } - - /** - * Constructor. - * - * @param protocol The connector protocol. - * @param port The listening port. - * @param next The next Restlet. - */ - public Server(Protocol protocol, int port, Restlet next) { - this(null, protocol, port, next); - } - - /** - * Constructor using the protocol's default port. - * - * @param protocol The connector protocol. - * @param next The next Restlet. - */ - public Server(Protocol protocol, Restlet next) { - this(null, protocol, next); - } - - /** - * Constructor using the protocol's default port. - * - * @param protocol The connector protocol. - * @param address The listening IP address (useful if multiple IP addresses - * available). You can also use a domain name as an alias for - * the IP address to listen to. - */ - public Server(Protocol protocol, String address) { - this(null, protocol, address, protocol.getDefaultPort(), null); - } - - /** - * Constructor using the protocol's default port. - * - * @param protocol The connector protocol. - * @param address The listening IP address (useful if multiple IP addresses - * available). You can also use a domain name as an alias for - * the IP address to listen to. - * @param nextClass The next server resource. - */ - public Server(Protocol protocol, String address, Class nextClass) { - this(protocol, address); - setNext(createFinder(nextClass)); - } - - /** - * Constructor. - * - * @param protocol The connector protocol. - * @param address The optional listening IP address (useful if multiple IP - * addresses available). You can also use a domain name as an - * alias for the IP address to listen to. - * @param port The listening port. - */ - public Server(Protocol protocol, String address, int port) { - this(null, protocol, address, port, null); - } - - /** - * Constructor. - * - * @param protocol The connector protocol. - * @param address The optional listening IP address (useful if multiple IP - * addresses available). You can also use a domain name as an - * alias for the IP address to listen to. - * @param port The listening port. - * @param next The next Restlet. - */ - public Server(Protocol protocol, String address, int port, Restlet next) { - this(null, protocol, address, port, next); - } - - /** - * Constructor using the protocol's default port. - * - * @param protocol The connector protocol. - * @param address The listening IP address (useful if multiple IP addresses - * available). You can also use a domain name as an alias for - * the IP address to listen to. - * @param next The next Restlet. - */ - public Server(Protocol protocol, String address, Restlet next) { - this(null, protocol, address, protocol.getDefaultPort(), next); - } - - /** - * Returns the actual server port after it has started. If an ephemeral port is - * used it will be returned, otherwise the fixed port will be provided. - * - * @return The actual server port. - */ - public int getActualPort() { - return (getPort() == 0) ? getEphemeralPort() : getPort(); - } - - /** - * Returns the optional listening IP address (local host used if null). - * - * @return The optional listening IP address (local host used if null). - */ - public String getAddress() { - return this.address; - } - - /** - * Returns the actual ephemeral port used when the listening port is set to '0'. - * The default value is '-1' if no ephemeral port is known. See - * InetSocketAddress#InetSocketAddress(int) and ServerSocket#getLocalPort() - * methods for details. - * - * @return The actual ephemeral port used. - */ - public int getEphemeralPort() { - return (Integer) getHelper().getAttributes().get("ephemeralPort"); - } - - /** - * Returns the internal server. - * - * @return The internal server. - */ - private RestletHelper getHelper() { - return this.helper; - } - - /** - * Returns the next Restlet. - * - * @return The next Restlet. - */ - public Restlet getNext() { - return this.next; - } - - /** - * Returns the listening port if specified. - * - * @return The listening port if specified. - */ - public int getPort() { - return this.port; - } - - @Override - public void handle(Request request, Response response) { - super.handle(request, response); - - if (getNext() != null) { - getNext().handle(request, response); - } - } - - /** - * Indicates if a next Restlet is set. - * - * @return True if a next Restlet is set. - */ - public boolean hasNext() { - return this.next != null; - } - - /** - * Indicates the underlying connector helper is available. - * - * @return True if the underlying connector helper is available. - */ - @Override - public boolean isAvailable() { - return getHelper() != null; - } - - /** - * Sets the optional listening IP address (local host used if null). - * - * @param address The optional listening IP address (local host used if null). - */ - public void setAddress(String address) { - this.address = address; - } - - /** - * Sets the next Restlet as a Finder for a given resource class. When the call - * is delegated to the Finder instance, a new instance of the resource class - * will be created and will actually handle the request. - * - * @param nextClass The next resource class to attach. - */ - public void setNext(Class nextClass) { - setNext(createFinder(nextClass)); - } - - /** - * Sets the next Restlet. - * - * @param next The next Restlet. - */ - public void setNext(Restlet next) { - this.next = next; - } - - /** - * Sets the listening port if specified. Note that '0' means that the system - * will pick up an ephemeral port at the binding time. This ephemeral can be - * retrieved once the server is started using the {@link #getEphemeralPort()} - * method. - * - * @param port The listening port if specified. - */ - protected void setPort(int port) { - this.port = port; - } - - @Override - public synchronized void start() throws Exception { - if (isStopped()) { - if (getHelper() != null) { - getHelper().start(); - } - - // Must be invoked as a last step - super.start(); - } - } - - @Override - public synchronized void stop() throws Exception { - if (isStarted()) { - // Must be invoked as a first step - super.stop(); - - if (getHelper() != null) { - getHelper().stop(); - } - } - } - + /** The listening address if specified. */ + private volatile String address; + + /** The helper provided by the implementation. */ + private final RestletHelper helper; + + /** The next Restlet. */ + private volatile Restlet next; + + /** The listening port if specified. */ + private volatile int port; + + /** + * Constructor. + * + * @param context The context. + * @param protocols The connector protocols. + * @param port The listening port. + * @param next The next Restlet. + */ + public Server(Context context, List protocols, int port, Restlet next) { + this(context, protocols, null, port, next); + } + + /** + * Constructor. + * + * @param context The context. + * @param protocols The connector protocols. + * @param address The optional listening IP address (useful if multiple IP addresses available). + * You can also use a domain name as an alias for the IP address to listen to. + * @param port The listening port. + * @param next The next Restlet. + */ + public Server( + Context context, List protocols, String address, int port, Restlet next) { + this(context, protocols, address, port, next, null); + } + + /** + * Constructor. + * + * @param context The context. + * @param protocols The connector protocols. + * @param address The optional listening IP address (useful if multiple IP addresses available). + * You can also use a domain name as an alias for the IP address to listen to. + * @param port The listening port. + * @param next The next Restlet. + * @param helperClass Optional helper class name. + */ + public Server( + Context context, + List protocols, + String address, + int port, + Restlet next, + String helperClass) { + super(context, protocols); + this.address = address; + this.port = port; + this.next = next; + + if (Engine.getInstance() != null) { + this.helper = Engine.getInstance().createHelper(this, helperClass); + } else { + this.helper = null; + } + + if (context != null && this.helper != null) { + context.getAttributes().put("org.restlet.engine.helper", this.helper); + } + } + + /** + * Constructor. Note that it uses the protocol's default port. + * + * @param context The parent context. + * @param protocol The connector protocol. + */ + public Server(Context context, Protocol protocol) { + this(context, protocol, (protocol == null) ? -1 : protocol.getDefaultPort()); + } + + /** + * Constructor. + * + * @param context The context. + * @param protocol The connector protocol. + * @param nextClass The next server resource. + */ + public Server(Context context, Protocol protocol, Class nextClass) { + this(context, protocol); + setNext(createFinder(nextClass)); + } + + /** + * Constructor. + * + * @param context The parent context. + * @param protocol The connector protocol. + * @param port The listening port. + */ + public Server(Context context, Protocol protocol, int port) { + this(context, protocol, port, (Restlet) null); + } + + /** + * Constructor. + * + * @param context The context. + * @param protocol The connector protocol. + * @param port The listening port. + * @param nextClass The next server resource. + */ + public Server( + Context context, + Protocol protocol, + int port, + Class nextClass) { + this(context, protocol, port); + setNext(createFinder(nextClass)); + } + + /** + * Constructor. + * + * @param context The context. + * @param protocol The connector protocol. + * @param port The listening port. + * @param next The next Restlet. + */ + public Server(Context context, Protocol protocol, int port, Restlet next) { + this(context, protocol, null, port, next); + } + + /** + * Constructor using the protocol's default port. + * + * @param context The context. + * @param protocol The connector protocol. + * @param next The next Restlet. + */ + public Server(Context context, Protocol protocol, Restlet next) { + this(context, protocol, null, (protocol == null) ? -1 : protocol.getDefaultPort(), next); + } + + /** + * Constructor. + * + * @param context The context. + * @param protocol The connector protocol. + * @param address The optional listening IP address (useful if multiple IP addresses available). + * You can also use a domain name as an alias for the IP address to listen to. + * @param port The listening port. + * @param next The next Restlet. + */ + public Server(Context context, Protocol protocol, String address, int port, Restlet next) { + this(context, (protocol == null) ? null : Arrays.asList(protocol), address, port, next); + } + + /** + * Constructor. + * + * @param protocols The connector protocols. + * @param port The listening port. + * @param next The next Restlet. + */ + public Server(List protocols, int port, Restlet next) { + this(null, protocols, port, next); + } + + /** + * Constructor. + * + * @param protocols The connector protocols. + * @param address The optional listening IP address (useful if multiple IP addresses available). + * You can also use a domain name as an alias for the IP address to listen to. + * @param port The listening port. + * @param next The next Restlet. + */ + public Server(List protocols, String address, int port, Restlet next) { + this(null, protocols, address, port, next); + } + + /** + * Constructor. + * + * @param protocol The connector protocol. + */ + public Server(Protocol protocol) { + this(null, protocol, (Restlet) null); + } + + /** + * Constructor using the protocol's default port. + * + * @param protocol The connector protocol. + * @param nextClass The next server resource. + */ + public Server(Protocol protocol, Class nextClass) { + this(null, protocol); + setNext(createFinder(nextClass)); + } + + /** + * Constructor. + * + * @param protocol The connector protocol. + * @param port The listening port. + */ + public Server(Protocol protocol, int port) { + this(null, protocol, port, (Restlet) null); + } + + /** + * Constructor. + * + * @param protocol The connector protocol. + * @param port The listening port. + * @param nextClass The next server resource. + */ + public Server(Protocol protocol, int port, Class nextClass) { + this(protocol, port); + setNext(createFinder(nextClass)); + } + + /** + * Constructor. + * + * @param protocol The connector protocol. + * @param port The listening port. + * @param next The next Restlet. + */ + public Server(Protocol protocol, int port, Restlet next) { + this(null, protocol, port, next); + } + + /** + * Constructor using the protocol's default port. + * + * @param protocol The connector protocol. + * @param next The next Restlet. + */ + public Server(Protocol protocol, Restlet next) { + this(null, protocol, next); + } + + /** + * Constructor using the protocol's default port. + * + * @param protocol The connector protocol. + * @param address The listening IP address (useful if multiple IP addresses available). You can + * also use a domain name as an alias for the IP address to listen to. + */ + public Server(Protocol protocol, String address) { + this(null, protocol, address, protocol.getDefaultPort(), null); + } + + /** + * Constructor using the protocol's default port. + * + * @param protocol The connector protocol. + * @param address The listening IP address (useful if multiple IP addresses available). You can + * also use a domain name as an alias for the IP address to listen to. + * @param nextClass The next server resource. + */ + public Server(Protocol protocol, String address, Class nextClass) { + this(protocol, address); + setNext(createFinder(nextClass)); + } + + /** + * Constructor. + * + * @param protocol The connector protocol. + * @param address The optional listening IP address (useful if multiple IP addresses available). + * You can also use a domain name as an alias for the IP address to listen to. + * @param port The listening port. + */ + public Server(Protocol protocol, String address, int port) { + this(null, protocol, address, port, null); + } + + /** + * Constructor. + * + * @param protocol The connector protocol. + * @param address The optional listening IP address (useful if multiple IP addresses available). + * You can also use a domain name as an alias for the IP address to listen to. + * @param port The listening port. + * @param next The next Restlet. + */ + public Server(Protocol protocol, String address, int port, Restlet next) { + this(null, protocol, address, port, next); + } + + /** + * Constructor using the protocol's default port. + * + * @param protocol The connector protocol. + * @param address The listening IP address (useful if multiple IP addresses available). You can + * also use a domain name as an alias for the IP address to listen to. + * @param next The next Restlet. + */ + public Server(Protocol protocol, String address, Restlet next) { + this(null, protocol, address, protocol.getDefaultPort(), next); + } + + /** + * Returns the actual server port after it has started. If an ephemeral port is used it will be + * returned, otherwise the fixed port will be provided. + * + * @return The actual server port. + */ + public int getActualPort() { + return (getPort() == 0) ? getEphemeralPort() : getPort(); + } + + /** + * Returns the optional listening IP address (local host used if null). + * + * @return The optional listening IP address (local host used if null). + */ + public String getAddress() { + return this.address; + } + + /** + * Returns the actual ephemeral port used when the listening port is set to '0'. The default + * value is '-1' if no ephemeral port is known. See InetSocketAddress#InetSocketAddress(int) and + * ServerSocket#getLocalPort() methods for details. + * + * @return The actual ephemeral port used. + */ + public int getEphemeralPort() { + return (Integer) getHelper().getAttributes().get("ephemeralPort"); + } + + /** + * Returns the internal server. + * + * @return The internal server. + */ + private RestletHelper getHelper() { + return this.helper; + } + + /** + * Returns the next Restlet. + * + * @return The next Restlet. + */ + public Restlet getNext() { + return this.next; + } + + /** + * Returns the listening port if specified. + * + * @return The listening port if specified. + */ + public int getPort() { + return this.port; + } + + @Override + public void handle(Request request, Response response) { + super.handle(request, response); + + if (getNext() != null) { + getNext().handle(request, response); + } + } + + /** + * Indicates if the next Restlet is set. + * + * @return True if a next Restlet is set. + */ + public boolean hasNext() { + return this.next != null; + } + + /** + * Indicates the underlying connector helper is available. + * + * @return True if the underlying connector helper is available. + */ + @Override + public boolean isAvailable() { + return getHelper() != null; + } + + /** + * Sets the optional listening IP address (local host used if null). + * + * @param address The optional listening IP address (local host used if null). + */ + public void setAddress(String address) { + this.address = address; + } + + /** + * Sets the next Restlet as a Finder for a given resource class. When the call is delegated to + * the Finder instance, a new instance of the resource class will be created and will actually + * handle the request. + * + * @param nextClass The next resource class to attach. + */ + public void setNext(Class nextClass) { + setNext(createFinder(nextClass)); + } + + /** + * Sets the next Restlet. + * + * @param next The next Restlet. + */ + public void setNext(Restlet next) { + this.next = next; + } + + /** + * Sets the listening port if specified. Note that '0' means that the system will pick up an + * ephemeral port at the binding time. This ephemeral can be retrieved once the server is + * started using the {@link #getEphemeralPort()} method. + * + * @param port The listening port if specified. + */ + protected void setPort(int port) { + this.port = port; + } + + @Override + public synchronized void start() throws Exception { + if (isStopped()) { + if (getHelper() != null) { + getHelper().start(); + } + + // Must be invoked as a last step + super.start(); + } + } + + @Override + public synchronized void stop() throws Exception { + if (isStarted()) { + // Must be invoked as a first step + super.stop(); + + if (getHelper() != null) { + getHelper().stop(); + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/Uniform.java b/org.restlet/src/main/java/org/restlet/Uniform.java index 6552a37b7f..6870439d6c 100644 --- a/org.restlet/src/main/java/org/restlet/Uniform.java +++ b/org.restlet/src/main/java/org/restlet/Uniform.java @@ -1,36 +1,33 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; /** - * Uniform REST interface. "The central feature that distinguishes the REST - * architectural style from other network-based styles is its emphasis on a - * uniform interface between components. By applying the software engineering - * principle of generality to the component interface, the overall system - * architecture is simplified and the visibility of interactions is improved. - * Implementations are decoupled from the services they provide, which - * encourages independent evolvability." Roy T. Fielding - * - * @see Source - * dissertation + * Uniform REST interface. "The central feature that distinguishes the REST architectural style from + * other network-based styles is its emphasis on a uniform interface between components. By applying + * the software engineering principle of generality to the component interface, the overall system + * architecture is simplified and the visibility of interactions is improved. Implementations are + * decoupled from the services they provide, which encourages independent evolvability." Roy T. + * Fielding + * + * @see Source + * dissertation * @author Jerome Louvel */ public interface Uniform { - /** - * Handles a uniform call. It is important to realize that this interface can be - * used either on the client-side or on the server-side. - * - * @param request The request to handle. - * @param response The associated response. - */ - void handle(Request request, Response response); + /** + * Handles a uniform call. It is important to realize that this interface can be used either on + * the client-side or on the server-side. + * + * @param request The request to handle. + * @param response The associated response. + */ + void handle(Request request, Response response); } diff --git a/org.restlet/src/main/java/org/restlet/data/AuthenticationInfo.java b/org.restlet/src/main/java/org/restlet/data/AuthenticationInfo.java index f93f067039..fe95757f20 100644 --- a/org.restlet/src/main/java/org/restlet/data/AuthenticationInfo.java +++ b/org.restlet/src/main/java/org/restlet/data/AuthenticationInfo.java @@ -1,229 +1,205 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.Objects; import org.restlet.engine.util.SystemUtils; /** - * Preemptive authentication information. Sent by an origin server to a client - * after a successful digest authentication attempt.
+ * Preemptive authentication information. Sent by an origin server to a client after a successful + * digest authentication attempt.
*
- * Note that when used with HTTP connectors, this class maps to the - * "Authentication-Info" header. - * - * @see HTTP - * Authentication - The Authentication-Info Header - * + * Note that when used with HTTP connectors, this class maps to the "Authentication-Info" header. + * + * @see HTTP Authentication - + * The Authentication-Info Header * @author Kelly McLaughlin * @author Jerome Louvel */ public class AuthenticationInfo { - /** The next nonce value. */ - private volatile String nextServerNonce; - - /** The nonce-count value. */ - private volatile int nonceCount; - - /** The client nonce. */ - private volatile String clientNonce; - - /** The quality of protection. */ - private volatile String quality; - - /** The optional response digest for mutual authentication. */ - private volatile String responseDigest; - - /** - * Default constructor. - * - * @param nextNonce The next nonce value. - */ - // public AuthenticationInfo(String nextNonce) { - // this(nextNonce, 0, ); - // } - - /** - * Constructor. - * - * @param nextNonce The next nonce value. - * @param nonceCount The nonce-count value. - * @param cnonce The cnonce value. - * @param quality The quality of protection. - * @param responseDigest The optional response digest for mutual authentication. - */ - public AuthenticationInfo(String nextNonce, int nonceCount, String cnonce, String quality, String responseDigest) { - this.nextServerNonce = nextNonce; - this.nonceCount = nonceCount; - this.clientNonce = cnonce; - this.quality = quality; - this.responseDigest = responseDigest; - } - - /** {@inheritDoc} */ - @Override - public final boolean equals(final Object obj) { - boolean result = (obj == this); - - // if obj == this no need to go further - if (!result) { - // if obj isn't a challenge request or is null don't evaluate - // further - if (obj instanceof AuthenticationInfo) { - final AuthenticationInfo that = (AuthenticationInfo) obj; - if (getNextServerNonce() != null) { - result = getNextServerNonce().equals(that.getNextServerNonce()); - } else { - result = (that.getNextServerNonce() == null); - } - - if (result) { - result = (getNonceCount() == that.getNonceCount()); - } - - if (result) { - if (getClientNonce() != null) { - result = getClientNonce().equals(that.getClientNonce()); - } else { - result = (that.getClientNonce() == null); - } - } - - if (result) { - if (getQuality() != null) { - result = getQuality().equals(that.getQuality()); - } else { - result = (that.getQuality() == null); - } - } - - if (result) { - if (getResponseDigest() != null) { - result = getResponseDigest().equals(that.getResponseDigest()); - } else { - result = (that.getResponseDigest() == null); - } - } - } - } - - return result; - } - - /** - * Returns the client nonce. - * - * @return The client nonce. - */ - public String getClientNonce() { - return this.clientNonce; - } - - /** - * Returns the next server nonce. This is the nonce the server wishes the client - * to use for a future authentication response - * - * @return The next nonce value. - */ - public String getNextServerNonce() { - return this.nextServerNonce; - } - - /** - * Returns the nonce-count value. - * - * @return The nonce-count value. - */ - public int getNonceCount() { - return this.nonceCount; - } - - /** - * Returns the quality of protection. The value can be - * {@link ChallengeMessage#QUALITY_AUTHENTICATION} for authentication or - * {@link ChallengeMessage#QUALITY_AUTHENTICATION_INTEGRITY} for authentication - * with integrity protection. - * - * @return The quality of protection. - */ - public String getQuality() { - return this.quality; - } - - /** - * Returns the optional response digest for mutual authentication. Note that - * when used with HTTP connectors, this property maps to the "response-digest" - * value in the "response-auth" directive of the "Authentication-Info" header. - * - * @return The optional response digest for mutual authentication. - */ - public String getResponseDigest() { - return this.responseDigest; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return SystemUtils.hashCode(getNextServerNonce(), getNonceCount(), getClientNonce(), getQuality(), - getResponseDigest()); - } - - /** - * Sets the client nonce. - * - * @param clientNonce The client nonce. - */ - public void setClientNonce(String clientNonce) { - this.clientNonce = clientNonce; - } - - /** - * Sets the next server nonce. This is the nonce the server wishes the client to - * use for a future authentication response - * - * @param nextNonce The next nonce. - */ - public void setNextServerNonce(String nextNonce) { - this.nextServerNonce = nextNonce; - } - - /** - * Sets the nonce-count value. - * - * @param nonceCount The nonceCount value. - */ - public void setNonceCount(int nonceCount) { - this.nonceCount = nonceCount; - } - - /** - * Sets the quality of protection. The value can be - * {@link ChallengeMessage#QUALITY_AUTHENTICATION} for authentication or - * {@link ChallengeMessage#QUALITY_AUTHENTICATION_INTEGRITY} for authentication - * with integrity protection. - * - * @param qop The quality of protection. - */ - public void setQuality(String qop) { - this.quality = qop; - } - - /** - * Sets the optional response digest for mutual authentication. Note that when - * used with HTTP connectors, this property maps to the "response-digest" value - * in the "response-auth" directive of the "Authentication-Info" header. - * - * @param responseDigest The response digest. - */ - public void setResponseDigest(String responseDigest) { - this.responseDigest = responseDigest; - } + /** The next nonce value. */ + private volatile String nextServerNonce; + + /** The nonce-count value. */ + private volatile int nonceCount; + + /** The client nonce. */ + private volatile String clientNonce; + + /** The quality of protection. */ + private volatile String quality; + + /** The optional response digest for mutual authentication. */ + private volatile String responseDigest; + + /** + * Constructor. + * + * @param nextNonce The next nonce value. + * @param nonceCount The nonce-count value. + * @param cnonce The cnonce value. + * @param quality The quality of protection. + * @param responseDigest The optional response digest for mutual authentication. + */ + public AuthenticationInfo( + String nextNonce, + int nonceCount, + String cnonce, + String quality, + String responseDigest) { + this.nextServerNonce = nextNonce; + this.nonceCount = nonceCount; + this.clientNonce = cnonce; + this.quality = quality; + this.responseDigest = responseDigest; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj == null) { + return false; + } + + if (obj instanceof final AuthenticationInfo that) { + if (!Objects.equals(getNextServerNonce(), that.getNextServerNonce())) { + return false; + } + if (getNonceCount() != that.getNonceCount()) { + return false; + } + if (!Objects.equals(getClientNonce(), that.getClientNonce())) { + return false; + } + if (!Objects.equals(getQuality(), that.getQuality())) { + return false; + } + return Objects.equals(getResponseDigest(), that.getResponseDigest()); + } + + return false; + } + + /** + * Returns the client nonce. + * + * @return The client nonce. + */ + public String getClientNonce() { + return this.clientNonce; + } + + /** + * Returns the next server nonce. This is the nonce the server wishes the client to use for a + * future authentication response + * + * @return The next nonce value. + */ + public String getNextServerNonce() { + return this.nextServerNonce; + } + + /** + * Returns the nonce-count value. + * + * @return The nonce-count value. + */ + public int getNonceCount() { + return this.nonceCount; + } + + /** + * Returns the quality of protection. The value can be {@link + * ChallengeMessage#QUALITY_AUTHENTICATION} for authentication or {@link + * ChallengeMessage#QUALITY_AUTHENTICATION_INTEGRITY} for authentication with integrity + * protection. + * + * @return The quality of protection. + */ + public String getQuality() { + return this.quality; + } + + /** + * Returns the optional response digest for mutual authentication. Note that when used with HTTP + * connectors, this property maps to the "response-digest" value in the "response-auth" + * directive of the "Authentication-Info" header. + * + * @return The optional response digest for mutual authentication. + */ + public String getResponseDigest() { + return this.responseDigest; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return SystemUtils.hashCode( + getNextServerNonce(), + getNonceCount(), + getClientNonce(), + getQuality(), + getResponseDigest()); + } + + /** + * Sets the client nonce. + * + * @param clientNonce The client nonce. + */ + public void setClientNonce(String clientNonce) { + this.clientNonce = clientNonce; + } + + /** + * Sets the next server nonce. This is the nonce the server wishes the client to use for a + * future authentication response + * + * @param nextNonce The next nonce. + */ + public void setNextServerNonce(String nextNonce) { + this.nextServerNonce = nextNonce; + } + + /** + * Sets the nonce-count value. + * + * @param nonceCount The nonceCount value. + */ + public void setNonceCount(int nonceCount) { + this.nonceCount = nonceCount; + } + + /** + * Sets the quality of protection. The value can be {@link + * ChallengeMessage#QUALITY_AUTHENTICATION} for authentication or {@link + * ChallengeMessage#QUALITY_AUTHENTICATION_INTEGRITY} for authentication with integrity + * protection. + * + * @param qop The quality of protection. + */ + public void setQuality(String qop) { + this.quality = qop; + } + + /** + * Sets the optional response digest for mutual authentication. Note that when used with HTTP + * connectors, this property maps to the "response-digest" value in the "response-auth" + * directive of the "Authentication-Info" header. + * + * @param responseDigest The response digest. + */ + public void setResponseDigest(String responseDigest) { + this.responseDigest = responseDigest; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/CacheDirective.java b/org.restlet/src/main/java/org/restlet/data/CacheDirective.java index 9ddd8d6622..9165154b98 100644 --- a/org.restlet/src/main/java/org/restlet/data/CacheDirective.java +++ b/org.restlet/src/main/java/org/restlet/data/CacheDirective.java @@ -1,461 +1,429 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.List; +import java.util.Objects; import org.restlet.engine.header.HeaderConstants; import org.restlet.engine.util.SystemUtils; import org.restlet.util.NamedValue; -import java.util.List; -import java.util.Objects; - /** - * Directive for caching mechanisms along the call chain. This overrides the - * default behavior of those caches and proxies.
+ * Directive for caching mechanisms along the call chain. This overrides the default behavior of + * those caches and proxies.
*
- * Note that when used with HTTP connectors, this class maps to the - * "Cache-Control" header. - * + * Note that when used with HTTP connectors, this class maps to the "Cache-Control" header. + * * @author Jerome Louvel */ public final class CacheDirective implements NamedValue { - /** - * Creates a "max-age" directive. Indicates that the client is willing to accept - * a response whose age is no greater than the specified time in seconds. Unless - * "max-stale" directive is also included, the client is not willing to accept a - * stale response.
- *
- * Note that this directive can be used on requests or responses. - * - * @param maxAge Maximum age in seconds. - * @return A new "max-age" directive. - * @see HTTP - * 1.1 - Modifications of the Basic Expiration Mechanism - * @see HTTP - * 1.1 - Cache Revalidation and Reload Controls - */ - public static CacheDirective maxAge(int maxAge) { - return new CacheDirective(HeaderConstants.CACHE_MAX_AGE, Integer.toString(maxAge), true); - } - - /** - * Creates a "max-stale" directive. Indicates that the client is willing to - * accept a response that has exceeded its expiration time by any amount of - * time.
- *
- * Note that this directive can be used on requests only. - * - * @return A new "max-stale" directive. - * @see HTTP - * 1.1 - Modifications of the Basic Expiration Mechanism - */ - public static CacheDirective maxStale() { - return new CacheDirective(HeaderConstants.CACHE_MAX_STALE); - } - - /** - * Creates a "max-stale" directive. Indicates that the client is willing to - * accept a response that has exceeded its expiration time by a given amount of - * time.
- *
- * Note that this directive can be used on requests only. - * - * @param maxStale Maximum stale age in seconds. - * @return A new "max-stale" directive. - * @see HTTP - * 1.1 - Modifications of the Basic Expiration Mechanism - */ - public static CacheDirective maxStale(int maxStale) { - return new CacheDirective(HeaderConstants.CACHE_MAX_STALE, Integer.toString(maxStale), true); - } - - /** - * Creates a "min-fresh" directive. Indicates that the client is willing to - * accept a response whose freshness lifetime is no less than its current age - * plus the specified time in seconds. That is, the client wants a response that - * will still be fresh for at least the specified number of seconds.
- *
- * Note that this directive can be used on requests only. - * - * @param minFresh Minimum freshness lifetime in seconds. - * @return A new "min-fresh" directive. - * @see HTTP - * 1.1 - Modifications of the Basic Expiration Mechanism - */ - public static CacheDirective minFresh(int minFresh) { - return new CacheDirective(HeaderConstants.CACHE_MIN_FRESH, Integer.toString(minFresh), true); - } - - /** - * Creates a "must-revalidate" directive. Indicates that the origin server - * requires revalidation of a cache entry on any subsequent use.
- *
- * Note that this directive can be used on responses only. - * - * @return A new "must-revalidate" directive. - * @see HTTP - * 1.1 - Cache Revalidation and Reload Controls - */ - public static CacheDirective mustRevalidate() { - return new CacheDirective(HeaderConstants.CACHE_MUST_REVALIDATE); - } - - /** - * Creates a "no-cache" directive. Indicates that a cache must not use the - * response to satisfy subsequent requests without successful revalidation with - * the origin server.
- *
- * Note that this directive can be used on requests or responses. - * - * @return A new "no-cache" directive. - * @see HTTP - * 1.1 - What is Cacheable - */ - public static CacheDirective noCache() { - return new CacheDirective(HeaderConstants.CACHE_NO_CACHE); - } - - /** - * Creates a "no-cache" directive. Indicates that a cache must not use the - * response to satisfy subsequent requests without successful revalidation with - * the origin server.
- *
- * Note that this directive can be used on requests or responses. - * - * @param fieldNames Field names, typically a HTTP header name, that must not be - * sent by caches. - * @return A new "no-cache" directive. - * @see HTTP - * 1.1 - What is Cacheable - */ - public static CacheDirective noCache(List fieldNames) { - StringBuilder sb = new StringBuilder(); - - if (fieldNames != null) { - for (int i = 0; i < fieldNames.size(); i++) { - sb.append("\"").append(fieldNames.get(i)).append("\""); - - if (i < fieldNames.size() - 1) { - sb.append(','); - } - } - } - - return new CacheDirective(HeaderConstants.CACHE_NO_CACHE, sb.toString()); - } - - /** - * Creates a "no-cache" directive. Indicates that a cache must not use the - * response to satisfy subsequent requests without successful revalidation with - * the origin server.
- *
- * Note that this directive can be used on requests or responses. - * - * @param fieldName A field name, typically a HTTP header name, that must not be - * sent by caches. - * @return A new "no-cache" directive. - * @see HTTP - * 1.1 - What is Cacheable - */ - public static CacheDirective noCache(String fieldName) { - return new CacheDirective(HeaderConstants.CACHE_NO_CACHE, "\"" + fieldName + "\""); - } - - /** - * Creates a "no-store" directive. Indicates that a cache must not release or - * retain any information about the call. This applies to both private and - * shared caches.
- *
- * Note that this directive can be used on requests or responses. - * - * @return A new "no-store" directive. - * @see HTTP - * 1.1 - What May be Stored by Caches - */ - public static CacheDirective noStore() { - return new CacheDirective(HeaderConstants.CACHE_NO_STORE); - } - - /** - * Creates a "no-transform" directive. Indicates that a cache or intermediary - * proxy must not transform the response entity.
- *
- * Note that this directive can be used on requests or responses. - * - * @return A new "no-transform" directive. - * @see HTTP - * 1.1 - No-Transform Directive - */ - public static CacheDirective noTransform() { - return new CacheDirective(HeaderConstants.CACHE_NO_TRANSFORM); - } - - /** - * Creates a "onlyIfCached" directive. Indicates that only cached responses - * should be returned to the client.
- *
- * Note that this directive can be used on requests only. - * - * @return A new "only-if-cached" directive. - * @see HTTP - * 1.1 - Cache Revalidation and Reload Controls - */ - public static CacheDirective onlyIfCached() { - return new CacheDirective(HeaderConstants.CACHE_ONLY_IF_CACHED); - } - - /** - * Creates a "private" directive. Indicates that all or part of the response - * message is intended for a single user and must not be cached by a shared - * cache.
- *
- * Note that this directive can be used on responses only. - * - * @return A new "private" directive. - * @see HTTP - * 1.1 - What is Cacheable - */ - public static CacheDirective privateInfo() { - return new CacheDirective(HeaderConstants.CACHE_PRIVATE); - } - - /** - * Creates a "private" directive. Indicates that all or part of the response - * message is intended for a single user and must not be cached by a shared - * cache.
- *
- * Note that this directive can be used on responses only. - * - * @param fieldNames Field names, typically a HTTP header name, that must be - * private. - * @return A new "private" directive. - * @see HTTP - * 1.1 - What is Cacheable - */ - public static CacheDirective privateInfo(List fieldNames) { - StringBuilder sb = new StringBuilder(); - - if (fieldNames != null) { - for (int i = 0; i < fieldNames.size(); i++) { - sb.append("\"").append(fieldNames.get(i)).append("\""); - - if (i < fieldNames.size() - 1) { - sb.append(','); - } - } - } - - return new CacheDirective(HeaderConstants.CACHE_PRIVATE, sb.toString()); - } - - /** - * Creates a "private" directive. Indicates that all or part of the response - * message is intended for a single user and must not be cached by a shared - * cache.
- *
- * Note that this directive can be used on responses only. - * - * @param fieldName A field name, typically a HTTP header name, that is private. - * @return A new "private" directive. - * @see HTTP - * 1.1 - What is Cacheable - */ - public static CacheDirective privateInfo(String fieldName) { - return new CacheDirective(HeaderConstants.CACHE_PRIVATE, "\"" + fieldName + "\""); - } - - /** - * Creates a "proxy-revalidate" directive. Indicates that the origin server - * requires revalidation of a cache entry on any subsequent use, except that it - * does not apply to non-shared user agent caches
- *
- * Note that this directive can be used on responses only. - * - * @return A new "proxy-revalidate" directive. - * @see HTTP - * 1.1 - Cache Revalidation and Reload Controls - */ - public static CacheDirective proxyMustRevalidate() { - return new CacheDirective(HeaderConstants.CACHE_PROXY_MUST_REVALIDATE); - } - - /** - * Creates a "public" directive. Indicates that the response may be cached by - * any cache, even if it would normally be non-cacheable or cacheable only - * within a non-shared cache.
- *
- * Note that this directive can be used on responses only. - * - * @return A new "public" directive. - * @see HTTP - * 1.1 - What is Cacheable - */ - public static CacheDirective publicInfo() { - return new CacheDirective(HeaderConstants.CACHE_PUBLIC); - } - - /** - * Creates a "s-maxage" directive. Indicates that the client is willing to - * accept a response from a shared cache (but not a private cache) whose age is - * no greater than the specified time in seconds.
- *
- * Note that this directive can be used on responses only. - * - * @param sharedMaxAge Maximum age in seconds. - * @return A new "s-maxage" directive. - * @see HTTP - * 1.1 - Modifications of the Basic Expiration Mechanism - */ - public static CacheDirective sharedMaxAge(int sharedMaxAge) { - return new CacheDirective(HeaderConstants.CACHE_SHARED_MAX_AGE, Integer.toString(sharedMaxAge), true); - } - - /** Indicates if the directive is a digit value. */ - private boolean digit; - - /** The name. */ - private volatile String name; - - /** The value. */ - private volatile String value; - - /** - * Constructor for directives with no value. - * - * @param name The directive name. - */ - public CacheDirective(String name) { - this(name, null); - } - - /** - * Constructor for directives with a value. - * - * @param name The directive name. - * @param value The directive value. - */ - public CacheDirective(String name, String value) { - this(name, value, false); - } - - /** - * Constructor for directives with a value. - * - * @param name The directive name. - * @param value The directive value. - * @param digit The kind of value (true for a digit value, false otherwise). - */ - public CacheDirective(String name, String value, boolean digit) { - this.name = name; - this.value = value; - this.digit = digit; - } - - /** {@inheritDoc} */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - if (!(obj instanceof CacheDirective)) { - return false; - } - - CacheDirective that = (CacheDirective) obj; - - return Objects.equals(getName(), that.getName()) && Objects.equals(getValue(), that.getValue()) - && (this.digit == that.digit); - } - - /** - * Returns the name. - * - * @return The name. - */ - public String getName() { - return name; - } - - /** - * Returns the value. - * - * @return The value. - */ - public String getValue() { - return value; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return SystemUtils.hashCode(getName(), getValue(), isDigit()); - } - - /** - * Returns true if the directive contains a digit value. - * - * @return True if the directive contains a digit value. - */ - public boolean isDigit() { - return digit; - } - - /** - * Indicates if the directive is a digit value. - * - * @param digit True if the directive contains a digit value. - */ - public void setDigit(boolean digit) { - this.digit = digit; - } - - /** - * Sets the name. - * - * @param name The name. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Sets the value. - * - * @param value The value. - */ - public void setValue(String value) { - this.value = value; - } - - @Override - public String toString() { - return "CacheDirective [digit=" + digit + ", name=" + name + ", value=" + value + "]"; - } + /** + * Creates a "max-age" directive. Indicates that the client is willing to accept a response + * whose age is not greater than the specified time in seconds. Unless the "max-stale" directive + * is also included, the client is not willing to accept a stale response.
+ *
+ * Note that this directive can be used on requests or responses. + * + * @param maxAge Maximum age in seconds. + * @return A new "max-age" directive. + * @see HTTP 1.1 - + * Modifications of the Basic Expiration Mechanism + * @see HTTP 1.1 - + * Cache Revalidation and Reload Controls + */ + public static CacheDirective maxAge(int maxAge) { + return new CacheDirective(HeaderConstants.CACHE_MAX_AGE, Integer.toString(maxAge), true); + } + + /** + * Creates a "max-stale" directive. Indicates that the client is willing to accept a response + * that has exceeded its expiration time by any amount of time.
+ *
+ * Note that this directive can be used on requests only. + * + * @return A new "max-stale" directive. + * @see HTTP 1.1 - + * Modifications of the Basic Expiration Mechanism + */ + public static CacheDirective maxStale() { + return new CacheDirective(HeaderConstants.CACHE_MAX_STALE); + } + + /** + * Creates a "max-stale" directive. Indicates that the client is willing to accept a response + * that has exceeded its expiration time by a given amount of time.
+ *
+ * Note that this directive can be used on requests only. + * + * @param maxStale Maximum stale age in seconds. + * @return A new "max-stale" directive. + * @see HTTP 1.1 - + * Modifications of the Basic Expiration Mechanism + */ + public static CacheDirective maxStale(int maxStale) { + return new CacheDirective( + HeaderConstants.CACHE_MAX_STALE, Integer.toString(maxStale), true); + } + + /** + * Creates a "min-fresh" directive. Indicates that the client is willing to accept a response + * whose freshness lifetime is no less than its current age plus the specified time in seconds. + * That is, the client wants a response that will still be fresh for at least the specified + * number of seconds.
+ *
+ * Note that this directive can be used on requests only. + * + * @param minFresh Minimum freshness lifetime in seconds. + * @return A new "min-fresh" directive. + * @see HTTP 1.1 - + * Modifications of the Basic Expiration Mechanism + */ + public static CacheDirective minFresh(int minFresh) { + return new CacheDirective( + HeaderConstants.CACHE_MIN_FRESH, Integer.toString(minFresh), true); + } + + /** + * Creates a "must-revalidate" directive. Indicates that the origin server requires revalidation + * of a cache entry on any later use.
+ *
+ * Note that this directive can be used on responses only. + * + * @return A new "must-revalidate" directive. + * @see HTTP 1.1 - + * Cache Revalidation and Reload Controls + */ + public static CacheDirective mustRevalidate() { + return new CacheDirective(HeaderConstants.CACHE_MUST_REVALIDATE); + } + + /** + * Creates a "no-cache" directive. Indicates that a cache must not use the response to satisfy + * later requests without successful revalidation with the origin server.
+ *
+ * Note that this directive can be used on requests or responses. + * + * @return A new "no-cache" directive. + * @see HTTP 1.1 - + * What is Cacheable + */ + public static CacheDirective noCache() { + return new CacheDirective(HeaderConstants.CACHE_NO_CACHE); + } + + /** + * Creates a "no-cache" directive. Indicates that a cache must not use the response to satisfy + * later requests without successful revalidation with the origin server.
+ *
+ * Note that this directive can be used on requests or responses. + * + * @param fieldNames Field names, typically an HTTP header name, that must not be sent by + * caches. + * @return A new "no-cache" directive. + * @see HTTP 1.1 - + * What is Cacheable + */ + public static CacheDirective noCache(List fieldNames) { + StringBuilder sb = new StringBuilder(); + + if (fieldNames != null) { + for (int i = 0; i < fieldNames.size(); i++) { + sb.append("\"").append(fieldNames.get(i)).append("\""); + + if (i < fieldNames.size() - 1) { + sb.append(','); + } + } + } + + return new CacheDirective(HeaderConstants.CACHE_NO_CACHE, sb.toString()); + } + + /** + * Creates a "no-cache" directive. Indicates that a cache must not use the response to satisfy + * later requests without successful revalidation with the origin server.
+ *
+ * Note that this directive can be used on requests or responses. + * + * @param fieldName A field name, typically an HTTP header name, that must not be sent by + * caches. + * @return A new "no-cache" directive. + * @see HTTP 1.1 - + * What is Cacheable + */ + public static CacheDirective noCache(String fieldName) { + return new CacheDirective(HeaderConstants.CACHE_NO_CACHE, "\"" + fieldName + "\""); + } + + /** + * Creates a "no-store" directive. Indicates that a cache must not release or retain any + * information about the call. This applies to both private and shared caches.
+ *
+ * Note that this directive can be used on requests or responses. + * + * @return A new "no-store" directive. + * @see HTTP 1.1 - + * What May be Stored by Caches + */ + public static CacheDirective noStore() { + return new CacheDirective(HeaderConstants.CACHE_NO_STORE); + } + + /** + * Creates a "no-transform" directive. Indicates that a cache or intermediary proxy must not + * transform the response entity.
+ *
+ * Note that this directive can be used on requests or responses. + * + * @return A new "no-transform" directive. + * @see HTTP 1.1 - + * No-Transform Directive + */ + public static CacheDirective noTransform() { + return new CacheDirective(HeaderConstants.CACHE_NO_TRANSFORM); + } + + /** + * Creates an "onlyIfCached" directive. Indicates that only cached responses should be returned + * to the client.
+ *
+ * Note that this directive can be used on requests only. + * + * @return A new "only-if-cached" directive. + * @see HTTP 1.1 - + * Cache Revalidation and Reload Controls + */ + public static CacheDirective onlyIfCached() { + return new CacheDirective(HeaderConstants.CACHE_ONLY_IF_CACHED); + } + + /** + * Creates a "private" directive. Indicates that all or part of the response message is intended + * for a single user and must not be cached by a shared cache.
+ *
+ * Note that this directive can be used on responses only. + * + * @return A new "private" directive. + * @see HTTP 1.1 - + * What is Cacheable + */ + public static CacheDirective privateInfo() { + return new CacheDirective(HeaderConstants.CACHE_PRIVATE); + } + + /** + * Creates a "private" directive. Indicates that all or part of the response message is intended + * for a single user and must not be cached by a shared cache.
+ *
+ * Note that this directive can be used on responses only. + * + * @param fieldNames Field names, typically an HTTP header name, that must be private. + * @return A new "private" directive. + * @see HTTP 1.1 - + * What is Cacheable + */ + public static CacheDirective privateInfo(List fieldNames) { + StringBuilder sb = new StringBuilder(); + + if (fieldNames != null) { + for (int i = 0; i < fieldNames.size(); i++) { + sb.append("\"").append(fieldNames.get(i)).append("\""); + + if (i < fieldNames.size() - 1) { + sb.append(','); + } + } + } + + return new CacheDirective(HeaderConstants.CACHE_PRIVATE, sb.toString()); + } + + /** + * Creates a "private" directive. Indicates that all or part of the response message is intended + * for a single user and must not be cached by a shared cache.
+ *
+ * Note that this directive can be used on responses only. + * + * @param fieldName A field name, typically an HTTP header name, that is private. + * @return A new "private" directive. + * @see HTTP 1.1 - + * What is Cacheable + */ + public static CacheDirective privateInfo(String fieldName) { + return new CacheDirective(HeaderConstants.CACHE_PRIVATE, "\"" + fieldName + "\""); + } + + /** + * Creates a "proxy-revalidate" directive. Indicates that the origin server requires + * revalidation of a cache entry on any later use, except that it does not apply to non-shared + * user agent caches
+ *
+ * Note that this directive can be used on responses only. + * + * @return A new "proxy-revalidate" directive. + * @see HTTP 1.1 - + * Cache Revalidation and Reload Controls + */ + public static CacheDirective proxyMustRevalidate() { + return new CacheDirective(HeaderConstants.CACHE_PROXY_MUST_REVALIDATE); + } + + /** + * Creates a "public" directive. Indicates that any cache may cache the response, even if it + * would normally be non-cacheable or cacheable only within a non-shared cache.
+ *
+ * Note that this directive can be used on responses only. + * + * @return A new "public" directive. + * @see HTTP 1.1 - + * What is Cacheable + */ + public static CacheDirective publicInfo() { + return new CacheDirective(HeaderConstants.CACHE_PUBLIC); + } + + /** + * Creates an "s-maxage" directive. Indicates that the client is willing to accept a response + * from a shared cache (but not a private cache) whose age is not greater than the specified + * time in seconds.
+ *
+ * Note that this directive can be used on responses only. + * + * @param sharedMaxAge Maximum age in seconds. + * @return A new "s-maxage" directive. + * @see HTTP 1.1 - + * Modifications of the Basic Expiration Mechanism + */ + public static CacheDirective sharedMaxAge(int sharedMaxAge) { + return new CacheDirective( + HeaderConstants.CACHE_SHARED_MAX_AGE, Integer.toString(sharedMaxAge), true); + } + + /** Indicates if the directive is a digit value. */ + private boolean digit; + + /** The name. */ + private volatile String name; + + /** The value. */ + private volatile String value; + + /** + * Constructor for directives with no value. + * + * @param name The directive name. + */ + public CacheDirective(String name) { + this(name, null); + } + + /** + * Constructor for directives with a value. + * + * @param name The directive name. + * @param value The directive value. + */ + public CacheDirective(String name, String value) { + this(name, value, false); + } + + /** + * Constructor for directives with a value. + * + * @param name The directive name. + * @param value The directive value. + * @param digit The kind of value (true for a digit value, false otherwise). + */ + public CacheDirective(String name, String value, boolean digit) { + this.name = name; + this.value = value; + this.digit = digit; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof CacheDirective that)) { + return false; + } + + return Objects.equals(getName(), that.getName()) + && Objects.equals(getValue(), that.getValue()) + && (this.digit == that.digit); + } + + /** + * Returns the name. + * + * @return The name. + */ + public String getName() { + return name; + } + + /** + * Returns the value. + * + * @return The value. + */ + public String getValue() { + return value; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return SystemUtils.hashCode(getName(), getValue(), isDigit()); + } + + /** + * Returns true if the directive contains a digit value. + * + * @return True if the directive contains a digit value. + */ + public boolean isDigit() { + return digit; + } + + /** + * Indicates if the directive is a digit value. + * + * @param digit True if the directive contains a digit value. + */ + public void setDigit(boolean digit) { + this.digit = digit; + } + + /** + * Sets the name. + * + * @param name The name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the value. + * + * @param value The value. + */ + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "CacheDirective [digit=" + digit + ", name=" + name + ", value=" + value + "]"; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/ChallengeMessage.java b/org.restlet/src/main/java/org/restlet/data/ChallengeMessage.java index 517d908b11..f93d25ad2e 100644 --- a/org.restlet/src/main/java/org/restlet/data/ChallengeMessage.java +++ b/org.restlet/src/main/java/org/restlet/data/ChallengeMessage.java @@ -1,275 +1,271 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.Objects; import org.restlet.engine.util.SystemUtils; import org.restlet.util.Series; -import java.util.Objects; - /** - * Base authentication challenge message exchanged between an origin server and - * a client. - * + * Base authentication challenge message exchanged between an origin server and a client. + * * @author Jerome Louvel */ public abstract class ChallengeMessage { - /** Authentication quality. */ - public static final String QUALITY_AUTHENTICATION = "auth"; - - /** Authentication and integrity. */ - public static final String QUALITY_AUTHENTICATION_INTEGRITY = "auth-int"; - - /** The raw value for custom challenge schemes. */ - private volatile String rawValue; - - /** The additional scheme parameters. */ - private volatile Series parameters; - - /** The challenge scheme. */ - private volatile ChallengeScheme scheme; - - /** The server nonce. */ - private volatile String serverNonce; - - /** The authentication realm. */ - private volatile String realm; - - /** - * An opaque string of data which should be returned by the client unchanged. - */ - private volatile String opaque; - - /** The digest algorithm. */ - private volatile String digestAlgorithm; - - /** - * Constructor. - * - * @param scheme The challenge scheme. - */ - public ChallengeMessage(ChallengeScheme scheme) { - this(scheme, null, null); - } - - /** - * Constructor. - * - * @param scheme The challenge scheme. - * @param parameters The additional scheme parameters. - */ - public ChallengeMessage(ChallengeScheme scheme, Series parameters) { - this(scheme, null, null); - } - - /** - * Constructor. - * - * @param scheme The challenge scheme. - * @param realm The authentication realm. - */ - public ChallengeMessage(ChallengeScheme scheme, String realm) { - this(scheme, realm, null); - } - - /** - * Constructor. - * - * @param scheme The challenge scheme. - * @param realm The authentication realm. - * @param parameters The additional scheme parameters. - */ - public ChallengeMessage(ChallengeScheme scheme, String realm, Series parameters) { - this(scheme, realm, parameters, Digest.ALGORITHM_MD5, null, null); - } - - /** - * Constructor. - * - * @param scheme The challenge scheme. - * @param realm The authentication realm. - * @param parameters The additional scheme parameters. - * @param digestAlgorithm The digest algorithm. - * @param opaque An opaque string of data which should be returned by - * the client unchanged. - * @param serverNonce The server nonce. - */ - public ChallengeMessage(ChallengeScheme scheme, String realm, Series parameters, String digestAlgorithm, - String opaque, String serverNonce) { - super(); - this.parameters = parameters; - this.scheme = scheme; - this.serverNonce = serverNonce; - this.realm = realm; - this.opaque = opaque; - this.digestAlgorithm = digestAlgorithm; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof ChallengeMessage)) { - return false; - } - - final ChallengeMessage that = (ChallengeMessage) obj; - - return getParameters().equals(that.getParameters()) && Objects.equals(getRealm(), that.getRealm()) - && Objects.equals(getScheme(), that.getScheme()) - && Objects.equals(getServerNonce(), that.getServerNonce()) - && Objects.equals(getOpaque(), that.getOpaque()) - && Objects.equals(getDigestAlgorithm(), that.getDigestAlgorithm()); - } - - /** - * Returns the digest algorithm. See {@link Digest} class for DIGEST_* - * constants. Default value is {@link Digest#ALGORITHM_MD5}. - * - * @return The digest algorithm. - */ - public String getDigestAlgorithm() { - return digestAlgorithm; - } - - /** - * Returns an opaque string of data which should be returned by the client - * unchanged. - * - * @return An opaque string of data. - */ - public String getOpaque() { - return opaque; - } - - /** - * Returns the modifiable series of scheme parameters. Creates a new instance if - * no one has been set. - * - * @return The modifiable series of scheme parameters. - */ - public Series getParameters() { - if (this.parameters == null) { - this.parameters = new Series(Parameter.class); - } - - return this.parameters; - } - - /** - * Returns the raw challenge value. - * - * @return The raw challenge value. - */ - public String getRawValue() { - return this.rawValue; - } - - /** - * Returns the realm name. - * - * @return The realm name. - */ - public String getRealm() { - return this.realm; - } - - /** - * Returns the scheme used. - * - * @return The scheme used. - */ - public ChallengeScheme getScheme() { - return this.scheme; - } - - /** - * Returns the server nonce. - * - * @return The server nonce. - */ - public String getServerNonce() { - return serverNonce; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return SystemUtils.hashCode(getScheme(), getRealm(), getParameters()); - } - - /** - * Sets the digest algorithm. See {@link Digest} class for ALGORITHM_* - * constants. Default value is {@link Digest#ALGORITHM_MD5}. - * - * @param digestAlgorithm The digest algorithm. - */ - public void setDigestAlgorithm(String digestAlgorithm) { - this.digestAlgorithm = digestAlgorithm; - } - - /** - * Sets an opaque string of data which should be returned by the client - * unchanged. - * - * @param opaque An opaque string of data. - */ - public void setOpaque(String opaque) { - this.opaque = opaque; - } - - /** - * Sets the parameters. - * - * @param parameters The parameters. - */ - public void setParameters(Series parameters) { - this.parameters = parameters; - } - - /** - * Sets the raw value. - * - * @param rawValue The raw value. - */ - public void setRawValue(String rawValue) { - this.rawValue = rawValue; - } - - /** - * Sets the realm name. - * - * @param realm The realm name. - */ - public void setRealm(String realm) { - this.realm = realm; - } - - /** - * Sets the scheme used. - * - * @param scheme The scheme used. - */ - public void setScheme(ChallengeScheme scheme) { - this.scheme = scheme; - } - - /** - * Sets the server nonce. - * - * @param serverNonce The server nonce. - */ - public void setServerNonce(String serverNonce) { - this.serverNonce = serverNonce; - } - + /** Authentication quality. */ + public static final String QUALITY_AUTHENTICATION = "auth"; + + /** Authentication and integrity. */ + public static final String QUALITY_AUTHENTICATION_INTEGRITY = "auth-int"; + + /** The raw value for custom challenge schemes. */ + private volatile String rawValue; + + /** The additional scheme parameters. */ + private volatile Series parameters; + + /** The challenge scheme. */ + private volatile ChallengeScheme scheme; + + /** The server nonce. */ + private volatile String serverNonce; + + /** The authentication realm. */ + private volatile String realm; + + /** An opaque string of data which should be returned by the client unchanged. */ + private volatile String opaque; + + /** The digest algorithm. */ + private volatile String digestAlgorithm; + + /** + * Constructor. + * + * @param scheme The challenge scheme. + */ + protected ChallengeMessage(ChallengeScheme scheme) { + this(scheme, null, null); + } + + /** + * Constructor. + * + * @param scheme The challenge scheme. + * @param parameters The additional scheme parameters. + */ + protected ChallengeMessage(ChallengeScheme scheme, Series parameters) { + this(scheme, null, null); + } + + /** + * Constructor. + * + * @param scheme The challenge scheme. + * @param realm The authentication realm. + */ + protected ChallengeMessage(ChallengeScheme scheme, String realm) { + this(scheme, realm, null); + } + + /** + * Constructor. + * + * @param scheme The challenge scheme. + * @param realm The authentication realm. + * @param parameters The additional scheme parameters. + */ + protected ChallengeMessage(ChallengeScheme scheme, String realm, Series parameters) { + this(scheme, realm, parameters, Digest.ALGORITHM_MD5, null, null); + } + + /** + * Constructor. + * + * @param scheme The challenge scheme. + * @param realm The authentication realm. + * @param parameters The additional scheme parameters. + * @param digestAlgorithm The digest algorithm. + * @param opaque An opaque string of data which should be returned by the client unchanged. + * @param serverNonce The server nonce. + */ + protected ChallengeMessage( + ChallengeScheme scheme, + String realm, + Series parameters, + String digestAlgorithm, + String opaque, + String serverNonce) { + super(); + this.parameters = parameters; + this.scheme = scheme; + this.serverNonce = serverNonce; + this.realm = realm; + this.opaque = opaque; + this.digestAlgorithm = digestAlgorithm; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof final ChallengeMessage that)) { + return false; + } + + return Objects.equals(getParameters(), that.getParameters()) + && Objects.equals(getRealm(), that.getRealm()) + && Objects.equals(getScheme(), that.getScheme()) + && Objects.equals(getServerNonce(), that.getServerNonce()) + && Objects.equals(getOpaque(), that.getOpaque()) + && Objects.equals(getDigestAlgorithm(), that.getDigestAlgorithm()); + } + + /** + * Returns the digest algorithm. See {@link Digest} class for DIGEST_* constants. Default value + * is {@link Digest#ALGORITHM_MD5}. + * + * @return The digest algorithm. + */ + public String getDigestAlgorithm() { + return digestAlgorithm; + } + + /** + * Returns an opaque string of data which should be returned by the client unchanged. + * + * @return An opaque string of data. + */ + public String getOpaque() { + return opaque; + } + + /** + * Returns the modifiable series of scheme parameters. Creates a new instance if no one has been + * set. + * + * @return The modifiable series of scheme parameters. + */ + public Series getParameters() { + if (this.parameters == null) { + this.parameters = new Series<>(Parameter.class); + } + + return this.parameters; + } + + /** + * Returns the raw challenge value. + * + * @return The raw challenge value. + */ + public String getRawValue() { + return this.rawValue; + } + + /** + * Returns the realm name. + * + * @return The realm name. + */ + public String getRealm() { + return this.realm; + } + + /** + * Returns the scheme used. + * + * @return The scheme used. + */ + public ChallengeScheme getScheme() { + return this.scheme; + } + + /** + * Returns the server nonce. + * + * @return The server nonce. + */ + public String getServerNonce() { + return serverNonce; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return SystemUtils.hashCode(getScheme(), getRealm(), getParameters()); + } + + /** + * Sets the digest algorithm. See {@link Digest} class for ALGORITHM_* constants. Default value + * is {@link Digest#ALGORITHM_MD5}. + * + * @param digestAlgorithm The digest algorithm. + */ + public void setDigestAlgorithm(String digestAlgorithm) { + this.digestAlgorithm = digestAlgorithm; + } + + /** + * Sets an opaque string of data which should be returned by the client unchanged. + * + * @param opaque An opaque string of data. + */ + public void setOpaque(String opaque) { + this.opaque = opaque; + } + + /** + * Sets the parameters. + * + * @param parameters The parameters. + */ + public void setParameters(Series parameters) { + this.parameters = parameters; + } + + /** + * Sets the raw value. + * + * @param rawValue The raw value. + */ + public void setRawValue(String rawValue) { + this.rawValue = rawValue; + } + + /** + * Sets the realm name. + * + * @param realm The realm name. + */ + public void setRealm(String realm) { + this.realm = realm; + } + + /** + * Sets the scheme used. + * + * @param scheme The scheme used. + */ + public void setScheme(ChallengeScheme scheme) { + this.scheme = scheme; + } + + /** + * Sets the server nonce. + * + * @param serverNonce The server nonce. + */ + public void setServerNonce(String serverNonce) { + this.serverNonce = serverNonce; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/ChallengeRequest.java b/org.restlet/src/main/java/org/restlet/data/ChallengeRequest.java index 0185a2a026..5c1f2a9ab1 100644 --- a/org.restlet/src/main/java/org/restlet/data/ChallengeRequest.java +++ b/org.restlet/src/main/java/org/restlet/data/ChallengeRequest.java @@ -1,185 +1,178 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.restlet.engine.util.SystemUtils; - import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; +import org.restlet.engine.util.SystemUtils; /** - * Authentication challenge sent by an origin server to a client. Upon reception - * of this request, the client should send a new request with the proper - * {@link ChallengeResponse} set.
+ * Authentication challenge sent by an origin server to a client. Upon reception of this request, + * the client should send a new request with the proper {@link ChallengeResponse} set.
*
- * Note that when used with HTTP connectors, this class maps to the - * "WWW-Authenticate" header. - * + * Note that when used with HTTP connectors, this class maps to the "WWW-Authenticate" header. + * * @author Jerome Louvel */ public final class ChallengeRequest extends ChallengeMessage { - /** The available options for quality of protection. */ - private volatile List qualityOptions; - - /** The URI references that define the protection domains. */ - private volatile List domainRefs; - - /** Indicates if the previous request from the client was stale. */ - private volatile boolean stale; - - /** - * Constructor. - * - * @param scheme The challenge scheme. - */ - public ChallengeRequest(ChallengeScheme scheme) { - this(scheme, null); - } - - /** - * Constructor. - * - * @param scheme The challenge scheme. - * @param realm The authentication realm. - */ - public ChallengeRequest(ChallengeScheme scheme, String realm) { - super(scheme, realm); - this.domainRefs = null; - this.qualityOptions = null; - this.stale = false; - } - - /** {@inheritDoc} */ - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof ChallengeRequest that)) { - return false; - } - - return getParameters().equals(that.getParameters()) - && Objects.equals(getRealm(), that.getRealm()) - && Objects.equals(getScheme(), that.getScheme()); - } - - /** - * Returns the base URI references that collectively define the protected - * domains for the digest authentication. By default, it returns a list with a - * single "/" URI reference. - * - * @return The base URI references. - */ - public List getDomainRefs() { - // Lazy initialization with double-check. - List r = this.domainRefs; - if (r == null) { - synchronized (this) { - r = this.domainRefs; - if (r == null) { - this.domainRefs = r = new CopyOnWriteArrayList(); - this.domainRefs.add(new Reference("/")); - } - } - } - return r; - } - - /** - * Returns the available options for quality of protection. The default value is - * {@link #QUALITY_AUTHENTICATION}. - * - * @return The available options for quality of protection. - */ - public List getQualityOptions() { - // Lazy initialization with double-check. - List r = this.qualityOptions; - if (r == null) { - synchronized (this) { - r = this.qualityOptions; - if (r == null) { - this.qualityOptions = r = new CopyOnWriteArrayList(); - this.qualityOptions.add(QUALITY_AUTHENTICATION); - } - } - } - return r; - } - - @Override - public int hashCode() { - return SystemUtils.hashCode(super.hashCode(), qualityOptions, domainRefs, stale); - } - - /** - * Indicates if the previous request from the client was stale. - * - * @return True if the previous request from the client was stale. - */ - public boolean isStale() { - return stale; - } - - /** - * Sets the URI references that define the protection domains for the digest - * authentication. - * - * @param domainRefs The base URI references. - */ - public void setDomainRefs(List domainRefs) { - this.domainRefs = domainRefs; - } - - /** - * Sets the URI references that define the protection domains for the digest - * authentication. Note that the parameters are copied into a new - * {@link CopyOnWriteArrayList} instance. - * - * @param domainUris The base URI references. - * @see #setDomainRefs(List) - */ - public void setDomainUris(Collection domainUris) { - List domainRefs = null; - - if (domainUris != null) { - domainRefs = new CopyOnWriteArrayList(); - - for (String domainUri : domainUris) { - domainRefs.add(new Reference(domainUri)); - } - } - - setDomainRefs(domainRefs); - } - - /** - * Sets the available options for quality of protection. The default value is - * {@link #QUALITY_AUTHENTICATION}. - * - * @param qualityOptions The available options for quality of protection. - */ - public void setQualityOptions(List qualityOptions) { - this.qualityOptions = qualityOptions; - } - - /** - * Indicates if the previous request from the client was stale. - * - * @param stale True if the previous request from the client was stale. - */ - public void setStale(boolean stale) { - this.stale = stale; - } - + /** The available options for quality of protection. */ + private volatile List qualityOptions; + + /** The URI references that define the protection domains. */ + private volatile List domainRefs; + + /** Indicates if the previous request from the client was stale. */ + private volatile boolean stale; + + /** + * Constructor. + * + * @param scheme The challenge scheme. + */ + public ChallengeRequest(ChallengeScheme scheme) { + this(scheme, null); + } + + /** + * Constructor. + * + * @param scheme The challenge scheme. + * @param realm The authentication realm. + */ + public ChallengeRequest(ChallengeScheme scheme, String realm) { + super(scheme, realm); + this.domainRefs = null; + this.qualityOptions = null; + this.stale = false; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ChallengeRequest that)) { + return false; + } + + return super.equals(that) + && Objects.equals(getParameters(), that.getParameters()) + && Objects.equals(getRealm(), that.getRealm()) + && Objects.equals(getScheme(), that.getScheme()); + } + + /** + * Returns the base URI references that collectively define the protected domains for the digest + * authentication. By default, it returns a list with a single "/" URI reference. + * + * @return The base URI references. + */ + public List getDomainRefs() { + // Lazy initialization with double-check. + List r = this.domainRefs; + if (r == null) { + synchronized (this) { + r = this.domainRefs; + if (r == null) { + this.domainRefs = r = new CopyOnWriteArrayList<>(); + this.domainRefs.add(new Reference("/")); + } + } + } + return r; + } + + /** + * Returns the available options for quality of protection. The default value is {@link + * #QUALITY_AUTHENTICATION}. + * + * @return The available options for quality of protection. + */ + public List getQualityOptions() { + // Lazy initialization with double-check. + List r = this.qualityOptions; + if (r == null) { + synchronized (this) { + r = this.qualityOptions; + if (r == null) { + this.qualityOptions = r = new CopyOnWriteArrayList<>(); + this.qualityOptions.add(QUALITY_AUTHENTICATION); + } + } + } + return r; + } + + @Override + public int hashCode() { + return SystemUtils.hashCode(super.hashCode(), qualityOptions, domainRefs, stale); + } + + /** + * Indicates if the previous request from the client was stale. + * + * @return True if the previous request from the client was stale. + */ + public boolean isStale() { + return stale; + } + + /** + * Sets the URI references that define the protection domains for the digest authentication. + * + * @param domainRefs The base URI references. + */ + public void setDomainRefs(List domainRefs) { + this.domainRefs = domainRefs; + } + + /** + * Sets the URI references that define the protection domains for the digest authentication. + * Note that the parameters are copied into a new {@link CopyOnWriteArrayList} instance. + * + * @param domainUris The base URI references. + * @see #setDomainRefs(List) + */ + public void setDomainUris(Collection domainUris) { + List newDomainRefs = null; + + if (domainUris != null) { + newDomainRefs = new CopyOnWriteArrayList<>(); + + for (String domainUri : domainUris) { + newDomainRefs.add(new Reference(domainUri)); + } + } + + setDomainRefs(newDomainRefs); + } + + /** + * Sets the available options for quality of protection. The default value is {@link + * #QUALITY_AUTHENTICATION}. + * + * @param qualityOptions The available options for quality of protection. + */ + public void setQualityOptions(List qualityOptions) { + this.qualityOptions = qualityOptions; + } + + /** + * Indicates if the previous request from the client was stale. + * + * @param stale True if the previous request from the client was stale. + */ + public void setStale(boolean stale) { + this.stale = stale; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/ChallengeResponse.java b/org.restlet/src/main/java/org/restlet/data/ChallengeResponse.java index d5ac4de161..d19a4afae5 100644 --- a/org.restlet/src/main/java/org/restlet/data/ChallengeResponse.java +++ b/org.restlet/src/main/java/org/restlet/data/ChallengeResponse.java @@ -1,413 +1,442 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.Arrays; +import java.util.Objects; import org.restlet.Request; import org.restlet.Response; import org.restlet.engine.util.SystemUtils; import org.restlet.util.Series; -import java.util.Arrays; -import java.util.Objects; - /** - * Authentication response sent by client to an origin server. This is typically - * following a {@link ChallengeRequest} sent by the origin server to the - * client.
+ * Authentication response sent by client to an origin server. This is typically following a {@link + * ChallengeRequest} sent by the origin server to the client.
*
- * Sometimes, it might be faster to preemptively issue a challenge response if - * the client knows for sure that the target resource will require - * authentication.
+ * Sometimes, it might be faster to preemptively issue a challenge response if the client knows for + * sure that the target resource will require authentication.
*
- * Note that when used with HTTP connectors, this class maps to the - * "Authorization" header. - * + * Note that when used with HTTP connectors, this class maps to the "Authorization" header. + * * @author Jerome Louvel */ public final class ChallengeResponse extends ChallengeMessage { - /** The client nonce value. */ - private volatile String clientNonce; - - /** - * The {@link Request#getResourceRef()} value duplicated here in case a proxy - * changed it. - */ - private volatile Reference digestRef; - - /** The user identifier, such as a login name or an access key. */ - private volatile String identifier; - - /** The chosen quality of protection. */ - private volatile String quality; - - /** The user secret, such as a password or a secret key. */ - private volatile char[] secret; - - /** The digest algorithm name optionally applied on the user secret. */ - private volatile String secretAlgorithm; - - /** The server nonce count. */ - private volatile int serverNonceCount; - - /** - * The time when the response was issued, as returned by - * {@link System#currentTimeMillis()}. - */ - private volatile long timeIssued; - - /** - * Constructor. It leverages the latest server response and challenge request - * to compute the credentials. - * - * @param challengeRequest The challenge request sent by the origin server. - * @param response The latest server response. - * @param identifier The user identifier, such as a login name or an - * access key. - * @param secret The user secret, such as a password or a secret key, - * with no digest applied. - */ - public ChallengeResponse(ChallengeRequest challengeRequest, Response response, String identifier, char[] secret) { - this(challengeRequest, response, identifier, secret, Digest.ALGORITHM_NONE); - } - - /** - * Constructor. It leverages the latest server response and challenge request in - * order to compute the credentials. - * - * @param challengeRequest The challenge request sent by the origin server. - * @param response The latest server response. - * @param identifier The user identifier, such as a login name or an - * access key. - * @param secret The user secret used to compute the secret, with an - * optional digest applied. - * @param secretAlgorithm The digest algorithm of the user secret (see - * {@link Digest} class). - */ - public ChallengeResponse(ChallengeRequest challengeRequest, Response response, String identifier, char[] secret, - String secretAlgorithm) { - this(challengeRequest.getScheme(), null, identifier, secret, secretAlgorithm, null, null, null, null, null, - null, null, 0, 0L); - org.restlet.engine.security.AuthenticatorUtils.update(this, response.getRequest(), response); - } - - /** - * Constructor. It leverages the latest server response and challenge request - * to compute the credentials. - * - * @param challengeRequest The challenge request sent by the origin server. - * @param response The latest server response. - * @param identifier The user identifier, such as a login name or an - * access key. - * @param secret The user secret, such as a password or a secret key. - */ - public ChallengeResponse(ChallengeRequest challengeRequest, Response response, String identifier, String secret) { - this(challengeRequest, response, identifier, secret.toCharArray(), Digest.ALGORITHM_NONE); - } - - /** - * Constructor with no credentials. - * - * @param scheme The challenge scheme. - */ - public ChallengeResponse(ChallengeScheme scheme) { - this(scheme, null, (char[]) null); - } - - /** - * Constructor. - * - * @param scheme The challenge scheme. - * @param parameters The additional scheme parameters. - * @param identifier The user identifier, such as a login name or an - * access key. - * @param secret The user secret, such as a password or a secret key. - * @param secretAlgorithm The digest algorithm name optionally applied on the - * user secret. - * @param realm The authentication realm. - * @param quality The chosen quality of protection. - * @param digestRef The {@link Request#getResourceRef()} value - * duplicated here in case a proxy changed it. - * @param digestAlgorithm The digest algorithm. - * @param opaque An opaque string of data which should be returned by - * the client unchanged. - * @param clientNonce The client nonce value. - * @param serverNonce The server nonce. - * @param serverNonceCount The server nonce count. - * @param timeIssued The time when the response was issued, as returned - * by {@link System#currentTimeMillis()}. - */ - public ChallengeResponse(ChallengeScheme scheme, Series parameters, String identifier, char[] secret, - String secretAlgorithm, String realm, String quality, Reference digestRef, String digestAlgorithm, - String opaque, String clientNonce, String serverNonce, int serverNonceCount, long timeIssued) { - super(scheme, realm, parameters, digestAlgorithm, opaque, serverNonce); - this.clientNonce = clientNonce; - this.digestRef = digestRef; - this.identifier = identifier; - this.quality = quality; - this.secret = secret; - this.secretAlgorithm = secretAlgorithm; - this.serverNonceCount = serverNonceCount; - this.timeIssued = timeIssued; - } - - /** - * Constructor. - * - * @param scheme The challenge scheme. - * @param identifier The user identifier, such as a login name or an access key. - * @param secret The user secret, such as a password or a secret key. - */ - public ChallengeResponse(ChallengeScheme scheme, String identifier, char[] secret) { - this(scheme, identifier, secret, null); - } - - /** - * Constructor. - * - * @param scheme The challenge scheme. - * @param identifier The user identifier, such as a login name or an access key. - * @param parameters The additional scheme parameters. - */ - public ChallengeResponse(ChallengeScheme scheme, String identifier, char[] secret, Series parameters) { - this(scheme, parameters, identifier, secret, Digest.ALGORITHM_NONE, null, null, null, null, null, null, null, 0, - 0L); - } - - /** - * Constructor. - * - * @param scheme The challenge scheme. - * @param identifier The user identifier, such as a login name or an access key. - * @param parameters The additional scheme parameters. - */ - public ChallengeResponse(ChallengeScheme scheme, String identifier, Series parameters) { - this(scheme, identifier, null, parameters); - } - - /** - * Constructor. - * - * @param scheme The challenge scheme. - * @param identifier The user identifier, such as a login name or an access key. - * @param secret The user secret, such as a password or a secret key. - */ - public ChallengeResponse(ChallengeScheme scheme, String identifier, String secret) { - this(scheme, identifier, (secret != null) ? secret.toCharArray() : null); - } - - /** {@inheritDoc} */ - @Override - public boolean equals(Object obj) { - // if obj == this no need to go further - if (obj == this) { - return true; - } - - // if obj isn't a challenge request or is null don't evaluate further - if (!(obj instanceof ChallengeResponse that)) { - return false; - } - - if (!Objects.equals(getRawValue(), that.getRawValue()) - || !Objects.equals(getIdentifier(), that.getIdentifier()) - || !Objects.equals(getScheme(), that.getScheme())) { - return false; - } - - return Arrays.equals(getSecret(), that.getSecret()); - } - - /** - * Returns the client nonce. - * - * @return The client nonce. - */ - public String getClientNonce() { - return this.clientNonce; - } - - /** - * Returns the {@link Request#getResourceRef()} value duplicated here in case a - * proxy changed it. - * - * @return The digest URI reference. - */ - public Reference getDigestRef() { - return digestRef; - } - - /** - * Returns the user identifier, such as a login name or an access key. - * - * @return The user identifier, such as a login name or an access key. - */ - public String getIdentifier() { - return this.identifier; - } - - /** - * Gets the principal associated to the identifier property. - * - * @return The principal associated to the identifier property. - */ - public java.security.Principal getPrincipal() { - return this::getIdentifier; - } - - /** - * Returns the chosen quality of protection. - * - * @return The chosen quality of protection. - */ - public String getQuality() { - return quality; - } - - /** - * Returns the user secret, such as a password or a secret key. - * - * It is not recommended to use {@link String#String(char[])} for security - * reasons. - * - * @return The user secret, such as a password or a secret key. - */ - public char[] getSecret() { - return this.secret; - } - - /** - * Returns the digest algorithm name optionally applied on the user secret. - * - * @return The digest algorithm name optionally applied on the user secret. - */ - public String getSecretAlgorithm() { - return secretAlgorithm; - } - - /** - * Returns the server nonce count. - * - * @return The server nonce count. - */ - public int getServerNonceCount() { - return serverNonceCount; - } - - /** - * Returns the server nonce count as a hexadecimal string of eight characters. - * - * @return The server nonce count as a hexadecimal string. - */ - public String getServerNonceCountAsHex() { - return org.restlet.engine.security.AuthenticatorUtils.formatNonceCount(getServerNonceCount()); - } - - /** - * Returns the time when the response was issued, as returned by - * {@link System#currentTimeMillis()}. - * - * @return The time when the response was issued. - */ - public long getTimeIssued() { - return timeIssued; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - // Note that the secret is simply discarded from hash code calculation - // because we don't want it to be materialized as a string - return SystemUtils.hashCode(getScheme(), getIdentifier(), getRawValue()); - } - - /** - * Sets the client nonce. - * - * @param clientNonce The client nonce. - */ - public void setClientNonce(String clientNonce) { - this.clientNonce = clientNonce; - } - - /** - * Sets the digest URI reference. - * - * @param digestRef The digest URI reference. - */ - public void setDigestRef(Reference digestRef) { - this.digestRef = digestRef; - } - - /** - * Sets the user identifier, such as a login name or an access key. - * - * @param identifier The user identifier, such as a login name or an access key. - */ - public void setIdentifier(String identifier) { - this.identifier = identifier; - } - - /** - * Sets the chosen quality of protection. - * - * @param quality The chosen quality of protection. - */ - public void setQuality(String quality) { - this.quality = quality; - } - - /** - * Sets the user secret, such as a password or a secret key. - * - * @param secret The user secret, such as a password or a secret key. - */ - public void setSecret(char[] secret) { - this.secret = secret; - } - - /** - * Sets the user secret, such as a password or a secret key. - * - * @param secret The user secret, such as a password or a secret key. - */ - public void setSecret(String secret) { - this.secret = (secret == null) ? null : secret.toCharArray(); - } - - /** - * Sets the digest algorithm name optionally applied on the user secret. - * - * @param secretDigestAlgorithm The digest algorithm name optionally applied on - * the user secret. - */ - public void setSecretAlgorithm(String secretDigestAlgorithm) { - this.secretAlgorithm = secretDigestAlgorithm; - } - - /** - * Sets the server nonce count. - * - * @param serverNonceCount The server nonce count. - */ - public void setServerNonceCount(int serverNonceCount) { - this.serverNonceCount = serverNonceCount; - } - - /** - * Sets the time when the response was issued, as returned by - * {@link System#currentTimeMillis()}. - * - * @param timeIssued The time when the response was issued. - */ - public void setTimeIssued(long timeIssued) { - this.timeIssued = timeIssued; - } + /** The client nonce value. */ + private volatile String clientNonce; + + /** The {@link Request#getResourceRef()} value duplicated here in case a proxy changed it. */ + private volatile Reference digestRef; + + /** The user identifier, such as a login name or an access key. */ + private volatile String identifier; + + /** The chosen quality of protection. */ + private volatile String quality; + + /** The user secret, such as a password or a secret key. */ + private volatile char[] secret; + + /** The digest algorithm name optionally applied on the user secret. */ + private volatile String secretAlgorithm; + + /** The server nonce count. */ + private volatile int serverNonceCount; + + /** The time when the response was issued, as returned by {@link System#currentTimeMillis()}. */ + private volatile long timeIssued; + + /** + * Constructor. It leverages the latest server response and challenge request to compute the + * credentials. + * + * @param challengeRequest The challenge request sent by the origin server. + * @param response The latest server response. + * @param identifier The user identifier, such as a login name or an access key. + * @param secret The user secret, such as a password or a secret key, with no digest applied. + */ + public ChallengeResponse( + ChallengeRequest challengeRequest, + Response response, + String identifier, + char[] secret) { + this(challengeRequest, response, identifier, secret, Digest.ALGORITHM_NONE); + } + + /** + * Constructor. It leverages the latest server response and challenge request to compute the + * credentials. + * + * @param challengeRequest The challenge request sent by the origin server. + * @param response The latest server response. + * @param identifier The user identifier, such as a login name or an access key. + * @param secret The user secret used to compute the secret, with an optional digest applied. + * @param secretAlgorithm The digest algorithm of the user secret (see {@link Digest} class). + */ + public ChallengeResponse( + ChallengeRequest challengeRequest, + Response response, + String identifier, + char[] secret, + String secretAlgorithm) { + this( + challengeRequest.getScheme(), + null, + identifier, + secret, + secretAlgorithm, + null, + null, + null, + null, + null, + null, + null, + 0, + 0L); + org.restlet.engine.security.AuthenticatorUtils.update( + this, response.getRequest(), response); + } + + /** + * Constructor. It leverages the latest server response and challenge request to compute the + * credentials. + * + * @param challengeRequest The challenge request sent by the origin server. + * @param response The latest server response. + * @param identifier The user identifier, such as a login name or an access key. + * @param secret The user secret, such as a password or a secret key. + */ + public ChallengeResponse( + ChallengeRequest challengeRequest, + Response response, + String identifier, + String secret) { + this(challengeRequest, response, identifier, secret.toCharArray(), Digest.ALGORITHM_NONE); + } + + /** + * Constructor with no credentials. + * + * @param scheme The challenge scheme. + */ + public ChallengeResponse(ChallengeScheme scheme) { + this(scheme, null, (char[]) null); + } + + /** + * Constructor. + * + * @param scheme The challenge scheme. + * @param parameters The additional scheme parameters. + * @param identifier The user identifier, such as a login name or an access key. + * @param secret The user secret, such as a password or a secret key. + * @param secretAlgorithm The digest algorithm name optionally applied on the user secret. + * @param realm The authentication realm. + * @param quality The chosen quality of protection. + * @param digestRef The {@link Request#getResourceRef()} value duplicated here in case a proxy + * changed it. + * @param digestAlgorithm The digest algorithm. + * @param opaque An opaque string of data which should be returned by the client unchanged. + * @param clientNonce The client nonce value. + * @param serverNonce The server nonce. + * @param serverNonceCount The server nonce count. + * @param timeIssued The time when the response was issued, as returned by {@link + * System#currentTimeMillis()}. + */ + public ChallengeResponse( + ChallengeScheme scheme, + Series parameters, + String identifier, + char[] secret, + String secretAlgorithm, + String realm, + String quality, + Reference digestRef, + String digestAlgorithm, + String opaque, + String clientNonce, + String serverNonce, + int serverNonceCount, + long timeIssued) { + super(scheme, realm, parameters, digestAlgorithm, opaque, serverNonce); + this.clientNonce = clientNonce; + this.digestRef = digestRef; + this.identifier = identifier; + this.quality = quality; + this.secret = secret; + this.secretAlgorithm = secretAlgorithm; + this.serverNonceCount = serverNonceCount; + this.timeIssued = timeIssued; + } + + /** + * Constructor. + * + * @param scheme The challenge scheme. + * @param identifier The user identifier, such as a login name or an access key. + * @param secret The user secret, such as a password or a secret key. + */ + public ChallengeResponse(ChallengeScheme scheme, String identifier, char[] secret) { + this(scheme, identifier, secret, null); + } + + /** + * Constructor. + * + * @param scheme The challenge scheme. + * @param identifier The user identifier, such as a login name or an access key. + * @param parameters The additional scheme parameters. + */ + public ChallengeResponse( + ChallengeScheme scheme, + String identifier, + char[] secret, + Series parameters) { + this( + scheme, + parameters, + identifier, + secret, + Digest.ALGORITHM_NONE, + null, + null, + null, + null, + null, + null, + null, + 0, + 0L); + } + + /** + * Constructor. + * + * @param scheme The challenge scheme. + * @param identifier The user identifier, such as a login name or an access key. + * @param parameters The additional scheme parameters. + */ + public ChallengeResponse( + ChallengeScheme scheme, String identifier, Series parameters) { + this(scheme, identifier, null, parameters); + } + + /** + * Constructor. + * + * @param scheme The challenge scheme. + * @param identifier The user identifier, such as a login name or an access key. + * @param secret The user secret, such as a password or a secret key. + */ + public ChallengeResponse(ChallengeScheme scheme, String identifier, String secret) { + this(scheme, identifier, (secret != null) ? secret.toCharArray() : null); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ChallengeResponse that)) { + return false; + } + return super.equals(that) + && Objects.equals(getRawValue(), that.getRawValue()) + && Objects.equals(getIdentifier(), that.getIdentifier()) + && Objects.equals(getScheme(), that.getScheme()) + && Arrays.equals(getSecret(), that.getSecret()); + } + + /** + * Returns the client nonce. + * + * @return The client nonce. + */ + public String getClientNonce() { + return this.clientNonce; + } + + /** + * Returns the {@link Request#getResourceRef()} value duplicated here in case a proxy changed + * it. + * + * @return The digest URI reference. + */ + public Reference getDigestRef() { + return digestRef; + } + + /** + * Returns the user identifier, such as a login name or an access key. + * + * @return The user identifier, such as a login name or an access key. + */ + public String getIdentifier() { + return this.identifier; + } + + /** + * Gets the principal associated with the identifier property. + * + * @return The principal associated with the identifier property. + */ + public java.security.Principal getPrincipal() { + return this::getIdentifier; + } + + /** + * Returns the chosen quality of protection. + * + * @return The chosen quality of protection. + */ + public String getQuality() { + return quality; + } + + /** + * Returns the user secret, such as a password or a secret key. + * + *

It is not recommended to use {@link String#String(char[])} for security reasons. + * + * @return The user secret, such as a password or a secret key. + */ + public char[] getSecret() { + return this.secret; + } + + /** + * Returns the digest algorithm name optionally applied on the user secret. + * + * @return The digest algorithm name optionally applied on the user secret. + */ + public String getSecretAlgorithm() { + return secretAlgorithm; + } + + /** + * Returns the server nonce count. + * + * @return The server nonce count. + */ + public int getServerNonceCount() { + return serverNonceCount; + } + + /** + * Returns the server nonce count as a hexadecimal string of eight characters. + * + * @return The server nonce count as a hexadecimal string. + */ + public String getServerNonceCountAsHex() { + return org.restlet.engine.security.AuthenticatorUtils.formatNonceCount( + getServerNonceCount()); + } + + /** + * Returns the time when the response was issued, as returned by {@link + * System#currentTimeMillis()}. + * + * @return The time when the response was issued. + */ + public long getTimeIssued() { + return timeIssued; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + // Note that the secret is simply discarded from hash code calculation + // because we don't want it to be materialized as a string + return SystemUtils.hashCode(getScheme(), getIdentifier(), getRawValue()); + } + + /** + * Sets the client nonce. + * + * @param clientNonce The client nonce. + */ + public void setClientNonce(String clientNonce) { + this.clientNonce = clientNonce; + } + + /** + * Sets the digest URI reference. + * + * @param digestRef The digest URI reference. + */ + public void setDigestRef(Reference digestRef) { + this.digestRef = digestRef; + } + + /** + * Sets the user identifier, such as a login name or an access key. + * + * @param identifier The user identifier, such as a login name or an access key. + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets the chosen quality of protection. + * + * @param quality The chosen quality of protection. + */ + public void setQuality(String quality) { + this.quality = quality; + } + + /** + * Sets the user secret, such as a password or a secret key. + * + * @param secret The user secret, such as a password or a secret key. + */ + public void setSecret(char[] secret) { + this.secret = secret; + } + + /** + * Sets the user secret, such as a password or a secret key. + * + * @param secret The user secret, such as a password or a secret key. + */ + public void setSecret(String secret) { + this.secret = (secret == null) ? null : secret.toCharArray(); + } + + /** + * Sets the digest algorithm name optionally applied on the user secret. + * + * @param secretDigestAlgorithm The digest algorithm name optionally applied on the user secret. + */ + public void setSecretAlgorithm(String secretDigestAlgorithm) { + this.secretAlgorithm = secretDigestAlgorithm; + } + + /** + * Sets the server nonce count. + * + * @param serverNonceCount The server nonce count. + */ + public void setServerNonceCount(int serverNonceCount) { + this.serverNonceCount = serverNonceCount; + } + + /** + * Sets the time when the response was issued, as returned by {@link + * System#currentTimeMillis()}. + * + * @param timeIssued The time when the response was issued. + */ + public void setTimeIssued(long timeIssued) { + this.timeIssued = timeIssued; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/ChallengeScheme.java b/org.restlet/src/main/java/org/restlet/data/ChallengeScheme.java index 49f636a926..282b4fb575 100644 --- a/org.restlet/src/main/java/org/restlet/data/ChallengeScheme.java +++ b/org.restlet/src/main/java/org/restlet/data/ChallengeScheme.java @@ -1,228 +1,233 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; /** * Challenge scheme used to authenticate remote clients. - * + * * @author Jerome Louvel */ public final class ChallengeScheme { - /** Custom scheme based on IP address or cookies or query parameters, etc. */ - public static final ChallengeScheme CUSTOM = new ChallengeScheme("CUSTOM", "Custom", "Custom authentication"); - - /** Plain FTP scheme. */ - public static final ChallengeScheme FTP_PLAIN = new ChallengeScheme("FTP_PLAIN", "PLAIN", - "Plain FTP authentication"); - - /** Amazon Query String HTTP scheme. */ - public static final ChallengeScheme HTTP_AWS_IAM = new ChallengeScheme("HTTP_AWS_IAM", "AWS3", - "Amazon IAM-based authentication"); - - /** Amazon Query String HTTP scheme. */ - public static final ChallengeScheme HTTP_AWS_QUERY = new ChallengeScheme("HTTP_AWS_QUERY", "AWS_QUERY", - "Amazon Query String authentication"); - - /** Amazon S3 HTTP scheme. */ - public static final ChallengeScheme HTTP_AWS_S3 = new ChallengeScheme("HTTP_AWS_S3", "AWS", - "Amazon S3 HTTP authentication"); - - /** - * Microsoft Azure Shared Key scheme. - * - * @see MSDN - * page - */ - public static final ChallengeScheme HTTP_AZURE_SHAREDKEY = new ChallengeScheme("HTTP_AZURE_SHAREDKEY", "SharedKey", - "Microsoft Azure Shared Key authorization (authentication)"); - - /** - * Microsoft Azure Shared Key lite scheme. - * - * @see MSDN - * page - */ - public static final ChallengeScheme HTTP_AZURE_SHAREDKEY_LITE = new ChallengeScheme("HTTP_AZURE_SHAREDKEY_LITE", - "SharedKeyLite", "Microsoft Azure Shared Key lite authorization (authentication)"); - - /** Basic HTTP scheme. */ - public static final ChallengeScheme HTTP_BASIC = new ChallengeScheme("HTTP_BASIC", "Basic", - "Basic HTTP authentication"); - - /** Cookie HTTP scheme. */ - public static final ChallengeScheme HTTP_COOKIE = new ChallengeScheme("HTTP_Cookie", "Cookie", - "Cookie HTTP authentication"); - - /** Digest HTTP scheme. */ - public static final ChallengeScheme HTTP_DIGEST = new ChallengeScheme("HTTP_DIGEST", "Digest", - "Digest HTTP authentication"); - - /** Microsoft NTML HTTP scheme. */ - public static final ChallengeScheme HTTP_NTLM = new ChallengeScheme("HTTP_NTLM", "NTLM", - "Microsoft NTLM HTTP authentication"); - - /** - * OAuth 1.0 HTTP scheme. Removed in later drafts and final OAuth 2.0 - * specification. - */ - public static final ChallengeScheme HTTP_OAUTH = new ChallengeScheme("HTTP_OAuth", "OAuth", - "OAuth 1.0 authentication"); - - /** OAuth Bearer HTTP scheme. */ - public static final ChallengeScheme HTTP_OAUTH_BEARER = new ChallengeScheme("HTTP_Bearer", "Bearer", - "OAuth 2.0 bearer token authentication"); - - /** OAuth MAC HTTP scheme. */ - public static final ChallengeScheme HTTP_OAUTH_MAC = new ChallengeScheme("HTTP_MAC", "Mac", - "OAuth 2.0 message authentication code authentication"); - - /** Private list of schemes for optimization purpose. */ - private static Map SCHEMES; - - static { - Map schemes = new HashMap(); - - schemes.put(CUSTOM.getName().toLowerCase(), CUSTOM); - schemes.put(FTP_PLAIN.getName().toLowerCase(), FTP_PLAIN); - schemes.put(HTTP_AWS_IAM.getName().toLowerCase(), HTTP_AWS_S3); - schemes.put(HTTP_AWS_QUERY.getName().toLowerCase(), HTTP_AWS_S3); - schemes.put(HTTP_AWS_S3.getName().toLowerCase(), HTTP_AWS_S3); - schemes.put(HTTP_AZURE_SHAREDKEY.getName().toLowerCase(), HTTP_AZURE_SHAREDKEY); - schemes.put(HTTP_AZURE_SHAREDKEY_LITE.getName().toLowerCase(), HTTP_AZURE_SHAREDKEY_LITE); - schemes.put(HTTP_BASIC.getName().toLowerCase(), HTTP_BASIC); - schemes.put(HTTP_COOKIE.getName().toLowerCase(), HTTP_COOKIE); - schemes.put(HTTP_DIGEST.getName().toLowerCase(), HTTP_DIGEST); - schemes.put(HTTP_NTLM.getName().toLowerCase(), HTTP_NTLM); - schemes.put(HTTP_OAUTH.getName().toLowerCase(), HTTP_OAUTH); - schemes.put(HTTP_OAUTH_BEARER.getName().toLowerCase(), HTTP_OAUTH); - schemes.put(HTTP_OAUTH_MAC.getName().toLowerCase(), HTTP_OAUTH); - - ChallengeScheme.SCHEMES = Collections.unmodifiableMap(schemes); - } - - /** - * Returns the challenge scheme associated to a scheme name. If an existing - * constant exists then it is returned, otherwise a new instance is created. - * - * @param name The scheme name. - * @return The associated challenge scheme. - */ - public static ChallengeScheme valueOf(final String name) { - if (name == null) { - throw new IllegalArgumentException("ChallengeScheme.valueOf(name) name must not be null"); - } - - ChallengeScheme result = SCHEMES.get(name.toLowerCase()); - - if (result == null) { - result = new ChallengeScheme(name, null, null); - } - - return result; - } - - /** The description. */ - private final String description; - - /** The name. */ - private final String name; - - /** The technical name. */ - private volatile String technicalName; - - /** - * Constructor. - * - * @param name The unique name. - * @param technicalName The technical name. - */ - public ChallengeScheme(final String name, final String technicalName) { - this(name, technicalName, null); - } - - /** - * Constructor. - * - * @param name The unique name. - * @param technicalName The technical name. - * @param description The description. - */ - public ChallengeScheme(final String name, final String technicalName, final String description) { - this.name = name; - this.description = description; - this.technicalName = technicalName; - } - - /** {@inheritDoc} */ - @Override - public boolean equals(final Object object) { - return (object instanceof ChallengeScheme) && ((ChallengeScheme) object).getName().equalsIgnoreCase(getName()); - } - - /** - * Returns the description. - * - * @return The description. - */ - public String getDescription() { - return this.description; - } - - /** - * Returns the name. - * - * @return The name. - */ - public String getName() { - return name; - } - - /** - * Returns the technical name (ex: BASIC). - * - * @return The technical name (ex: BASIC). - */ - public String getTechnicalName() { - return this.technicalName; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (getName() == null) ? 0 : getName().toLowerCase().hashCode(); - } - - /** - * Sets the technical name (ex: BASIC). - * - * @param technicalName The technical name (ex: BASIC). - */ - @SuppressWarnings("unused") - private void setTechnicalName(String technicalName) { - this.technicalName = technicalName; - } - - /** - * Returns the name. - * - * @return The name. - */ - @Override - public String toString() { - return getName(); - } - + /** Custom scheme based on IP address or cookies or query parameters, etc. */ + public static final ChallengeScheme CUSTOM = + new ChallengeScheme("CUSTOM", "Custom", "Custom authentication"); + + /** Plain FTP scheme. */ + public static final ChallengeScheme FTP_PLAIN = + new ChallengeScheme("FTP_PLAIN", "PLAIN", "Plain FTP authentication"); + + /** Amazon Query String HTTP scheme. */ + public static final ChallengeScheme HTTP_AWS_IAM = + new ChallengeScheme("HTTP_AWS_IAM", "AWS3", "Amazon IAM-based authentication"); + + /** Amazon Query String HTTP scheme. */ + public static final ChallengeScheme HTTP_AWS_QUERY = + new ChallengeScheme( + "HTTP_AWS_QUERY", "AWS_QUERY", "Amazon Query String authentication"); + + /** Amazon S3 HTTP scheme. */ + public static final ChallengeScheme HTTP_AWS_S3 = + new ChallengeScheme("HTTP_AWS_S3", "AWS", "Amazon S3 HTTP authentication"); + + /** + * Microsoft Azure Shared Key scheme. + * + * @see MSDN + * page + */ + public static final ChallengeScheme HTTP_AZURE_SHAREDKEY = + new ChallengeScheme( + "HTTP_AZURE_SHAREDKEY", + "SharedKey", + "Microsoft Azure Shared Key authorization (authentication)"); + + /** + * Microsoft Azure Shared Key lite scheme. + * + * @see MSDN + * page + */ + public static final ChallengeScheme HTTP_AZURE_SHAREDKEY_LITE = + new ChallengeScheme( + "HTTP_AZURE_SHAREDKEY_LITE", + "SharedKeyLite", + "Microsoft Azure Shared Key lite authorization (authentication)"); + + /** Basic HTTP scheme. */ + public static final ChallengeScheme HTTP_BASIC = + new ChallengeScheme("HTTP_BASIC", "Basic", "Basic HTTP authentication"); + + /** Cookie HTTP scheme. */ + public static final ChallengeScheme HTTP_COOKIE = + new ChallengeScheme("HTTP_Cookie", "Cookie", "Cookie HTTP authentication"); + + /** Digest HTTP scheme. */ + public static final ChallengeScheme HTTP_DIGEST = + new ChallengeScheme("HTTP_DIGEST", "Digest", "Digest HTTP authentication"); + + /** Microsoft NTML HTTP scheme. */ + public static final ChallengeScheme HTTP_NTLM = + new ChallengeScheme("HTTP_NTLM", "NTLM", "Microsoft NTLM HTTP authentication"); + + /** OAuth 1.0 HTTP scheme. Removed in later drafts and final OAuth 2.0 specification. */ + public static final ChallengeScheme HTTP_OAUTH = + new ChallengeScheme("HTTP_OAuth", "OAuth", "OAuth 1.0 authentication"); + + /** OAuth Bearer HTTP scheme. */ + public static final ChallengeScheme HTTP_OAUTH_BEARER = + new ChallengeScheme("HTTP_Bearer", "Bearer", "OAuth 2.0 bearer token authentication"); + + /** OAuth MAC HTTP scheme. */ + public static final ChallengeScheme HTTP_OAUTH_MAC = + new ChallengeScheme( + "HTTP_MAC", "Mac", "OAuth 2.0 message authentication code authentication"); + + /** Private list of schemes for optimization purpose. */ + private static final Map SCHEMES = + Map.ofEntries( + Map.entry(CUSTOM.getName().toLowerCase(), CUSTOM), + Map.entry(FTP_PLAIN.getName().toLowerCase(), FTP_PLAIN), + Map.entry(HTTP_AWS_IAM.getName().toLowerCase(), HTTP_AWS_S3), + Map.entry(HTTP_AWS_QUERY.getName().toLowerCase(), HTTP_AWS_S3), + Map.entry(HTTP_AWS_S3.getName().toLowerCase(), HTTP_AWS_S3), + Map.entry(HTTP_AZURE_SHAREDKEY.getName().toLowerCase(), HTTP_AZURE_SHAREDKEY), + Map.entry( + HTTP_AZURE_SHAREDKEY_LITE.getName().toLowerCase(), + HTTP_AZURE_SHAREDKEY_LITE), + Map.entry(HTTP_BASIC.getName().toLowerCase(), HTTP_BASIC), + Map.entry(HTTP_COOKIE.getName().toLowerCase(), HTTP_COOKIE), + Map.entry(HTTP_DIGEST.getName().toLowerCase(), HTTP_DIGEST), + Map.entry(HTTP_NTLM.getName().toLowerCase(), HTTP_NTLM), + Map.entry(HTTP_OAUTH.getName().toLowerCase(), HTTP_OAUTH), + Map.entry(HTTP_OAUTH_BEARER.getName().toLowerCase(), HTTP_OAUTH), + Map.entry(HTTP_OAUTH_MAC.getName().toLowerCase(), HTTP_OAUTH)); + + /** + * Returns the challenge scheme associated with a scheme name. If an existing constant exists, + * then it is returned; otherwise a new instance is created. + * + * @param name The scheme name. + * @return The associated challenge scheme. + */ + public static ChallengeScheme valueOf(final String name) { + if (name == null) { + throw new IllegalArgumentException( + "ChallengeScheme.valueOf(name) name must not be null"); + } + + ChallengeScheme result = SCHEMES.get(name.toLowerCase()); + + if (result == null) { + result = new ChallengeScheme(name, null, null); + } + + return result; + } + + /** The description. */ + private final String description; + + /** The name. */ + private final String name; + + /** The technical name. */ + private volatile String technicalName; + + /** + * Constructor. + * + * @param name The unique name. + * @param technicalName The technical name. + */ + public ChallengeScheme(final String name, final String technicalName) { + this(name, technicalName, null); + } + + /** + * Constructor. + * + * @param name The unique name. + * @param technicalName The technical name. + * @param description The description. + */ + public ChallengeScheme( + final String name, final String technicalName, final String description) { + this.name = name; + this.description = description; + this.technicalName = technicalName; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ChallengeScheme that)) { + return false; + } + return getName().equalsIgnoreCase(that.getName()); + } + + /** + * Returns the description. + * + * @return The description. + */ + public String getDescription() { + return this.description; + } + + /** + * Returns the name. + * + * @return The name. + */ + public String getName() { + return name; + } + + /** + * Returns the technical name (ex: BASIC). + * + * @return The technical name (ex: BASIC). + */ + public String getTechnicalName() { + return this.technicalName; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (getName() == null) ? 0 : getName().toLowerCase().hashCode(); + } + + /** + * Sets the technical name (ex: BASIC). + * + * @param technicalName The technical name (ex: BASIC). + */ + @SuppressWarnings("unused") + private void setTechnicalName(String technicalName) { + this.technicalName = technicalName; + } + + /** + * Returns the name. + * + * @return The name. + */ + @Override + public String toString() { + return getName(); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/CharacterSet.java b/org.restlet/src/main/java/org/restlet/data/CharacterSet.java index 20c70e0bdc..0a0beeee39 100644 --- a/org.restlet/src/main/java/org/restlet/data/CharacterSet.java +++ b/org.restlet/src/main/java/org/restlet/data/CharacterSet.java @@ -1,293 +1,301 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; /** * Metadata used to specify the character set of textual representations. - * + * * @author Jerome Louvel */ public final class CharacterSet extends Metadata { - /** All character sets acceptable. */ - public static final CharacterSet ALL = new CharacterSet("*", "All character sets"); - - /** - * The ISO/IEC 8859-1 (Latin 1) character set. - * - * @see Wikipedia page - */ - public static final CharacterSet ISO_8859_1 = new CharacterSet("ISO-8859-1", - "ISO/IEC 8859-1 or Latin 1 character set"); - - /** - * The ISO/IEC 8859-2 (Latin 2) character set. - * - * @see Wikipedia page - */ - public static final CharacterSet ISO_8859_2 = new CharacterSet("ISO-8859-2", - "ISO/IEC 8859-2 or Latin 2 character set"); - - /** - * The ISO/IEC 8859-3 (Latin 3) character set. - * - * @see Wikipedia page - */ - public static final CharacterSet ISO_8859_3 = new CharacterSet("ISO-8859-3", - "ISO/IEC 8859-3 or Latin 3 character set"); - - /** - * The ISO/IEC 8859-4 (Latin 4) character set. - * - * @see Wikipedia page - */ - public static final CharacterSet ISO_8859_4 = new CharacterSet("ISO-8859-4", - "ISO/IEC 8859-4 or Latin 4 character set"); - - /** - * The ISO/IEC 8859-5 (Cyrillic) character set. - * - * @see Wikipedia page - */ - public static final CharacterSet ISO_8859_5 = new CharacterSet("ISO-8859-5", - "ISO/IEC 8859-5 or Cyrillic character set"); - - /** - * The ISO/IEC 8859-6 (Arabic) character set. - * - * @see Wikipedia page - */ - public static final CharacterSet ISO_8859_6 = new CharacterSet("ISO-8859-6", - "ISO/IEC 8859-6 or Arabic character set"); - - /** - * The ISO/IEC 8859-7 (Greek) character set. - * - * @see Wikipedia page - */ - public static final CharacterSet ISO_8859_7 = new CharacterSet("ISO-8859-7", - "ISO/IEC 8859-7 or Greek character set"); - - /** - * The ISO/IEC 8859-8 (Hebrew) character set. - * - * @see Wikipedia page - */ - public static final CharacterSet ISO_8859_8 = new CharacterSet("ISO-8859-8", - "ISO/IEC 8859-8 or Hebrew character set"); - - /** - * The ISO/IEC 8859-9 (Latin 5) character set. - * - * @see Wikipedia page - */ - public static final CharacterSet ISO_8859_9 = new CharacterSet("ISO-8859-9", - "ISO/IEC 8859-9 or Latin 5 character set"); - - /** - * The ISO/IEC 8859-10 (Latin 6) character set. - * - * @see Wikipedia page - */ - public static final CharacterSet ISO_8859_10 = new CharacterSet("ISO-8859-10", - "ISO/IEC 8859-10 or Latin 6 character set"); - - /** - * The Macintosh ("Mac OS Roman") character set. - * - * @see Wikipedia page - */ - public static final CharacterSet MACINTOSH = new CharacterSet("macintosh", "Mac OS Roman character set"); - - /** - * The US-ASCII character set. - * - * @see Wikipedia page - */ - public static final CharacterSet US_ASCII = new CharacterSet("US-ASCII", "US ASCII character set"); - - /** - * The UTF-16 character set. - * - * @see Wikipedia page - */ - public static final CharacterSet UTF_16 = new CharacterSet("UTF-16", "UTF 16 character set"); - - /** - * The UTF-8 character set. - * - * @see Wikipedia page - */ - public static final CharacterSet UTF_8 = new CharacterSet("UTF-8", "UTF 8 character set"); - - /** - * The Windows-1252 ('ANSI') character set. - * - * @see Wikipedia page - * - */ - public static final CharacterSet WINDOWS_1252 = new CharacterSet("windows-1252", "Windows 1232 character set"); - - /** - * The default character set of the JVM. - * - * @see java.nio.charset.Charset#defaultCharset() - */ - public static final CharacterSet DEFAULT = new CharacterSet(java.nio.charset.Charset.defaultCharset()); - - /** - * Handles mapping between Java character set names and IANA preferred name. For - * example, "MACROMAN" is not an official IANA name and "ISO-8859-6" is - * preferred over "arabic". - * - * @param name The character set name. - * @return The IANA character set name. - */ - private static String getIanaName(String name) { - if (name != null) { - name = name.toUpperCase(); - - if (name.equalsIgnoreCase("MACROMAN")) { - name = MACINTOSH.getName(); - } else if (name.equalsIgnoreCase("ASCII")) { - name = US_ASCII.getName(); - } else if (name.equalsIgnoreCase("latin1")) { - name = ISO_8859_1.getName(); - } else if (name.equalsIgnoreCase("latin2")) { - name = ISO_8859_2.getName(); - } else if (name.equalsIgnoreCase("latin3")) { - name = ISO_8859_3.getName(); - } else if (name.equalsIgnoreCase("latin4")) { - name = ISO_8859_4.getName(); - } else if (name.equalsIgnoreCase("cyrillic")) { - name = ISO_8859_5.getName(); - } else if (name.equalsIgnoreCase("arabic")) { - name = ISO_8859_6.getName(); - } else if (name.equalsIgnoreCase("greek")) { - name = ISO_8859_7.getName(); - } else if (name.equalsIgnoreCase("hebrew")) { - name = ISO_8859_8.getName(); - } else if (name.equalsIgnoreCase("latin5")) { - name = ISO_8859_9.getName(); - } else if (name.equalsIgnoreCase("latin6")) { - name = ISO_8859_10.getName(); - } - } - - return name; - } - - /** - * Returns the character set associated to a name. If an existing constant - * exists then it is returned, otherwise a new instance is created. - * - * @param name The name. - * @return The associated character set. - */ - public static CharacterSet valueOf(String name) { - CharacterSet result = null; - name = getIanaName(name); - - if ((name != null) && !name.isEmpty()) { - if (name.equalsIgnoreCase(ALL.getName())) { - result = ALL; - } else if (name.equalsIgnoreCase(ISO_8859_1.getName())) { - result = ISO_8859_1; - } else if (name.equalsIgnoreCase(US_ASCII.getName())) { - result = US_ASCII; - } else if (name.equalsIgnoreCase(UTF_8.getName())) { - result = UTF_8; - } else if (name.equalsIgnoreCase(UTF_16.getName())) { - result = UTF_16; - } else if (name.equalsIgnoreCase(WINDOWS_1252.getName())) { - result = WINDOWS_1252; - } else if (name.equalsIgnoreCase(MACINTOSH.getName())) { - result = MACINTOSH; - } else { - result = new CharacterSet(name); - } - } - - return result; - } - - /** - * Constructor. - * - * @param charset The character set. - */ - public CharacterSet(final java.nio.charset.Charset charset) { - this(charset.name(), charset.displayName()); - } - - /** - * Constructor. - * - * @param name The name. - */ - public CharacterSet(String name) { - this(name == null ? null : name.toUpperCase(), "Character set or range of character sets"); - } - - /** - * Constructor. - * - * @param name The name. - * @param description The description. - */ - public CharacterSet(String name, String description) { - super(getIanaName(name), description); - } - - /** {@inheritDoc} */ - @Override - public boolean equals(Object object) { - return (object instanceof CharacterSet) && getName().equalsIgnoreCase(((CharacterSet) object).getName()); - } - - @Override - public Metadata getParent() { - return equals(ALL) ? null : ALL; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (getName() == null) ? 0 : getName().toLowerCase().hashCode(); - } - - /** - * Indicates if a given character set is included in the current one. The test - * is true if both character sets are equal or if the given character set is - * within the range of the current one. For example, ALL includes all character - * sets. A null character set is considered as included into the current one. - *

- * Examples: - *

    - *
  • ALL.includes(UTF_16) returns true
  • - *
  • UTF_16.includes(ALL) returns false
  • - *
- * - * @param included The character set to test for inclusion. - * @return True if the given character set is included in the current one. - * @see #isCompatible(Metadata) - */ - public boolean includes(Metadata included) { - return equals(ALL) || (included == null) || equals(included); - } - - /** - * Returns the NIO charset matching the character set name. - * - * @return The NIO charset. - */ - public java.nio.charset.Charset toCharset() { - return java.nio.charset.Charset.forName(getName()); - } + /** All character sets acceptable. */ + public static final CharacterSet ALL = new CharacterSet("*", "All character sets"); + + /** + * The ISO/IEC 8859-1 (Latin 1) character set. + * + * @see Wikipedia page + */ + public static final CharacterSet ISO_8859_1 = + new CharacterSet("ISO-8859-1", "ISO/IEC 8859-1 or Latin 1 character set"); + + /** + * The ISO/IEC 8859-2 (Latin 2) character set. + * + * @see Wikipedia page + */ + public static final CharacterSet ISO_8859_2 = + new CharacterSet("ISO-8859-2", "ISO/IEC 8859-2 or Latin 2 character set"); + + /** + * The ISO/IEC 8859-3 (Latin 3) character set. + * + * @see Wikipedia page + */ + public static final CharacterSet ISO_8859_3 = + new CharacterSet("ISO-8859-3", "ISO/IEC 8859-3 or Latin 3 character set"); + + /** + * The ISO/IEC 8859-4 (Latin 4) character set. + * + * @see Wikipedia page + */ + public static final CharacterSet ISO_8859_4 = + new CharacterSet("ISO-8859-4", "ISO/IEC 8859-4 or Latin 4 character set"); + + /** + * The ISO/IEC 8859-5 (Cyrillic) character set. + * + * @see Wikipedia page + */ + public static final CharacterSet ISO_8859_5 = + new CharacterSet("ISO-8859-5", "ISO/IEC 8859-5 or Cyrillic character set"); + + /** + * The ISO/IEC 8859-6 (Arabic) character set. + * + * @see Wikipedia page + */ + public static final CharacterSet ISO_8859_6 = + new CharacterSet("ISO-8859-6", "ISO/IEC 8859-6 or Arabic character set"); + + /** + * The ISO/IEC 8859-7 (Greek) character set. + * + * @see Wikipedia page + */ + public static final CharacterSet ISO_8859_7 = + new CharacterSet("ISO-8859-7", "ISO/IEC 8859-7 or Greek character set"); + + /** + * The ISO/IEC 8859-8 (Hebrew) character set. + * + * @see Wikipedia page + */ + public static final CharacterSet ISO_8859_8 = + new CharacterSet("ISO-8859-8", "ISO/IEC 8859-8 or Hebrew character set"); + + /** + * The ISO/IEC 8859-9 (Latin 5) character set. + * + * @see Wikipedia page + */ + public static final CharacterSet ISO_8859_9 = + new CharacterSet("ISO-8859-9", "ISO/IEC 8859-9 or Latin 5 character set"); + + /** + * The ISO/IEC 8859-10 (Latin 6) character set. + * + * @see Wikipedia page + */ + public static final CharacterSet ISO_8859_10 = + new CharacterSet("ISO-8859-10", "ISO/IEC 8859-10 or Latin 6 character set"); + + /** + * The Macintosh ("Mac OS Roman") character set. + * + * @see Wikipedia page + */ + public static final CharacterSet MACINTOSH = + new CharacterSet("macintosh", "Mac OS Roman character set"); + + /** + * The US-ASCII character set. + * + * @see Wikipedia page + */ + public static final CharacterSet US_ASCII = + new CharacterSet("US-ASCII", "US ASCII character set"); + + /** + * The UTF-16 character set. + * + * @see Wikipedia page + */ + public static final CharacterSet UTF_16 = new CharacterSet("UTF-16", "UTF 16 character set"); + + /** + * The UTF-8 character set. + * + * @see Wikipedia page + */ + public static final CharacterSet UTF_8 = new CharacterSet("UTF-8", "UTF 8 character set"); + + /** + * The Windows-1252 ('ANSI') character set. + * + * @see Wikipedia page + */ + public static final CharacterSet WINDOWS_1252 = + new CharacterSet("windows-1252", "Windows 1232 character set"); + + /** + * The default character set of the JVM. + * + * @see java.nio.charset.Charset#defaultCharset() + */ + public static final CharacterSet DEFAULT = + new CharacterSet(java.nio.charset.Charset.defaultCharset()); + + /** + * Handles mapping between Java character set names and IANA preferred name. For example, + * "MACROMAN" is not an official IANA name and "ISO-8859-6" is preferred over "arabic". + * + * @param name The character set name. + * @return The IANA character set name. + */ + private static String getIanaName(String name) { + if (name != null) { + name = name.toUpperCase(); + + if (name.equalsIgnoreCase("MACROMAN")) { + name = MACINTOSH.getName(); + } else if (name.equalsIgnoreCase("ASCII")) { + name = US_ASCII.getName(); + } else if (name.equalsIgnoreCase("latin1")) { + name = ISO_8859_1.getName(); + } else if (name.equalsIgnoreCase("latin2")) { + name = ISO_8859_2.getName(); + } else if (name.equalsIgnoreCase("latin3")) { + name = ISO_8859_3.getName(); + } else if (name.equalsIgnoreCase("latin4")) { + name = ISO_8859_4.getName(); + } else if (name.equalsIgnoreCase("cyrillic")) { + name = ISO_8859_5.getName(); + } else if (name.equalsIgnoreCase("arabic")) { + name = ISO_8859_6.getName(); + } else if (name.equalsIgnoreCase("greek")) { + name = ISO_8859_7.getName(); + } else if (name.equalsIgnoreCase("hebrew")) { + name = ISO_8859_8.getName(); + } else if (name.equalsIgnoreCase("latin5")) { + name = ISO_8859_9.getName(); + } else if (name.equalsIgnoreCase("latin6")) { + name = ISO_8859_10.getName(); + } + } + + return name; + } + + /** + * Returns the character set associated with a name. If an existing constant exists, then it is + * returned; otherwise a new instance is created. + * + * @param name The name. + * @return The associated character set. + */ + public static CharacterSet valueOf(String name) { + CharacterSet result = null; + name = getIanaName(name); + + if ((name != null) && !name.isEmpty()) { + if (name.equalsIgnoreCase(ALL.getName())) { + result = ALL; + } else if (name.equalsIgnoreCase(ISO_8859_1.getName())) { + result = ISO_8859_1; + } else if (name.equalsIgnoreCase(US_ASCII.getName())) { + result = US_ASCII; + } else if (name.equalsIgnoreCase(UTF_8.getName())) { + result = UTF_8; + } else if (name.equalsIgnoreCase(UTF_16.getName())) { + result = UTF_16; + } else if (name.equalsIgnoreCase(WINDOWS_1252.getName())) { + result = WINDOWS_1252; + } else if (name.equalsIgnoreCase(MACINTOSH.getName())) { + result = MACINTOSH; + } else { + result = new CharacterSet(name); + } + } + + return result; + } + + /** + * Constructor. + * + * @param charset The character set. + */ + public CharacterSet(final java.nio.charset.Charset charset) { + this(charset.name(), charset.displayName()); + } + + /** + * Constructor. + * + * @param name The name. + */ + public CharacterSet(String name) { + this(name == null ? null : name.toUpperCase(), "Character set or range of character sets"); + } + + /** + * Constructor. + * + * @param name The name. + * @param description The description. + */ + public CharacterSet(String name, String description) { + super(getIanaName(name), description); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof CharacterSet that)) { + return false; + } + return getName().equalsIgnoreCase(that.getName()); + } + + @Override + public Metadata getParent() { + return equals(ALL) ? null : ALL; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (getName() == null) ? 0 : getName().toLowerCase().hashCode(); + } + + /** + * Indicates if a given character set is included in the current one. The test is true if both + * character sets are equal or if the given character set is within the range of the current + * one. For example, ALL includes all character sets. A null character set is considered as + * included in the current one. + * + *

Examples: + * + *

    + *
  • ALL.includes(UTF_16) returns true + *
  • UTF_16.includes(ALL) returns false + *
+ * + * @param included The character set to test for inclusion. + * @return True if the given character set is included in the current one. + * @see #isCompatible(Metadata) + */ + public boolean includes(Metadata included) { + return equals(ALL) || (included == null) || equals(included); + } + + /** + * Returns the NIO charset matching the character set name. + * + * @return The NIO charset. + */ + public java.nio.charset.Charset toCharset() { + return java.nio.charset.Charset.forName(getName()); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/ClientInfo.java b/org.restlet/src/main/java/org/restlet/data/ClientInfo.java index 28d369e35e..3fba8ca3db 100644 --- a/org.restlet/src/main/java/org/restlet/data/ClientInfo.java +++ b/org.restlet/src/main/java/org/restlet/data/ClientInfo.java @@ -1,69 +1,67 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.restlet.Context; -import org.restlet.engine.Engine; -import org.restlet.engine.io.IoUtils; - +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.net.URL; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import org.restlet.Context; +import org.restlet.engine.Engine; +import org.restlet.engine.io.IoUtils; +import org.restlet.routing.Template; +import org.restlet.routing.Variable; /** - * Client specific data related to a call. When extracted from a request, most - * of these data are directly taken from the underlying headers. There are some - * exceptions: agentAttributes and mainAgentProduct which are taken from the - * agent name (for example the "user-agent" header for HTTP requests).
+ * Client-specific data related to a call. When extracted from a request, most of these data are + * directly taken from the underlying headers. There are some exceptions: agentAttributes and + * mainAgentProduct which are taken from the agent name (for example, the "user-agent" header for + * HTTP requests).
*
- * As described by the HTTP specification, the "user-agent" can be seen as a - * ordered list of products name (ie a name and a version) and/or comments.
+ * As described by the HTTP specification, the "user-agent" can be seen as an ordered list of + * products name (i.e., a name and a version) and/or comments.
*
- * Each HTTP client (mainly browsers and web crawlers) defines its own - * "user-agent" header which can be seen as the "signature" of the client. - * Unfortunately, there is no rule to identify clearly a kind a client and its - * version (let's say Firefox 2.x, Internet Explorer IE 7.0, Opera, etc) - * according to its signature. Each signature follow its own rules which may - * vary according to the version of the client.
+ * Each HTTP client (mainly browsers and web crawlers) defines its own "user-agent" header which can + * be seen as the "signature" of the client. Unfortunately, there is no rule to identify clearly a + * kind a client and its version (let's say Firefox 2.x, Internet Explorer IE 7.0, Opera, etc.) + * according to its signature. Each signature follows its own rules, which may vary according to the + * version of the client.
*
- * In order to help retrieving interesting data such as product name (Firefox, - * IE, etc), version, operating system, Restlet users has the ability to define - * their own way to extract data from the "user-agent" header. It is based on a - * list of templates declared in a file called "agent.properties" and located in - * the classpath in the sub directory "org/restlet/data". Each template - * describes a typical user-agent string and allows to use predefined variables - * that help to retrieve the content of the agent name, version, operating - * system.
+ * To help retrieving interesting data such as product name (Firefox, IE, etc.), version, operating + * system, Restlet users can define their own way to extract data from the "user-agent" header. It + * is based on a list of templates declared in a file called "agent.properties" and located in the + * classpath in the subdirectory "org/restlet/data". Each template describes a typical user-agent + * string and allows using predefined variables that help to retrieve the content of the agent name, + * version, operating system.
*
- * The "user-agent" string is confronted to the each template from the beginning - * of the property file to the end. The loop stops at the first matched - * template.
+ * The "user-agent" string is confronted to each template from the beginning of the property file to + * the end. The loop stops at the first matched template.
*
- * Here is a sample of such template:
- * + * Here is a sample of such a template:
+ * *

  * #Firefox for Windows
  *  Mozilla/{mozillaVersion} (Windows; U; {agentOs}; {osData}; rv:{releaseVersion}) Gecko/{geckoReleaseDate} {agentName}/{agentVersion}
  * 
- * - * This template matches the "user-agent" string of the Firefox client for - * windows: - * + * + * This template matches the "user-agent" string of the Firefox client for windows: + * *
  *  Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20060918 Firefox/2.0
  * 
- * + * * At this time, six predefined variables are used:
+ * * * * @@ -96,968 +94,969 @@ * * *
list of predefined variables
A sequence of characters that can be empty
+ * *
*
- * These variables are used to generate a {@link Product} instance with the main - * data (name, version, comment). This instance is accessible via the - * {@link ClientInfo#getMainAgentProduct()} method. All other variables used in - * the template aims at catching a sequence of characters and are accessible via - * the {@link ClientInfo#getAgentAttributes()} method. - * + * These variables are used to generate a {@link Product} instance with the main data (name, + * version, comment). This instance is accessible via the {@link ClientInfo#getMainAgentProduct()} + * method. All other variables used in the template aim at catching a sequence of characters and are + * accessible via the {@link ClientInfo#getAgentAttributes()} method. + * * @author Jerome Louvel */ public final class ClientInfo { - /** - * List of user-agent templates defined in "agent.properties" file.
- * - * @see ClientInfo#getAgentAttributes() - */ - private static volatile List userAgentTemplates = null; - - /** - * Returns the preferred metadata taking into account both metadata supported by - * the server and client preferences. - * - * @param supported The metadata supported by the server. - * @param preferences The client preferences. - * @return The preferred metadata. - */ - public static T getPreferredMetadata(List supported, List> preferences) { - T result = null; - float maxQuality = 0; - - if (supported != null) { - for (Preference pref : preferences) { - for (T metadata : supported) { - if (pref.getMetadata().isCompatible(metadata) && (pref.getQuality() > maxQuality)) { - result = metadata; - maxQuality = pref.getQuality(); - } - } - } - } - - return result; - } - - /** - * Returns the list of user-agent templates defined in "agent.properties" file. - * - * @return The list of user-agent templates defined in "agent.properties" file. - * @see ClientInfo#getAgentAttributes() - */ - private static List getUserAgentTemplates() { - // Lazy initialization with double-check. - List u = ClientInfo.userAgentTemplates; - if (u == null) { - synchronized (ClientInfo.class) { - u = ClientInfo.userAgentTemplates; - if (u == null) { - // Load from the "agent.properties" file - java.net.URL userAgentPropertiesUrl = Engine.getResource("org/restlet/data/agent.properties"); - if (userAgentPropertiesUrl != null) { - java.io.BufferedReader reader; - try { - reader = new java.io.BufferedReader( - new InputStreamReader(userAgentPropertiesUrl.openStream(), - CharacterSet.UTF_8.getName()), - IoUtils.BUFFER_SIZE); - String line = reader.readLine(); - for (; line != null; line = reader.readLine()) { - if ((line.trim().length() > 0) && !line.trim().startsWith("#")) { - if (u == null) { - u = new CopyOnWriteArrayList(); - } - u.add(line); - } - } - reader.close(); - } catch (IOException e) { - if (Context.getCurrent() != null) { - Context.getCurrent().getLogger().warning("Cannot read '" - + userAgentPropertiesUrl.toString() + "' due to: " + e.getMessage()); - } - } - } - ClientInfo.userAgentTemplates = u; - } - } - } - return u; - } - - /** The character set preferences. */ - private volatile List> acceptedCharacterSets; - - /** The encoding preferences. */ - private volatile List> acceptedEncodings; - - /** The language preferences. */ - private volatile List> acceptedLanguages; - - /** The media preferences. */ - private volatile List> acceptedMediaTypes; - - /** The patch preferences. */ - private volatile List> acceptedPatches; - - /** The immediate IP addresses. */ - private volatile String address; - - /** The agent name. */ - private volatile String agent; - - /** The attributes data taken from the agent name. */ - private volatile Map agentAttributes; - - /** The main product data taken from the agent name. */ - private volatile Product agentMainProduct; - - /** The list of product tokens taken from the agent name. */ - private volatile List agentProducts; - - /** - * Indicates if the subject has been authenticated. The application is - * responsible for updating this property, relying on - * {@link org.restlet.security.Authenticator} or manually. - */ - private volatile boolean authenticated; - - /** List of client certificates. */ - private volatile List certificates; - - /** The SSL Cipher Suite, if available and accessible. */ - private volatile String cipherSuite; - - /** List of expectations. */ - private volatile List expectations; - - /** The forwarded IP addresses. */ - private volatile List forwardedAddresses; - - /** The email address of the human user controlling the user agent. */ - private volatile String from; - - /** The port number. */ - private volatile int port; - - /** List of additional client principals. */ - private volatile List principals; - - /** List of user roles. */ - private volatile List roles; - - /** Authenticated user. */ - private volatile org.restlet.security.User user; - - /** - * Constructor. - */ - public ClientInfo() { - this.address = null; - this.agent = null; - this.port = -1; - this.acceptedCharacterSets = null; - this.acceptedEncodings = null; - this.acceptedLanguages = null; - this.acceptedMediaTypes = null; - this.acceptedPatches = null; - this.forwardedAddresses = null; - this.from = null; - this.agentProducts = null; - this.principals = null; - this.user = null; - this.roles = null; - this.expectations = null; - } - - /** - * Constructor from a list of variants. Note that only media types are taken - * into account. - * - * @param variants The variants corresponding to the accepted media types. - */ - public ClientInfo(List variants) { - if (variants != null) { - for (org.restlet.representation.Variant variant : variants) { - getAcceptedMediaTypes().add(new Preference(variant.getMediaType())); - } - } - } - - /** - * Constructor from a media type. - * - * @param mediaType The preferred media type. - */ - public ClientInfo(MediaType mediaType) { - getAcceptedMediaTypes().add(new Preference(mediaType)); - } - - /** - * Updates the client preferences to accept the given metadata (media types, - * character sets, etc.) with a 1.0 quality in addition to existing ones. - * - * @param metadata The metadata to accept. - */ - public void accept(Metadata... metadata) { - if (metadata != null) { - for (Metadata md : metadata) { - accept(md, 1.0F); - } - } - } - - /** - * Updates the client preferences to accept the given metadata (media types, - * character sets, etc.) with a given quality in addition to existing ones. - * - * @param metadata The metadata to accept. - * @param quality The quality to set. - */ - public void accept(Metadata metadata, float quality) { - if (metadata instanceof MediaType) { - getAcceptedMediaTypes().add(new Preference((MediaType) metadata, quality)); - } else if (metadata instanceof Language) { - getAcceptedLanguages().add(new Preference((Language) metadata, quality)); - } else if (metadata instanceof Encoding) { - getAcceptedEncodings().add(new Preference((Encoding) metadata, quality)); - } else { - getAcceptedCharacterSets().add(new Preference((CharacterSet) metadata, quality)); - } - } - - /** - * Returns the modifiable list of character set preferences. Creates a new - * instance if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Accept-Charset" header. - * - * @return The character set preferences. - */ - public List> getAcceptedCharacterSets() { - // Lazy initialization with double-check. - List> a = this.acceptedCharacterSets; - if (a == null) { - synchronized (this) { - a = this.acceptedCharacterSets; - if (a == null) { - this.acceptedCharacterSets = a = new CopyOnWriteArrayList>(); - } - } - } - return a; - } - - /** - * Returns the modifiable list of encoding preferences. Creates a new instance - * if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Accept-Encoding" header. - * - * @return The encoding preferences. - */ - public List> getAcceptedEncodings() { - // Lazy initialization with double-check. - List> a = this.acceptedEncodings; - if (a == null) { - synchronized (this) { - a = this.acceptedEncodings; - if (a == null) { - this.acceptedEncodings = a = new CopyOnWriteArrayList>(); - } - } - } - return a; - } - - /** - * Returns the modifiable list of language preferences. Creates a new instance - * if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Accept-Language" header. - * - * @return The language preferences. - */ - public List> getAcceptedLanguages() { - // Lazy initialization with double-check. - List> a = this.acceptedLanguages; - if (a == null) { - synchronized (this) { - a = this.acceptedLanguages; - if (a == null) { - this.acceptedLanguages = a = new CopyOnWriteArrayList>(); - } - } - } - return a; - } - - /** - * Returns the modifiable list of media type preferences. Creates a new instance - * if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the "Accept" - * header. - * - * @return The media type preferences. - */ - public List> getAcceptedMediaTypes() { - // Lazy initialization with double-check. - List> a = this.acceptedMediaTypes; - if (a == null) { - synchronized (this) { - a = this.acceptedMediaTypes; - if (a == null) { - this.acceptedMediaTypes = a = new CopyOnWriteArrayList>(); - } - } - } - return a; - } - - /** - * Returns the modifiable list of patch preferences. Creates a new instance if - * no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Accept-Patch" header. - * - * @return The patch preferences. - */ - public List> getAcceptedPatches() { - // Lazy initialization with double-check. - List> a = this.acceptedPatches; - if (a == null) { - synchronized (this) { - a = this.acceptedPatches; - if (a == null) { - this.acceptedPatches = a = new CopyOnWriteArrayList>(); - } - } - } - return a; - } - - /** - * Returns the immediate client's IP address. If the real client is separated - * from the server by a proxy server, this will return the IP address of the - * proxy. - * - * @return The immediate client's IP address. - * @see #getUpstreamAddress() - * @see #getForwardedAddresses() - */ - public String getAddress() { - return this.address; - } - - /** - * Returns the agent name (ex: "Restlet-Framework/2.0"). Note that when used - * with HTTP connectors, this property maps to the "User-Agent" header. - * - * @return The agent name. - */ - public String getAgent() { - return this.agent; - } - - /** - * Returns a list of attributes taken from the name of the user agent. - * - * @return A list of attributes taken from the name of the user agent. - * @see #getAgent() - */ - public Map getAgentAttributes() { - if (this.agentAttributes == null) { - this.agentAttributes = new ConcurrentHashMap(); - Map map = new ConcurrentHashMap(); - - // Loop on a list of user-agent templates until a template match - // the current user-agent string. The list of templates is - // located in a file named "agent.properties" available on - // the classpath. - // Some defined variables are used in order to catch the name, - // version and optional comment. Respectively, these - // variables are called "agentName", "agentVersion" and - // "agentComment". - org.restlet.routing.Template template = null; - // Predefined variables. - org.restlet.routing.Variable agentName = new org.restlet.routing.Variable( - org.restlet.routing.Variable.TYPE_TOKEN); - org.restlet.routing.Variable agentVersion = new org.restlet.routing.Variable( - org.restlet.routing.Variable.TYPE_TOKEN); - org.restlet.routing.Variable agentComment = new org.restlet.routing.Variable( - org.restlet.routing.Variable.TYPE_COMMENT); - org.restlet.routing.Variable agentCommentAttribute = new org.restlet.routing.Variable( - org.restlet.routing.Variable.TYPE_COMMENT_ATTRIBUTE); - org.restlet.routing.Variable facultativeData = new org.restlet.routing.Variable( - org.restlet.routing.Variable.TYPE_ALL, null, false, false); - - if (ClientInfo.getUserAgentTemplates() != null) { - for (String string : ClientInfo.getUserAgentTemplates()) { - template = new org.restlet.routing.Template(string, org.restlet.routing.Template.MODE_EQUALS); - - // Update the predefined variables. - template.getVariables().put("agentName", agentName); - template.getVariables().put("agentVersion", agentVersion); - template.getVariables().put("agentComment", agentComment); - template.getVariables().put("agentOs", agentCommentAttribute); - template.getVariables().put("commentAttribute", agentCommentAttribute); - template.getVariables().put("facultativeData", facultativeData); - - // Parse the template - if (template.parse(getAgent(), map) > -1) { - for (String key : map.keySet()) { - this.agentAttributes.put(key, (String) map.get(key)); - } - break; - } - } - } - } - - return this.agentAttributes; - } - - /** - * Returns the name of the user agent. - * - * @return The name of the user agent. - * @see #getAgent() - */ - public String getAgentName() { - final Product product = getMainAgentProduct(); - if (product != null) { - return product.getName(); - } - - return null; - } - - /** - * Returns the list of product tokens from the user agent name. - * - * @return The list of product tokens from the user agent name. - * @see #getAgent() - */ - public List getAgentProducts() { - if (this.agentProducts == null) { - this.agentProducts = org.restlet.engine.header.ProductReader.read(getAgent()); - } - return this.agentProducts; - } - - /** - * Returns the version of the user agent. - * - * @return The version of the user agent. - * @see #getAgent() - */ - public String getAgentVersion() { - final Product product = getMainAgentProduct(); - if (product != null) { - return product.getVersion(); - } - return null; - - } - - /** - * Returns the client certificates. Those certificates are available when a - * request is received via an HTTPS connection, corresponding to the SSL/TLS - * certificates. - * - * @return The client certificates. - * @see javax.net.ssl.SSLSession#getPeerCertificates() - */ - public List getCertificates() { - // Lazy initialization with double-check. - List a = this.certificates; - if (a == null) { - synchronized (this) { - a = this.certificates; - if (a == null) { - this.certificates = a = new CopyOnWriteArrayList(); - } - } - } - return a; - } - - /** - * Returns the SSL Cipher Suite, if available and accessible. - * - * @return The SSL Cipher Suite, if available and accessible. - * @see javax.net.ssl.SSLSession#getCipherSuite() - */ - public String getCipherSuite() { - return this.cipherSuite; - } - - /** - * Returns the client expectations. - * - * @return The client expectations. - */ - public List getExpectations() { - // Lazy initialization with double-check. - List a = this.expectations; - if (a == null) { - synchronized (this) { - a = this.expectations; - if (a == null) { - this.expectations = a = new CopyOnWriteArrayList(); - } - } - } - return a; - } - - /** - * Returns the list of forwarded IP addresses. This is useful when the user - * agent is separated from the origin server by a chain of intermediary - * components. Creates a new instance if no one has been set.
- *
- * The first address is the one of the immediate client component and the last - * address should correspond to the origin client (frequently a user agent).
- *
- * This information is only safe for intermediary components within your local - * network. Other addresses could easily be changed by setting a fake header and - * should not be trusted for serious security checks.
- *
- * Note that your HTTP server connectors need to have a special - * "useForwardedForHeader" parameter explicitly set to "true" in order to - * activate this feature, due to potential security issues. - * - * @return The list of forwarded IP addresses. - * @see #getUpstreamAddress() - * @see Wikipedia page - * for the "X-Forwarded-For" HTTP header - */ - public List getForwardedAddresses() { - // Lazy initialization with double-check. - List a = this.forwardedAddresses; - if (a == null) { - synchronized (this) { - a = this.forwardedAddresses; - if (a == null) { - this.forwardedAddresses = a = new CopyOnWriteArrayList(); - } - } - } - return a; - } - - /** - * Returns the email address of the human user controlling the user agent. - * Default value is null. - * - * @return The email address of the human user controlling the user agent. - */ - public String getFrom() { - return from; - } - - /** - * Returns a Product object based on the name of the user agent. - * - * @return A Product object based on name of the user agent. - */ - public Product getMainAgentProduct() { - if (this.agentMainProduct == null) { - if (getAgentAttributes() != null) { - this.agentMainProduct = new Product(getAgentAttributes().get("agentName"), - getAgentAttributes().get("agentVersion"), getAgentAttributes().get("agentComment")); - } - } - - return this.agentMainProduct; - } - - /** - * Returns the port number which sent the call. If no port is specified, -1 is - * returned. - * - * @return The port number which sent the call. - */ - public int getPort() { - return this.port; - } - - /** - * Returns the preferred character set among a list of supported ones, based on - * the client preferences. - * - * @param supported The supported character sets. - * @return The preferred character set. - */ - public CharacterSet getPreferredCharacterSet(List supported) { - return getPreferredMetadata(supported, getAcceptedCharacterSets()); - } - - /** - * Returns the preferred encoding among a list of supported ones, based on the - * client preferences. - * - * @param supported The supported encodings. - * @return The preferred encoding. - */ - public Encoding getPreferredEncoding(List supported) { - return getPreferredMetadata(supported, getAcceptedEncodings()); - } - - /** - * Returns the preferred language among a list of supported ones, based on the - * client preferences. - * - * @param supported The supported languages. - * @return The preferred language. - */ - public Language getPreferredLanguage(List supported) { - return getPreferredMetadata(supported, getAcceptedLanguages()); - } - - /** - * Returns the preferred media type among a list of supported ones, based on the - * client preferences. - * - * @param supported The supported media types. - * @return The preferred media type. - */ - public MediaType getPreferredMediaType(List supported) { - return getPreferredMetadata(supported, getAcceptedMediaTypes()); - } - - /** - * Returns the preferred patch among a list of supported ones, based on the - * client preferences. - * - * @param supported The supported patches. - * @return The preferred patch. - */ - public MediaType getPreferredPatch(List supported) { - return getPreferredMetadata(supported, getAcceptedPatches()); - } - - /** - * Returns the additional client principals. Note that {@link #getUser()} and - * {@link #getRoles()} methods already return user and role principals. - * - * @return The additional client principals. - */ - public List getPrincipals() { - // Lazy initialization with double-check. - List a = this.principals; - if (a == null) { - synchronized (this) { - a = this.principals; - if (a == null) { - this.principals = a = new CopyOnWriteArrayList(); - } - } - } - return a; - } - - /** - * Returns the authenticated user roles. - * - * @return The authenticated user roles. - */ - public List getRoles() { - // Lazy initialization with double-check. - List a = this.roles; - if (a == null) { - synchronized (this) { - a = this.roles; - if (a == null) { - this.roles = a = new CopyOnWriteArrayList(); - } - } - } - return a; - } - - /** - * Returns the IP address of the upstream client component. In general this will - * correspond the the user agent IP address. This is useful if there are - * intermediary components like proxies and load balancers. - * - * If the supporting {@link #getForwardedAddresses()} method returns a non empty - * list, the IP address will be the first element. Otherwise, the value of - * {@link #getAddress()} will be returned.
- *
- * Note that your HTTP server connectors need to have a special - * "useForwardedForHeader" parameter explicitly set to "true" in order to - * activate this feature, due to potential security issues. - * - * @return The most upstream IP address. - * @see #getAddress() - * @see #getForwardedAddresses() - */ - public String getUpstreamAddress() { - if (this.forwardedAddresses == null || this.forwardedAddresses.isEmpty()) { - return getAddress(); - } - - return this.forwardedAddresses.get(0); - } - - /** - * Returns the authenticated user. - * - * @return The authenticated user. - */ - public org.restlet.security.User getUser() { - return user; - } - - /** - * Indicates if the identifier or principal has been authenticated. The - * application is responsible for updating this property, relying on a - * {@link org.restlet.security.Authenticator} or manually. - * - * @return True if the identifier or principal has been authenticated. - */ - public boolean isAuthenticated() { - return this.authenticated; - } - - /** - * Sets the character set preferences. Note that when used with HTTP connectors, - * this property maps to the "Accept-Charset" header. - * - * @param acceptedCharacterSets The character set preferences. - */ - public void setAcceptedCharacterSets(List> acceptedCharacterSets) { - synchronized (this) { - List> ac = getAcceptedCharacterSets(); - ac.clear(); - ac.addAll(acceptedCharacterSets); - } - } - - /** - * Sets the encoding preferences. Note that when used with HTTP connectors, this - * property maps to the "Accept-Encoding" header. - * - * @param acceptedEncodings The encoding preferences. - */ - public void setAcceptedEncodings(List> acceptedEncodings) { - synchronized (this) { - List> ac = getAcceptedEncodings(); - ac.clear(); - ac.addAll(acceptedEncodings); - } - } - - /** - * Sets the language preferences. Note that when used with HTTP connectors, this - * property maps to the "Accept-Language" header. - * - * @param acceptedLanguages The language preferences. - */ - public void setAcceptedLanguages(List> acceptedLanguages) { - synchronized (this) { - List> ac = getAcceptedLanguages(); - ac.clear(); - ac.addAll(acceptedLanguages); - } - } - - /** - * Sets the media type preferences. Note that when used with HTTP connectors, - * this property maps to the "Accept" header. - * - * @param acceptedMediaTypes The media type preferences. - */ - public void setAcceptedMediaTypes(List> acceptedMediaTypes) { - synchronized (this) { - List> ac = getAcceptedMediaTypes(); - ac.clear(); - ac.addAll(acceptedMediaTypes); - } - } - - /** - * Sets the patch preferences. Note that when used with HTTP connectors, this - * property maps to the "Accept-Patch" header. - * - * @param acceptedPatches The media type preferences. - */ - public void setAcceptedPatches(List> acceptedPatches) { - synchronized (this) { - List> ac = getAcceptedPatches(); - ac.clear(); - ac.addAll(acceptedPatches); - } - } - - /** - * Sets the client's IP address. - * - * @param address The client's IP address. - */ - public void setAddress(String address) { - this.address = address; - } - - /** - * Sets the agent name (ex: "Restlet-Framework/2.0"). Note that when used with - * HTTP connectors, this property maps to the "User-Agent" header. - * - * @param agent The agent name. - */ - public void setAgent(String agent) { - this.agent = agent; - } - - /** - * Sets a list of attributes taken from the name of the user agent. - * - * @param agentAttributes A list of attributes taken from the name of the user - * agent. - */ - public void setAgentAttributes(Map agentAttributes) { - synchronized (this) { - Map aa = getAgentAttributes(); - aa.clear(); - aa.putAll(agentAttributes); - } - } - - /** - * Sets the list of product tokens from the user agent name. - * - * @param agentProducts The list of product tokens from the user agent name. - */ - public void setAgentProducts(List agentProducts) { - synchronized (this) { - List ap = getAgentProducts(); - ap.clear(); - ap.addAll(agentProducts); - } - } - - /** - * Indicates if the identifier or principal has been authenticated. The - * application is responsible for updating this property, relying on a - * {@link org.restlet.security.Authenticator} or manually. - * - * @param authenticated True if the identifier or principal has been - * authenticated. - */ - public void setAuthenticated(boolean authenticated) { - this.authenticated = authenticated; - } - - /** - * Sets the new client certificates. - * - * @param certificates The client certificates. - * @see #getCertificates() - */ - public void setCertificates(List certificates) { - synchronized (this) { - List fa = getCertificates(); - fa.clear(); - fa.addAll(certificates); - } - } - - /** - * Sets the SSL Cipher Suite, if available and accessible. - * - * @param cipherSuite The SSL Cipher Suite, if available and accessible. - */ - public void setCipherSuite(String cipherSuite) { - this.cipherSuite = cipherSuite; - } - - /** - * Sets the client expectations. - * - * @param expectations The client expectations. - */ - public void setExpectations(List expectations) { - synchronized (this) { - List e = getExpectations(); - e.clear(); - e.addAll(expectations); - } - } - - /** - * Sets the list of forwarded IP addresses. - * - * @param forwardedAddresses The list of forwarded IP addresses. - * @see #getForwardedAddresses() - */ - public void setForwardedAddresses(List forwardedAddresses) { - synchronized (this) { - List fa = getForwardedAddresses(); - fa.clear(); - fa.addAll(forwardedAddresses); - } - } - - /** - * Sets the email address of the human user controlling the user agent. - * - * @param from The email address of the human user controlling the user agent. - */ - public void setFrom(String from) { - this.from = from; - } - - /** - * Sets the port number which sent the call. - * - * @param port The port number which sent the call. - */ - public void setPort(int port) { - this.port = port; - } - - /** - * Sets the additional client principals. - * - * @param principals The additional client principals. - * @see #getPrincipals() - */ - public void setPrincipals(List principals) { - synchronized (this) { - List fa = getPrincipals(); - fa.clear(); - fa.addAll(principals); - } - } - - /** - * Sets the authenticated user roles. - * - * @param roles The authenticated user roles. - */ - public void setRoles(List roles) { - synchronized (this) { - List r = getRoles(); - r.clear(); - r.addAll(roles); - } - } - - /** - * Sets the authenticated user. - * - * @param user The authenticated user. - */ - public void setUser(org.restlet.security.User user) { - this.user = user; - } - + /** + * List of user-agent templates defined in the "agent.properties" file.
+ * + * @see ClientInfo#getAgentAttributes() + */ + private static volatile List userAgentTemplates = null; + + /** + * Returns the preferred metadata taking into account both metadata supported by the server and + * client preferences. + * + * @param supported The metadata supported by the server. + * @param preferences The client preferences. + * @return The preferred metadata. + */ + public static T getPreferredMetadata( + List supported, List> preferences) { + T result = null; + float maxQuality = 0; + + if (supported != null) { + for (Preference pref : preferences) { + for (T metadata : supported) { + if (pref.getMetadata().isCompatible(metadata) + && (pref.getQuality() > maxQuality)) { + result = metadata; + maxQuality = pref.getQuality(); + } + } + } + } + + return result; + } + + /** + * Returns the list of user-agent templates defined in the "agent.properties" file. + * + * @return The list of user-agent templates defined in the "agent.properties" file. + * @see ClientInfo#getAgentAttributes() + */ + private static List getUserAgentTemplates() { + // Lazy initialization with double-check. + List u = ClientInfo.userAgentTemplates; + if (u == null) { + synchronized (ClientInfo.class) { + u = ClientInfo.userAgentTemplates; + if (u == null) { + u = + loadUserAgentTemplates( + Engine.getResource("org/restlet/data/agent.properties")); + ClientInfo.userAgentTemplates = u; + } + } + } + return u; + } + + /** + * Returns the list of user-agent templates defined in the given url. + * + * @return The list of user-agent templates defined in the given url. + */ + private static List loadUserAgentTemplates(final URL userAgentPropertiesUrl) { + if (userAgentPropertiesUrl == null) { + return null; + } + + List u = null; + try (final InputStreamReader in = + new InputStreamReader( + userAgentPropertiesUrl.openStream(), CharacterSet.UTF_8.getName()); + BufferedReader reader = new BufferedReader(in, IoUtils.BUFFER_SIZE)) { + + String line; + while ((line = reader.readLine()) != null) { + final String trim = line.trim(); + if ((!trim.isEmpty()) && !trim.startsWith("#")) { + if (u == null) { + u = new CopyOnWriteArrayList<>(); + } + u.add(line); + } + } + } catch (IOException e) { + if (Context.getCurrent() != null) { + Context.getCurrent() + .getLogger() + .warning( + "Cannot read '" + + userAgentPropertiesUrl + + "' due to: " + + e.getMessage()); + } + } + + return u; + } + + /** The character set preferences. */ + private volatile List> acceptedCharacterSets; + + /** The encoding preferences. */ + private volatile List> acceptedEncodings; + + /** The language preferences. */ + private volatile List> acceptedLanguages; + + /** The media preferences. */ + private volatile List> acceptedMediaTypes; + + /** The patch preferences. */ + private volatile List> acceptedPatches; + + /** The immediate IP addresses. */ + private volatile String address; + + /** The agent name. */ + private volatile String agent; + + /** The attributes data taken from the agent name. */ + private volatile Map agentAttributes; + + /** The main product data taken from the agent name. */ + private volatile Product agentMainProduct; + + /** The list of product tokens taken from the agent name. */ + private volatile List agentProducts; + + /** + * Indicates if the subject has been authenticated. The application is responsible for updating + * this property, relying on {@link org.restlet.security.Authenticator} or manually. + */ + private volatile boolean authenticated; + + /** List of client certificates. */ + private volatile List certificates; + + /** The SSL Cipher Suite, if available and accessible. */ + private volatile String cipherSuite; + + /** List of expectations. */ + private volatile List expectations; + + /** The forwarded IP addresses. */ + private volatile List forwardedAddresses; + + /** The email address of the human user controlling the user agent. */ + private volatile String from; + + /** The port number. */ + private volatile int port; + + /** List of additional client principals. */ + private volatile List principals; + + /** List of user roles. */ + private volatile List roles; + + /** Authenticated user. */ + private volatile org.restlet.security.User user; + + /** Constructor. */ + public ClientInfo() { + this.address = null; + this.agent = null; + this.port = -1; + this.acceptedCharacterSets = null; + this.acceptedEncodings = null; + this.acceptedLanguages = null; + this.acceptedMediaTypes = null; + this.acceptedPatches = null; + this.forwardedAddresses = null; + this.from = null; + this.agentProducts = null; + this.principals = null; + this.user = null; + this.roles = null; + this.expectations = null; + } + + /** + * Constructor from a list of variants. Note that only media types are taken into account. + * + * @param variants The variants corresponding to the accepted media types. + */ + public ClientInfo(List variants) { + if (variants != null) { + for (org.restlet.representation.Variant variant : variants) { + getAcceptedMediaTypes().add(new Preference<>(variant.getMediaType())); + } + } + } + + /** + * Constructor from a media type. + * + * @param mediaType The preferred media type. + */ + public ClientInfo(MediaType mediaType) { + getAcceptedMediaTypes().add(new Preference<>(mediaType)); + } + + /** + * Updates the client preferences to accept the given metadata (media types, character sets, + * etc.) with a 1.0 quality in addition to existing ones. + * + * @param metadata The metadata to accept. + */ + public void accept(Metadata... metadata) { + if (metadata != null) { + for (Metadata md : metadata) { + accept(md, 1.0F); + } + } + } + + /** + * Updates the client preferences to accept the given metadata (media types, character sets, + * etc.) with a given quality in addition to existing ones. + * + * @param metadata The metadata to accept. + * @param quality The quality to set. + */ + public void accept(Metadata metadata, float quality) { + switch (metadata) { + case MediaType mediaType -> + getAcceptedMediaTypes().add(new Preference<>(mediaType, quality)); + case Language language -> + getAcceptedLanguages().add(new Preference<>(language, quality)); + case Encoding encoding -> + getAcceptedEncodings().add(new Preference<>(encoding, quality)); + case null, default -> + getAcceptedCharacterSets() + .add(new Preference<>((CharacterSet) metadata, quality)); + } + } + + /** + * Returns the modifiable list of character set preferences. Creates a new instance if no one + * has been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Accept-Charset" header. + * + * @return The character set preferences. + */ + public List> getAcceptedCharacterSets() { + // Lazy initialization with double-check. + List> a = this.acceptedCharacterSets; + if (a == null) { + synchronized (this) { + a = this.acceptedCharacterSets; + if (a == null) { + this.acceptedCharacterSets = a = new CopyOnWriteArrayList<>(); + } + } + } + return a; + } + + /** + * Returns the modifiable list of encoding preferences. Creates a new instance if no one has + * been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Accept-Encoding" header. + * + * @return The encoding preferences. + */ + public List> getAcceptedEncodings() { + // Lazy initialization with double-check. + List> a = this.acceptedEncodings; + if (a == null) { + synchronized (this) { + a = this.acceptedEncodings; + if (a == null) { + this.acceptedEncodings = a = new CopyOnWriteArrayList<>(); + } + } + } + return a; + } + + /** + * Returns the modifiable list of language preferences. Creates a new instance if no one has + * been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Accept-Language" header. + * + * @return The language preferences. + */ + public List> getAcceptedLanguages() { + // Lazy initialization with double-check. + List> a = this.acceptedLanguages; + if (a == null) { + synchronized (this) { + a = this.acceptedLanguages; + if (a == null) { + this.acceptedLanguages = a = new CopyOnWriteArrayList<>(); + } + } + } + return a; + } + + /** + * Returns the modifiable list of media type preferences. Creates a new instance if no one has + * been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Accept" header. + * + * @return The media type preferences. + */ + public List> getAcceptedMediaTypes() { + // Lazy initialization with double-check. + List> a = this.acceptedMediaTypes; + if (a == null) { + synchronized (this) { + a = this.acceptedMediaTypes; + if (a == null) { + this.acceptedMediaTypes = a = new CopyOnWriteArrayList<>(); + } + } + } + return a; + } + + /** + * Returns the modifiable list of patch preferences. Creates a new instance if no one has been + * set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Accept-Patch" header. + * + * @return The patch preferences. + */ + public List> getAcceptedPatches() { + // Lazy initialization with double-check. + List> a = this.acceptedPatches; + if (a == null) { + synchronized (this) { + a = this.acceptedPatches; + if (a == null) { + this.acceptedPatches = a = new CopyOnWriteArrayList<>(); + } + } + } + return a; + } + + /** + * Returns the immediate client's IP address. If the real client is separated from the server by + * a proxy server, this will return the IP address of the proxy. + * + * @return The immediate client's IP address. + * @see #getUpstreamAddress() + * @see #getForwardedAddresses() + */ + public String getAddress() { + return this.address; + } + + /** + * Returns the agent name (ex: "Restlet-Framework/2.0"). Note that when used with HTTP + * connectors, this property maps to the "User-Agent" header. + * + * @return The agent name. + */ + public String getAgent() { + return this.agent; + } + + /** + * Returns a list of attributes taken from the name of the user agent. + * + * @return A list of attributes taken from the name of the user agent. + * @see #getAgent() + */ + public Map getAgentAttributes() { + if (this.agentAttributes == null) { + this.agentAttributes = new ConcurrentHashMap<>(); + Map map = new ConcurrentHashMap<>(); + + // Loop on a list of user-agent templates until a template match + // the current user-agent string. The list of templates is + // located in a file named "agent.properties" available on + // the classpath. + // Some defined variables are used to catch the name, + // version and optional comment. Respectively, these + // variables are called "agentName", "agentVersion" and + // "agentComment". + Template template; + // Predefined variables. + Variable agentName = new Variable(Variable.TYPE_TOKEN); + Variable agentVersion = new Variable(Variable.TYPE_TOKEN); + Variable agentComment = new Variable(Variable.TYPE_COMMENT); + Variable agentCommentAttribute = new Variable(Variable.TYPE_COMMENT_ATTRIBUTE); + Variable facultativeData = new Variable(Variable.TYPE_ALL, null, false, false); + + if (ClientInfo.getUserAgentTemplates() != null) { + for (String string : ClientInfo.getUserAgentTemplates()) { + template = new Template(string, Template.MODE_EQUALS); + + // Update the predefined variables. + template.getVariables().put("agentName", agentName); + template.getVariables().put("agentVersion", agentVersion); + template.getVariables().put("agentComment", agentComment); + template.getVariables().put("agentOs", agentCommentAttribute); + template.getVariables().put("commentAttribute", agentCommentAttribute); + template.getVariables().put("facultativeData", facultativeData); + + // Parse the template + if (template.parse(getAgent(), map) > -1) { + for (Map.Entry entry : map.entrySet()) { + this.agentAttributes.put(entry.getKey(), (String) entry.getValue()); + } + break; + } + } + } + } + + return this.agentAttributes; + } + + /** + * Returns the name of the user agent. + * + * @return The name of the user agent. + * @see #getAgent() + */ + public String getAgentName() { + final Product product = getMainAgentProduct(); + if (product != null) { + return product.getName(); + } + + return null; + } + + /** + * Returns the list of product tokens from the user agent name. + * + * @return The list of product tokens from the user agent name. + * @see #getAgent() + */ + public List getAgentProducts() { + if (this.agentProducts == null) { + this.agentProducts = org.restlet.engine.header.ProductReader.read(getAgent()); + } + return this.agentProducts; + } + + /** + * Returns the version of the user agent. + * + * @return The version of the user agent. + * @see #getAgent() + */ + public String getAgentVersion() { + final Product product = getMainAgentProduct(); + if (product != null) { + return product.getVersion(); + } + return null; + } + + /** + * Returns the client certificates. Those certificates are available when a request is received + * via an HTTPS connection, corresponding to the SSL/TLS certificates. + * + * @return The client certificates. + * @see javax.net.ssl.SSLSession#getPeerCertificates() + */ + public List getCertificates() { + // Lazy initialization with double-check. + List a = this.certificates; + if (a == null) { + synchronized (this) { + a = this.certificates; + if (a == null) { + this.certificates = a = new CopyOnWriteArrayList<>(); + } + } + } + return a; + } + + /** + * Returns the SSL Cipher Suite, if available and accessible. + * + * @return The SSL Cipher Suite, if available and accessible. + * @see javax.net.ssl.SSLSession#getCipherSuite() + */ + public String getCipherSuite() { + return this.cipherSuite; + } + + /** + * Returns the client expectations. + * + * @return The client expectations. + */ + public List getExpectations() { + // Lazy initialization with double-check. + List a = this.expectations; + if (a == null) { + synchronized (this) { + a = this.expectations; + if (a == null) { + this.expectations = a = new CopyOnWriteArrayList<>(); + } + } + } + return a; + } + + /** + * Returns the list of forwarded IP addresses. This is useful when the user agent is separated + * from the origin server by a chain of intermediary components. Creates a new instance if no + * one has been set.
+ *
+ * The first address is the one of the immediate client component, and the last address should + * correspond to the origin client (frequently a user agent).
+ *
+ * This information is only safe for intermediary components within your local network. Other + * addresses could easily be changed by setting a fake header and should not be trusted for + * serious security checks.
+ *
+ * Note that your HTTP server connectors need to have a special "useForwardedForHeader" + * parameter explicitly set to "true" to activate this feature, due to potential security + * issues. + * + * @return The list of forwarded IP addresses. + * @see #getUpstreamAddress() + * @see Wikipedia page for the + * "X-Forwarded-For" HTTP header + */ + public List getForwardedAddresses() { + // Lazy initialization with double-check. + List a = this.forwardedAddresses; + if (a == null) { + synchronized (this) { + a = this.forwardedAddresses; + if (a == null) { + this.forwardedAddresses = a = new CopyOnWriteArrayList<>(); + } + } + } + return a; + } + + /** + * Returns the email address of the human user controlling the user agent. The default value is + * null. + * + * @return The email address of the human user controlling the user agent. + */ + public String getFrom() { + return from; + } + + /** + * Returns a Product object based on the name of the user agent. + * + * @return A Product object based on the name of the user agent. + */ + public Product getMainAgentProduct() { + if (this.agentMainProduct == null && getAgentAttributes() != null) { + this.agentMainProduct = + new Product( + getAgentAttributes().get("agentName"), + getAgentAttributes().get("agentVersion"), + getAgentAttributes().get("agentComment")); + } + + return this.agentMainProduct; + } + + /** + * Returns the port number which sent the call. If no port is specified, -1 is returned. + * + * @return The port number which sent the call. + */ + public int getPort() { + return this.port; + } + + /** + * Returns the preferred character set among a list of supported ones, based on the client + * preferences. + * + * @param supported The supported character sets. + * @return The preferred character set. + */ + public CharacterSet getPreferredCharacterSet(List supported) { + return getPreferredMetadata(supported, getAcceptedCharacterSets()); + } + + /** + * Returns the preferred encoding among a list of supported ones, based on the client + * preferences. + * + * @param supported The supported encodings. + * @return The preferred encoding. + */ + public Encoding getPreferredEncoding(List supported) { + return getPreferredMetadata(supported, getAcceptedEncodings()); + } + + /** + * Returns the preferred language among a list of supported ones, based on the client + * preferences. + * + * @param supported The supported languages. + * @return The preferred language. + */ + public Language getPreferredLanguage(List supported) { + return getPreferredMetadata(supported, getAcceptedLanguages()); + } + + /** + * Returns the preferred media type among a list of supported ones, based on the client + * preferences. + * + * @param supported The supported media types. + * @return The preferred media type. + */ + public MediaType getPreferredMediaType(List supported) { + return getPreferredMetadata(supported, getAcceptedMediaTypes()); + } + + /** + * Returns the preferred patch among a list of supported ones, based on the client preferences. + * + * @param supported The supported patches. + * @return The preferred patch. + */ + public MediaType getPreferredPatch(List supported) { + return getPreferredMetadata(supported, getAcceptedPatches()); + } + + /** + * Returns the additional client principals. Note that {@link #getUser()} and {@link + * #getRoles()} methods already return user and role principals. + * + * @return The additional client principals. + */ + public List getPrincipals() { + // Lazy initialization with double-check. + List a = this.principals; + if (a == null) { + synchronized (this) { + a = this.principals; + if (a == null) { + this.principals = a = new CopyOnWriteArrayList<>(); + } + } + } + return a; + } + + /** + * Returns the authenticated user roles. + * + * @return The authenticated user roles. + */ + public List getRoles() { + // Lazy initialization with double-check. + List a = this.roles; + if (a == null) { + synchronized (this) { + a = this.roles; + if (a == null) { + this.roles = a = new CopyOnWriteArrayList<>(); + } + } + } + return a; + } + + /** + * Returns the IP address of the upstream client component. In general, this will correspond the + * the user agent IP address. This is useful if there are intermediary components like proxies + * and load balancers. + * + *

If the supporting {@link #getForwardedAddresses()} method returns a non-empty list, the IP + * address will be the first element. Otherwise, the value of {@link #getAddress()} will be + * returned.
+ *
+ * Note that your HTTP server connectors need to have a special "useForwardedForHeader" + * parameter explicitly set to "true" to activate this feature, due to potential security + * issues. + * + * @return The most upstream IP address. + * @see #getAddress() + * @see #getForwardedAddresses() + */ + public String getUpstreamAddress() { + if (this.forwardedAddresses == null || this.forwardedAddresses.isEmpty()) { + return getAddress(); + } + + return this.forwardedAddresses.getFirst(); + } + + /** + * Returns the authenticated user. + * + * @return The authenticated user. + */ + public org.restlet.security.User getUser() { + return user; + } + + /** + * Indicates if the identifier or principal has been authenticated. The application is + * responsible for updating this property, relying on a {@link + * org.restlet.security.Authenticator} or manually. + * + * @return True if the identifier or principal has been authenticated. + */ + public boolean isAuthenticated() { + return this.authenticated; + } + + /** + * Sets the character set preferences. Note that when used with HTTP connectors, this property + * maps to the "Accept-Charset" header. + * + * @param acceptedCharacterSets The character set preferences. + */ + public void setAcceptedCharacterSets(List> acceptedCharacterSets) { + synchronized (this) { + List> ac = getAcceptedCharacterSets(); + ac.clear(); + ac.addAll(acceptedCharacterSets); + } + } + + /** + * Sets the encoding preferences. Note that when used with HTTP connectors, this property maps + * to the "Accept-Encoding" header. + * + * @param acceptedEncodings The encoding preferences. + */ + public void setAcceptedEncodings(List> acceptedEncodings) { + synchronized (this) { + List> ac = getAcceptedEncodings(); + ac.clear(); + ac.addAll(acceptedEncodings); + } + } + + /** + * Sets the language preferences. Note that when used with HTTP connectors, this property maps + * to the "Accept-Language" header. + * + * @param acceptedLanguages The language preferences. + */ + public void setAcceptedLanguages(List> acceptedLanguages) { + synchronized (this) { + List> ac = getAcceptedLanguages(); + ac.clear(); + ac.addAll(acceptedLanguages); + } + } + + /** + * Sets the media type preferences. Note that when used with HTTP connectors, this property maps + * to the "Accept" header. + * + * @param acceptedMediaTypes The media type preferences. + */ + public void setAcceptedMediaTypes(List> acceptedMediaTypes) { + synchronized (this) { + List> ac = getAcceptedMediaTypes(); + ac.clear(); + ac.addAll(acceptedMediaTypes); + } + } + + /** + * Sets the patch preferences. Note that when used with HTTP connectors, this property maps to + * the "Accept-Patch" header. + * + * @param acceptedPatches The media type preferences. + */ + public void setAcceptedPatches(List> acceptedPatches) { + synchronized (this) { + List> ac = getAcceptedPatches(); + ac.clear(); + ac.addAll(acceptedPatches); + } + } + + /** + * Sets the client's IP address. + * + * @param address The client's IP address. + */ + public void setAddress(String address) { + this.address = address; + } + + /** + * Sets the agent name (ex: "Restlet-Framework/2.0"). Note that when used with HTTP connectors, + * this property maps to the "User-Agent" header. + * + * @param agent The agent name. + */ + public void setAgent(String agent) { + this.agent = agent; + } + + /** + * Sets a list of attributes taken from the name of the user agent. + * + * @param agentAttributes A list of attributes taken from the name of the user agent. + */ + public void setAgentAttributes(Map agentAttributes) { + synchronized (this) { + Map aa = getAgentAttributes(); + aa.clear(); + aa.putAll(agentAttributes); + } + } + + /** + * Sets the list of product tokens from the user agent name. + * + * @param agentProducts The list of product tokens from the user agent name. + */ + public void setAgentProducts(List agentProducts) { + synchronized (this) { + List ap = getAgentProducts(); + ap.clear(); + ap.addAll(agentProducts); + } + } + + /** + * Indicates if the identifier or principal has been authenticated. The application is + * responsible for updating this property, relying on a {@link + * org.restlet.security.Authenticator} or manually. + * + * @param authenticated True if the identifier or principal has been authenticated. + */ + public void setAuthenticated(boolean authenticated) { + this.authenticated = authenticated; + } + + /** + * Sets the new client certificates. + * + * @param certificates The client certificates. + * @see #getCertificates() + */ + public void setCertificates(List certificates) { + synchronized (this) { + List fa = getCertificates(); + fa.clear(); + fa.addAll(certificates); + } + } + + /** + * Sets the SSL Cipher Suite, if available and accessible. + * + * @param cipherSuite The SSL Cipher Suite, if available and accessible. + */ + public void setCipherSuite(String cipherSuite) { + this.cipherSuite = cipherSuite; + } + + /** + * Sets the client expectations. + * + * @param expectations The client expectations. + */ + public void setExpectations(List expectations) { + synchronized (this) { + List e = getExpectations(); + e.clear(); + e.addAll(expectations); + } + } + + /** + * Sets the list of forwarded IP addresses. + * + * @param forwardedAddresses The list of forwarded IP addresses. + * @see #getForwardedAddresses() + */ + public void setForwardedAddresses(List forwardedAddresses) { + synchronized (this) { + List fa = getForwardedAddresses(); + fa.clear(); + fa.addAll(forwardedAddresses); + } + } + + /** + * Sets the email address of the human user controlling the user agent. + * + * @param from The email address of the human user controlling the user agent. + */ + public void setFrom(String from) { + this.from = from; + } + + /** + * Sets the port number which sent the call. + * + * @param port The port number which sent the call. + */ + public void setPort(int port) { + this.port = port; + } + + /** + * Sets the additional client principals. + * + * @param principals The additional client principals. + * @see #getPrincipals() + */ + public void setPrincipals(List principals) { + synchronized (this) { + List fa = getPrincipals(); + fa.clear(); + fa.addAll(principals); + } + } + + /** + * Sets the authenticated user roles. + * + * @param roles The authenticated user roles. + */ + public void setRoles(List roles) { + synchronized (this) { + List r = getRoles(); + r.clear(); + r.addAll(roles); + } + } + + /** + * Sets the authenticated user. + * + * @param user The authenticated user. + */ + public void setUser(org.restlet.security.User user) { + this.user = user; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Conditions.java b/org.restlet/src/main/java/org/restlet/data/Conditions.java index 690ffb7c89..cab4802b82 100644 --- a/org.restlet/src/main/java/org/restlet/data/Conditions.java +++ b/org.restlet/src/main/java/org/restlet/data/Conditions.java @@ -1,439 +1,415 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.restlet.engine.util.DateUtils; -import org.restlet.representation.RepresentationInfo; - import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; +import org.restlet.engine.util.DateUtils; +import org.restlet.representation.RepresentationInfo; /** - * Set of conditions applying to a request. This is equivalent to the HTTP - * conditional headers. - * - * @see If-Match - * @see If-Modified-Since - * @see If-None-Match - * @see If-Range - * @see If-Unmodified-Since - * + * Set of conditions applying to a request. This is an equivalent to the HTTP conditional headers. + * + * @see If-Match + * @see If-Modified-Since + * @see If-None-Match + * @see If-Range + * @see If-Unmodified-Since * @author Jerome Louvel */ public final class Conditions { - /** The "if-match" condition. */ - private volatile List match; - - /** The "if-modified-since" condition. */ - private volatile Date modifiedSince; - - /** The "if-none-match" condition. */ - private volatile List noneMatch; - - /** The "if-range" condition as a Date. */ - private volatile Date rangeDate; - - /** The "if-range" condition as an entity tag. */ - private volatile Tag rangeTag; - - /** The "if-unmodified-since" condition */ - private volatile Date unmodifiedSince; - - /** - * Constructor. - */ - public Conditions() { - } - - /** - * Returns the modifiable list of tags that must be matched. Creates a new - * instance if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-Match" header. - * - * @return The "if-match" condition. - */ - public List getMatch() { - // Lazy initialization with double-check. - List m = this.match; - if (m == null) { - synchronized (this) { - m = this.match; - if (m == null) { - this.match = m = new ArrayList(); - } - } - } - return m; - } - - /** - * Returns the condition based on the modification date of the requested - * variant.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-Modified-Since" header. - * - * @return The condition date. - */ - public Date getModifiedSince() { - return this.modifiedSince; - } - - /** - * Returns the modifiable list of tags that mustn't match. Creates a new - * instance if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-None-Match" header. - * - * @return The list of tags that mustn't match. - */ - public List getNoneMatch() { - // Lazy initialization with double-check. - List n = this.noneMatch; - if (n == null) { - synchronized (this) { - n = this.noneMatch; - if (n == null) { - this.noneMatch = n = new ArrayList(); - } - } - } - return n; - } - - /** - * Returns the range condition based on the modification date of the requested - * variant.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-Range" header. - * - * @return The range condition date. - */ - public Date getRangeDate() { - return rangeDate; - } - - /** - * Returns the range conditional status of an entity. - * - * @param representationInfo The representation information that will be tested. - * @return the status of the response. - */ - public Status getRangeStatus(RepresentationInfo representationInfo) { - return getRangeStatus((representationInfo == null) ? null : representationInfo.getTag(), - (representationInfo == null) ? null : representationInfo.getModificationDate()); - } - - /** - * Returns the range conditional status of an entity. - * - * @param tag The tag of the entity. - * @param modificationDate The modification date of the entity. - * @return The status of the response. - */ - public Status getRangeStatus(Tag tag, Date modificationDate) { - Status result = Status.CLIENT_ERROR_PRECONDITION_FAILED; - if (getRangeTag() != null) { - boolean all = getRangeTag().equals(Tag.ALL); - - // If a tag exists - if (tag != null) { - if (all || getRangeTag().equals(tag)) { - result = Status.SUCCESS_OK; - } - } - } else if (getRangeDate() != null) { - // If a modification date exists - if (getRangeDate().equals(modificationDate)) { - result = Status.SUCCESS_OK; - } - } - - return result; - } - - /** - * Returns the range condition based on the entity tag of the requested - * variant.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-Range" header. - * - * @return The range entity tag. - */ - public Tag getRangeTag() { - return rangeTag; - } - - /** - * Returns the conditional status of a variant using a given method. - * - * @param method The request method. - * @param entityExists Indicates if the entity exists. - * @param tag The tag. - * @param modificationDate The modification date. - * @return Null if the requested method can be performed, the status of the - * response otherwise. - */ - public Status getStatus(Method method, boolean entityExists, Tag tag, Date modificationDate) { - Status result = null; - - // Is the "if-Match" rule followed or not? - if ((this.match != null) && !this.match.isEmpty()) { - boolean matched = false; - boolean failed = false; - boolean all = (getMatch().size() > 0) && getMatch().get(0).equals(Tag.ALL); - String statusMessage = null; - - if (entityExists) { - // If a tag exists - if (!all && (tag != null)) { - // Check if it matches one of the representations already - // cached by the client - Tag matchTag; - - for (Iterator iter = getMatch().iterator(); !matched && iter.hasNext();) { - matchTag = iter.next(); - matched = matchTag.equals(tag, false); - } - } else { - matched = all; - } - } else { - // See - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 - // If none of the entity tags match, or if "*" is given and no - // current entity exists, the server MUST NOT perform the - // requested method - failed = all; - statusMessage = "A non existing resource can't match any tag."; - } - - failed = failed || !matched; - - if (failed) { - result = Status.CLIENT_ERROR_PRECONDITION_FAILED; - if (statusMessage != null) { - result = new Status(result, statusMessage); - } - } - } - - // Is the "if-None-Match" rule followed or not? - if ((result == null) && (this.noneMatch != null) && !this.noneMatch.isEmpty()) { - boolean matched = false; - - if (entityExists) { - // If a tag exists - if (tag != null) { - // Check if it matches one of the representations - // already cached by the client - Tag noneMatchTag; - - for (Iterator iter = getNoneMatch().iterator(); !matched && iter.hasNext();) { - noneMatchTag = iter.next(); - matched = noneMatchTag.equals(tag, (Method.GET.equals(method) || Method.HEAD.equals(method))); - } - - // The current representation matches one of those already - // cached by the client - if (matched) { - // Check if the current representation has been updated - // since the "if-modified-since" date. In this case, the - // rule is followed. - Date modifiedSince = getModifiedSince(); - boolean isModifiedSince = (modifiedSince != null) && (DateUtils.after(new Date(), modifiedSince) - || (modificationDate == null) || DateUtils.after(modifiedSince, modificationDate)); - matched = !isModifiedSince; - } - } else { - matched = (getNoneMatch().size() > 0) && Tag.ALL.equals(getNoneMatch().get(0)); - } - } - - if (matched) { - if (Method.GET.equals(method) || Method.HEAD.equals(method)) { - result = Status.REDIRECTION_NOT_MODIFIED; - } else { - result = Status.CLIENT_ERROR_PRECONDITION_FAILED; - } - } - } - - // Is the "if-Modified-Since" rule followed or not? - if ((result == null) && (getModifiedSince() != null)) { - Date modifiedSince = getModifiedSince(); - boolean isModifiedSince = (DateUtils.after(new Date(), modifiedSince) || (modificationDate == null) - || DateUtils.after(modifiedSince, modificationDate)); - - if (!isModifiedSince) { - if (Method.GET.equals(method) || Method.HEAD.equals(method)) { - result = Status.REDIRECTION_NOT_MODIFIED; - } else { - result = Status.CLIENT_ERROR_PRECONDITION_FAILED; - } - } - } - - // Is the "if-Unmodified-Since" rule followed or not? - if ((result == null) && (getUnmodifiedSince() != null)) { - Date unModifiedSince = getUnmodifiedSince(); - boolean isUnModifiedSince = ((unModifiedSince == null) || (modificationDate == null) - || !DateUtils.before(modificationDate, unModifiedSince)); - - if (!isUnModifiedSince) { - result = Status.CLIENT_ERROR_PRECONDITION_FAILED; - } - } - - return result; - } - - /** - * Returns the conditional status of a variant using a given method. - * - * @param method The request method. - * @param representationInfo The representation information that will be tested. - * @return Null if the requested method can be performed, the status of the - * response otherwise. - */ - public Status getStatus(Method method, RepresentationInfo representationInfo) { - return getStatus(method, representationInfo != null, - (representationInfo == null) ? null : representationInfo.getTag(), - (representationInfo == null) ? null : representationInfo.getModificationDate()); - } - - /** - * Returns the condition based on the modification date of the requested - * variant.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-Unmodified-Since" header. - * - * @return The condition date. - */ - public Date getUnmodifiedSince() { - return this.unmodifiedSince; - } - - /** - * Indicates if there are some conditions set. - * - * @return True if there are some conditions set. - */ - public boolean hasSome() { - return (((this.match != null) && !this.match.isEmpty()) - || ((this.noneMatch != null) && !this.noneMatch.isEmpty()) || (getModifiedSince() != null) - || (getUnmodifiedSince() != null)); - } - - /** - * Indicates if there are some range conditions set. - * - * @return True if there are some range conditions set. - */ - public boolean hasSomeRange() { - return getRangeTag() != null || getRangeDate() != null; - } - - /** - * Sets the modifiable list of tags that must be matched.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-Match" header. - * - * @param tags The "if-match" condition. - */ - public void setMatch(List tags) { - this.match = tags; - } - - /** - * Sets the condition based on the modification date of the requested - * variant.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-Modified-Since" header. - * - * @param date The modification date. - */ - public void setModifiedSince(Date date) { - this.modifiedSince = DateUtils.unmodifiable(date); - } - - /** - * Sets the modifiable list of tags that mustn't match. Creates a new instance - * if no one has been set.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-None-Match" header. - * - * @param tags The list of tags that mustn't match. - */ - public void setNoneMatch(List tags) { - this.noneMatch = tags; - } - - /** - * Sets the range condition based on the modification date of the requested - * variant.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-Range" header. - * - * @param rangeDate The date of the range condition. - */ - public void setRangeDate(Date rangeDate) { - this.rangeDate = rangeDate; - } - - /** - * Sets the range condition based on the entity tag of the requested - * variant.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-Range" header. - * - * @param rangeTag The entity tag of the range condition. - */ - public void setRangeTag(Tag rangeTag) { - this.rangeTag = rangeTag; - } - - /** - * Sets the condition based on the modification date of the requested - * variant.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "If-Unmodified-Since" header. - * - * @param date The condition date. - */ - public void setUnmodifiedSince(Date date) { - this.unmodifiedSince = DateUtils.unmodifiable(date); - } - + /** The "if-match" condition. */ + private volatile List match; + + /** The "if-modified-since" condition. */ + private volatile Date modifiedSince; + + /** The "if-none-match" condition. */ + private volatile List noneMatch; + + /** The "if-range" condition as a Date. */ + private volatile Date rangeDate; + + /** The "if-range" condition as an entity tag. */ + private volatile Tag rangeTag; + + /** The "if-unmodified-since" condition */ + private volatile Date unmodifiedSince; + + /** Constructor. */ + public Conditions() { + // Nothing to do + } + + /** + * Returns the modifiable list of tags that must be matched. Creates a new instance if no one + * has been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-Match" header. + * + * @return The "if-match" condition. + */ + public List getMatch() { + // Lazy initialization with double-check. + List m = this.match; + if (m == null) { + synchronized (this) { + m = this.match; + if (m == null) { + this.match = m = new ArrayList<>(); + } + } + } + return m; + } + + /** + * Returns the condition based on the modification date of the requested variant.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-Modified-Since" + * header. + * + * @return The condition date. + */ + public Date getModifiedSince() { + return this.modifiedSince; + } + + /** + * Returns the modifiable list of tags that mustn't match. Creates a new instance if no one has + * been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-None-Match" header. + * + * @return The list of tags that mustn't match. + */ + public List getNoneMatch() { + // Lazy initialization with double-check. + List n = this.noneMatch; + if (n == null) { + synchronized (this) { + n = this.noneMatch; + if (n == null) { + this.noneMatch = n = new ArrayList<>(); + } + } + } + return n; + } + + /** + * Returns the range condition based on the modification date of the requested variant.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-Range" header. + * + * @return The range condition date. + */ + public Date getRangeDate() { + return rangeDate; + } + + /** + * Returns the range conditional status of an entity. + * + * @param representationInfo The representation information that will be tested. + * @return the status of the response. + */ + public Status getRangeStatus(RepresentationInfo representationInfo) { + return getRangeStatus( + (representationInfo == null) ? null : representationInfo.getTag(), + (representationInfo == null) ? null : representationInfo.getModificationDate()); + } + + /** + * Returns the range conditional status of an entity. + * + * @param tag The tag of the entity. + * @param modificationDate The modification date of the entity. + * @return The status of the response. + */ + public Status getRangeStatus(Tag tag, Date modificationDate) { + Status result = Status.CLIENT_ERROR_PRECONDITION_FAILED; + if (getRangeTag() != null) { + boolean all = getRangeTag().equals(Tag.ALL); + + // If a tag exists + if (tag != null && (all || getRangeTag().equals(tag))) { + result = Status.SUCCESS_OK; + } + + } else if (getRangeDate() != null && getRangeDate().equals(modificationDate)) { + result = Status.SUCCESS_OK; + } + + return result; + } + + /** + * Returns the range condition based on the entity tag of the requested variant.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-Range" header. + * + * @return The range entity tag. + */ + public Tag getRangeTag() { + return rangeTag; + } + + /** + * Returns the conditional status of a variant using a given method. + * + * @param method The request method. + * @param entityExists Indicates if the entity exists. + * @param tag The tag. + * @param modificationDate The modification date. + * @return Null if the requested method can be performed, the status of the response otherwise. + */ + public Status getStatus(Method method, boolean entityExists, Tag tag, Date modificationDate) { + Status result = null; + + // Is the "if-Match" rule followed or not? + if ((this.match != null) && !this.match.isEmpty()) { + boolean matched = false; + boolean failed = false; + boolean all = (!getMatch().isEmpty()) && getMatch().getFirst().equals(Tag.ALL); + String statusMessage = null; + + if (entityExists) { + // If a tag exists + if (!all && (tag != null)) { + // Check if it matches one of the representations already + // cached by the client + Tag matchTag; + + for (Iterator iter = getMatch().iterator(); !matched && iter.hasNext(); ) { + matchTag = iter.next(); + matched = matchTag.equals(tag, false); + } + } else { + matched = all; + } + } else { + // See + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 + // If none of the entity tags match, or if "*" is given and no + // current entity exists, the server MUST NOT perform the + // requested method + failed = all; + statusMessage = "A non existing resource can't match any tag."; + } + + failed = failed || !matched; + + if (failed) { + result = Status.CLIENT_ERROR_PRECONDITION_FAILED; + if (statusMessage != null) { + result = new Status(result, statusMessage); + } + } + } + + // Is the "if-None-Match" rule followed or not? + if ((result == null) && (this.noneMatch != null) && !this.noneMatch.isEmpty()) { + boolean matched = false; + + if (entityExists) { + // If a tag exists + if (tag != null) { + // Check if it matches one of the representations + // already cached by the client + Tag noneMatchTag; + + for (Iterator iter = getNoneMatch().iterator(); + !matched && iter.hasNext(); ) { + noneMatchTag = iter.next(); + matched = + noneMatchTag.equals( + tag, + (Method.GET.equals(method) || Method.HEAD.equals(method))); + } + + // The current representation matches one of those already + // cached by the client + if (matched) { + // Check if the current representation has been updated + // since the "if-modified-since" date. In this case, the + // rule is followed. + matched = isNotModifiedSince(modificationDate); + } + } else { + matched = + (!getNoneMatch().isEmpty()) + && Tag.ALL.equals(getNoneMatch().getFirst()); + } + } + + if (matched) { + if (Method.GET.equals(method) || Method.HEAD.equals(method)) { + result = Status.REDIRECTION_NOT_MODIFIED; + } else { + result = Status.CLIENT_ERROR_PRECONDITION_FAILED; + } + } + } + + // Is the "if-Modified-Since" rule followed or not? + if (result == null && isNotModifiedSince(modificationDate)) { + if (Method.GET.equals(method) || Method.HEAD.equals(method)) { + result = Status.REDIRECTION_NOT_MODIFIED; + } else { + result = Status.CLIENT_ERROR_PRECONDITION_FAILED; + } + } + + // Is the "if-Unmodified-Since" rule followed or not? + if ((result == null) && (getUnmodifiedSince() != null)) { + Date unModifiedSince = getUnmodifiedSince(); + boolean isUnModifiedSince = + ((unModifiedSince == null) + || (modificationDate == null) + || !DateUtils.before(modificationDate, unModifiedSince)); + + if (!isUnModifiedSince) { + result = Status.CLIENT_ERROR_PRECONDITION_FAILED; + } + } + + return result; + } + + private boolean isNotModifiedSince(final Date modificationDate) { + Date since = getModifiedSince(); + return (since == null) + || (!DateUtils.after(new Date(), since) + && (modificationDate != null) + && !DateUtils.after(since, modificationDate)); + } + + /** + * Returns the conditional status of a variant using a given method. + * + * @param method The request method. + * @param representationInfo The representation information that will be tested. + * @return Null if the requested method can be performed, the status of the response otherwise. + */ + public Status getStatus(Method method, RepresentationInfo representationInfo) { + return getStatus( + method, + representationInfo != null, + (representationInfo == null) ? null : representationInfo.getTag(), + (representationInfo == null) ? null : representationInfo.getModificationDate()); + } + + /** + * Returns the condition based on the modification date of the requested variant.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-Unmodified-Since" + * header. + * + * @return The condition date. + */ + public Date getUnmodifiedSince() { + return this.unmodifiedSince; + } + + /** + * Indicates if there are some conditions set. + * + * @return True if there are some conditions set. + */ + public boolean hasSome() { + return (((this.match != null) && !this.match.isEmpty()) + || ((this.noneMatch != null) && !this.noneMatch.isEmpty()) + || (getModifiedSince() != null) + || (getUnmodifiedSince() != null)); + } + + /** + * Indicates if there are some range conditions set. + * + * @return True if there are some range conditions set. + */ + public boolean hasSomeRange() { + return getRangeTag() != null || getRangeDate() != null; + } + + /** + * Sets the modifiable list of tags that must be matched.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-Match" header. + * + * @param tags The "if-match" condition. + */ + public void setMatch(List tags) { + this.match = tags; + } + + /** + * Sets the condition based on the modification date of the requested variant.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-Modified-Since" + * header. + * + * @param date The modification date. + */ + public void setModifiedSince(Date date) { + this.modifiedSince = DateUtils.unmodifiable(date); + } + + /** + * Sets the modifiable list of tags that mustn't match. Creates a new instance if no one has + * been set.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-None-Match" header. + * + * @param tags The list of tags that mustn't match. + */ + public void setNoneMatch(List tags) { + this.noneMatch = tags; + } + + /** + * Sets the range condition based on the modification date of the requested variant.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-Range" header. + * + * @param rangeDate The date of the range condition. + */ + public void setRangeDate(Date rangeDate) { + this.rangeDate = rangeDate; + } + + /** + * Sets the range condition based on the entity tag of the requested variant.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-Range" header. + * + * @param rangeTag The entity tag of the range condition. + */ + public void setRangeTag(Tag rangeTag) { + this.rangeTag = rangeTag; + } + + /** + * Sets the condition based on the modification date of the requested variant.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "If-Unmodified-Since" + * header. + * + * @param date The condition date. + */ + public void setUnmodifiedSince(Date date) { + this.unmodifiedSince = DateUtils.unmodifiable(date); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Cookie.java b/org.restlet/src/main/java/org/restlet/data/Cookie.java index 4e0e1be791..70de428008 100644 --- a/org.restlet/src/main/java/org/restlet/data/Cookie.java +++ b/org.restlet/src/main/java/org/restlet/data/Cookie.java @@ -1,213 +1,215 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.Objects; import org.restlet.Request; import org.restlet.engine.util.SystemUtils; import org.restlet.util.NamedValue; -import java.util.Objects; - /** - * Cookie provided by a client. To get the list of all cookies sent by a client, - * you can use the {@link Request#getCookies()} method.
+ * Cookie provided by a client. To get the list of all cookies sent by a client, you can use the + * {@link Request#getCookies()} method.
*
- * Note that if you are on the server side and want to set a cookie on the - * client, you should use the {@link CookieSetting} class instead.
+ * Note that if you are on the server side and want to set a cookie on the client, you should use + * the {@link CookieSetting} class instead.
*
- * Note that when used with HTTP connectors, this class maps to the "Cookie" - * header. - * + * Note that when used with HTTP connectors, this class maps to the "Cookie" header. + * * @see Request#getCookies() * @author Jerome Louvel */ public class Cookie implements NamedValue { - /** The domain name. */ - private volatile String domain; - - /** The name. */ - private volatile String name; - - /** The validity path. */ - private volatile String path; - - /** The value. */ - private volatile String value; - - /** The version number. */ - private volatile int version; - - /** - * Default constructor. - */ - public Cookie() { - this(0, null, null, null, null); - } - - /** - * Constructor. - * - * @param version The version number. - * @param name The name. - * @param value The value. - */ - public Cookie(int version, String name, String value) { - this(version, name, value, null, null); - } - - /** - * Constructor. - * - * @param version The version number. - * @param name The name. - * @param value The value. - * @param path The validity path. - * @param domain The domain name. - */ - public Cookie(int version, String name, String value, String path, String domain) { - this.version = version; - this.name = name; - this.value = value; - this.path = path; - this.domain = domain; - } - - /** - * Constructor. - * - * @param name The name. - * @param value The value. - */ - public Cookie(String name, String value) { - this(0, name, value, null, null); - } - - /** {@inheritDoc} */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - if (!(obj instanceof Cookie that)) { - // if obj isn't a cookie or is null don't evaluate further - return false; - } - - return Objects.equals(getName(), that.getName()) - && Objects.equals(getValue(), that.getValue()) - && (this.version == that.version) - && Objects.equals(getDomain(), that.getDomain()) - && Objects.equals(getPath(), that.getPath()); - } - - /** - * Returns the domain name. - * - * @return The domain name. - */ - public String getDomain() { - return this.domain; - } - - /** - * Returns the name. - * - * @return The name. - */ - public String getName() { - return name; - } - - /** - * Returns the validity path. - * - * @return The validity path. - */ - public String getPath() { - return this.path; - } - - /** - * Returns the value. - * - * @return The value. - */ - public String getValue() { - return value; - } - - /** - * Returns the cookie specification version. - * - * @return The cookie specification version. - */ - public int getVersion() { - return this.version; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return SystemUtils.hashCode(getName(), getValue(), getVersion(), getPath(), getDomain()); - } - - /** - * Sets the domain name. - * - * @param domain The domain name. - */ - public void setDomain(String domain) { - this.domain = domain; - } - - /** - * Sets the name. - * - * @param name The name. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Sets the validity path. - * - * @param path The validity path. - */ - public void setPath(String path) { - this.path = path; - } - - /** - * Sets the value. - * - * @param value The value. - */ - public void setValue(String value) { - this.value = value; - } - - /** - * Sets the cookie specification version. - * - * @param version The cookie specification version. - */ - public void setVersion(int version) { - this.version = version; - } - - @Override - public String toString() { - return "Cookie [domain=" + domain + ", name=" + name + ", path=" + path + ", value=" + value + ", version=" - + version + "]"; - } + /** The domain name. */ + private volatile String domain; + + /** The name. */ + private volatile String name; + + /** The validity path. */ + private volatile String path; + + /** The value. */ + private volatile String value; + + /** The version number. */ + private volatile int version; + + /** Default constructor. */ + public Cookie() { + this(0, null, null, null, null); + } + + /** + * Constructor. + * + * @param version The version number. + * @param name The name. + * @param value The value. + */ + public Cookie(int version, String name, String value) { + this(version, name, value, null, null); + } + + /** + * Constructor. + * + * @param version The version number. + * @param name The name. + * @param value The value. + * @param path The validity path. + * @param domain The domain name. + */ + public Cookie(int version, String name, String value, String path, String domain) { + this.version = version; + this.name = name; + this.value = value; + this.path = path; + this.domain = domain; + } + + /** + * Constructor. + * + * @param name The name. + * @param value The value. + */ + public Cookie(String name, String value) { + this(0, name, value, null, null); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Cookie that)) { + return false; + } + + return Objects.equals(getName(), that.getName()) + && Objects.equals(getValue(), that.getValue()) + && (this.version == that.version) + && Objects.equals(getDomain(), that.getDomain()) + && Objects.equals(getPath(), that.getPath()); + } + + /** + * Returns the domain name. + * + * @return The domain name. + */ + public String getDomain() { + return this.domain; + } + + /** + * Returns the name. + * + * @return The name. + */ + public String getName() { + return name; + } + + /** + * Returns the validity path. + * + * @return The validity path. + */ + public String getPath() { + return this.path; + } + + /** + * Returns the value. + * + * @return The value. + */ + public String getValue() { + return value; + } + + /** + * Returns the cookie specification version. + * + * @return The cookie specification version. + */ + public int getVersion() { + return this.version; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return SystemUtils.hashCode(getName(), getValue(), getVersion(), getPath(), getDomain()); + } + + /** + * Sets the domain name. + * + * @param domain The domain name. + */ + public void setDomain(String domain) { + this.domain = domain; + } + + /** + * Sets the name. + * + * @param name The name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the validity path. + * + * @param path The validity path. + */ + public void setPath(String path) { + this.path = path; + } + + /** + * Sets the value. + * + * @param value The value. + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Sets the cookie specification version. + * + * @param version The cookie specification version. + */ + public void setVersion(int version) { + this.version = version; + } + + @Override + public String toString() { + return "Cookie [domain=" + + domain + + ", name=" + + name + + ", path=" + + path + + ", value=" + + value + + ", version=" + + version + + "]"; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/CookieSetting.java b/org.restlet/src/main/java/org/restlet/data/CookieSetting.java index dacf999fcd..9cfe6e59e6 100644 --- a/org.restlet/src/main/java/org/restlet/data/CookieSetting.java +++ b/org.restlet/src/main/java/org/restlet/data/CookieSetting.java @@ -1,250 +1,272 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.Objects; import org.restlet.Response; import org.restlet.engine.util.SystemUtils; -import java.util.Objects; - /** - * Cookie setting provided by a server. This allows a server side application to - * add, modify or remove a cookie on the client.
+ * Cookie setting provided by a server. This allows a server side application to add, modify, or + * remove a cookie on the client.
*
- * Note that when used with HTTP connectors, this class maps to the "Set-Cookie" - * and "Set-Cookie2" headers. - * + * Note that when used with HTTP connectors, this class maps to the "Set-Cookie" and "Set-Cookie2" + * headers. + * * @see Response#getCookieSettings() * @author Jerome Louvel */ public final class CookieSetting extends Cookie { - /** - * Indicates whether to restrict cookie access to untrusted parties. Currently, - * this toggles the non-standard but widely supported HttpOnly cookie parameter. - */ - private volatile boolean accessRestricted; - - /** The user's comment. */ - private volatile String comment; - - /** - * The maximum age in seconds. Use 0 to discard an existing cookie. - */ - private volatile int maxAge; - - /** Indicates if cookie should only be transmitted by secure means. */ - private volatile boolean secure; - - /** - * Default constructor. - */ - public CookieSetting() { - this(0, null, null); - } - - /** - * Constructor. - * - * @param version The cookie's version. - * @param name The cookie's name. - * @param value The cookie's value. - */ - public CookieSetting(int version, String name, String value) { - this(version, name, value, null, null); - } - - /** - * Constructor. - * - * @param version The cookie's version. - * @param name The cookie's name. - * @param value The cookie's value. - * @param path The cookie's path. - * @param domain The cookie's domain name. - */ - public CookieSetting(int version, String name, String value, String path, String domain) { - this(version, name, value, path, domain, null, -1, false, false); - } - - /** - * Constructor. - * - * @param version The cookie's version. - * @param name The cookie's name. - * @param value The cookie's value. - * @param path The cookie's path. - * @param domain The cookie's domain name. - * @param comment The cookie's comment. - * @param maxAge Sets the maximum age in seconds.
- * Use 0 to immediately discard an existing cookie.
- * Use -1 to discard the cookie at the end of the session - * (default). - * @param secure Indicates if cookie should only be transmitted by secure - * means. - */ - public CookieSetting(int version, String name, String value, String path, String domain, String comment, int maxAge, - boolean secure) { - this(version, name, value, path, domain, comment, maxAge, secure, false); - } - - /** - * Constructor. - * - * @param version The cookie's version. - * @param name The cookie's name. - * @param value The cookie's value. - * @param path The cookie's path. - * @param domain The cookie's domain name. - * @param comment The cookie's comment. - * @param maxAge Sets the maximum age in seconds.
- * Use 0 to immediately discard an existing cookie.
- * Use -1 to discard the cookie at the end of the - * session (default). - * @param secure Indicates if cookie should only be transmitted by - * secure means. - * @param accessRestricted Indicates whether to restrict cookie access to - * untrusted parties. Currently this toggles the - * non-standard but widely supported HttpOnly cookie - * parameter. - */ - public CookieSetting(int version, String name, String value, String path, String domain, String comment, int maxAge, - boolean secure, boolean accessRestricted) { - super(version, name, value, path, domain); - this.comment = comment; - this.maxAge = maxAge; - this.secure = secure; - this.accessRestricted = accessRestricted; - } - - /** - * Preferred constructor. - * - * @param name The cookie's name. - * @param value The cookie's value. - */ - public CookieSetting(String name, String value) { - this(0, name, value, null, null); - } - - /** {@inheritDoc} */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof CookieSetting that)) { - return false; - } - - return super.equals(obj) - && this.maxAge == that.maxAge - && this.secure == that.secure - && Objects.equals(this.comment, that.comment); - } - - /** - * Returns the comment for the user. - * - * @return The comment for the user. - */ - public String getComment() { - return this.comment; - } - - /** - * Returns the description of this REST element. - * - * @return The description of this REST element. - */ - public String getDescription() { - return "Cookie setting"; - } - - /** - * Returns the maximum age in seconds. Use 0 to immediately discard an existing - * cookie. Use -1 to discard the cookie at the end of the session (default). - * - * @return The maximum age in seconds. - */ - public int getMaxAge() { - return this.maxAge; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return SystemUtils.hashCode(super.hashCode(), getComment(), getMaxAge(), isSecure()); - } - - /** - * Indicates if cookie access is restricted for untrusted parties. Currently - * this toggles the non-standard but widely supported HttpOnly cookie parameter. - * - * @return accessRestricted True if cookie access should be restricted - */ - public boolean isAccessRestricted() { - return this.accessRestricted; - } - - /** - * Indicates if cookie should only be transmitted by secure means. - * - * @return True if cookie should only be transmitted by secure means. - */ - public boolean isSecure() { - return this.secure; - } - - /** - * Indicates whether to restrict cookie access to untrusted parties. Currently - * this toggles the non-standard but widely supported HttpOnly cookie parameter. - * - * @param accessRestricted True if cookie access should be restricted - */ - public void setAccessRestricted(boolean accessRestricted) { - this.accessRestricted = accessRestricted; - } - - /** - * Sets the comment for the user. - * - * @param comment The comment for the user. - */ - public void setComment(String comment) { - this.comment = comment; - } - - /** - * Sets the maximum age in seconds. Use 0 to immediately discard an existing - * cookie. Use -1 to discard the cookie at the end of the session (default). - * - * @param maxAge The maximum age in seconds. - */ - public void setMaxAge(int maxAge) { - this.maxAge = maxAge; - } - - /** - * Indicates if cookie should only be transmitted by secure means. - * - * @param secure True if cookie should only be transmitted by secure means. - */ - public void setSecure(boolean secure) { - this.secure = secure; - } - - @Override - public String toString() { - return "CookieSetting [accessRestricted=" + accessRestricted + ", comment=" + comment + ", maxAge=" + maxAge - + ", secure=" + secure + ", domain=" + getDomain() + ", name=" + getName() + ", path=" + getPath() - + ", value=" + getValue() + ", version=" + getVersion() + "]"; - } + private static final String DESCRIPTION = "Cookie setting"; + + /** + * Indicates whether to restrict cookie access to untrusted parties. Currently, this toggles the + * non-standard but widely supported HttpOnly cookie parameter. + */ + private volatile boolean accessRestricted; + + /** The user's comment. */ + private volatile String comment; + + /** The maximum age in seconds. Use 0 to discard an existing cookie. */ + private volatile int maxAge; + + /** Indicates if the cookie should only be transmitted by secure means. */ + private volatile boolean secure; + + /** Default constructor. */ + public CookieSetting() { + this(0, null, null); + } + + /** + * Constructor. + * + * @param version The cookie's version. + * @param name The cookie's name. + * @param value The cookie's value. + */ + public CookieSetting(int version, String name, String value) { + this(version, name, value, null, null); + } + + /** + * Constructor. + * + * @param version The cookie's version. + * @param name The cookie's name. + * @param value The cookie's value. + * @param path The cookie's path. + * @param domain The cookie's domain name. + */ + public CookieSetting(int version, String name, String value, String path, String domain) { + this(version, name, value, path, domain, null, -1, false, false); + } + + /** + * Constructor. + * + * @param version The cookie's version. + * @param name The cookie's name. + * @param value The cookie's value. + * @param path The cookie's path. + * @param domain The cookie's domain name. + * @param comment The cookie's comment. + * @param maxAge Sets the maximum age in seconds.
+ * Use 0 to immediately discard an existing cookie.
+ * Use -1 to discard the cookie at the end of the session (default). + * @param secure Indicates if the cookie should only be transmitted by secure means. + */ + public CookieSetting( + int version, + String name, + String value, + String path, + String domain, + String comment, + int maxAge, + boolean secure) { + this(version, name, value, path, domain, comment, maxAge, secure, false); + } + + /** + * Constructor. + * + * @param version The cookie's version. + * @param name The cookie's name. + * @param value The cookie's value. + * @param path The cookie's path. + * @param domain The cookie's domain name. + * @param comment The cookie's comment. + * @param maxAge Sets the maximum age in seconds.
+ * Use 0 to immediately discard an existing cookie.
+ * Use -1 to discard the cookie at the end of the session (default). + * @param secure Indicates if the cookie should only be transmitted by secure means. + * @param accessRestricted Indicates whether to restrict cookie access to untrusted parties. + * Currently, this toggles the non-standard but widely supported HttpOnly cookie parameter. + */ + public CookieSetting( + int version, + String name, + String value, + String path, + String domain, + String comment, + int maxAge, + boolean secure, + boolean accessRestricted) { + super(version, name, value, path, domain); + this.comment = comment; + this.maxAge = maxAge; + this.secure = secure; + this.accessRestricted = accessRestricted; + } + + /** + * Preferred constructor. + * + * @param name The cookie's name. + * @param value The cookie's value. + */ + public CookieSetting(String name, String value) { + this(0, name, value, null, null); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof CookieSetting that)) { + return false; + } + + return super.equals(obj) + && this.maxAge == that.maxAge + && this.secure == that.secure + && accessRestricted == that.accessRestricted + && Objects.equals(this.comment, that.comment); + } + + /** + * Returns the comment for the user. + * + * @return The comment for the user. + */ + public String getComment() { + return this.comment; + } + + /** + * Returns the description of this REST element. + * + * @return The description of this REST element. + */ + public String getDescription() { + return DESCRIPTION; + } + + /** + * Returns the maximum age in seconds. Use 0 to immediately discard an existing cookie. Use -1 + * to discard the cookie at the end of the session (default). + * + * @return The maximum age in seconds. + */ + public int getMaxAge() { + return this.maxAge; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return SystemUtils.hashCode(super.hashCode(), getComment(), getMaxAge(), isSecure()); + } + + /** + * Indicates if cookie access is restricted for untrusted parties. Currently this toggles the + * non-standard but widely supported HttpOnly cookie parameter. + * + * @return accessRestricted True if cookie access should be restricted + */ + public boolean isAccessRestricted() { + return this.accessRestricted; + } + + /** + * Indicates if the cookie should only be transmitted by secure means. + * + * @return True if the cookie should only be transmitted by secure means. + */ + public boolean isSecure() { + return this.secure; + } + + /** + * Indicates whether to restrict cookie access to untrusted parties. Currently, this toggles the + * non-standard but widely supported HttpOnly cookie parameter. + * + * @param accessRestricted True if cookie access should be restricted + */ + public void setAccessRestricted(boolean accessRestricted) { + this.accessRestricted = accessRestricted; + } + + /** + * Sets the comment for the user. + * + * @param comment The comment for the user. + */ + public void setComment(String comment) { + this.comment = comment; + } + + /** + * Sets the maximum age in seconds. Use 0 to immediately discard an existing cookie. Use -1 to + * discard the cookie at the end of the session (default). + * + * @param maxAge The maximum age in seconds. + */ + public void setMaxAge(int maxAge) { + this.maxAge = maxAge; + } + + /** + * Indicates if the cookie should only be transmitted by secure means. + * + * @param secure True if the cookie should only be transmitted by secure means. + */ + public void setSecure(boolean secure) { + this.secure = secure; + } + + @Override + public String toString() { + return "CookieSetting [accessRestricted=" + + accessRestricted + + ", comment=" + + comment + + ", maxAge=" + + maxAge + + ", secure=" + + secure + + ", domain=" + + getDomain() + + ", name=" + + getName() + + ", path=" + + getPath() + + ", value=" + + getValue() + + ", version=" + + getVersion() + + "]"; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Digest.java b/org.restlet/src/main/java/org/restlet/data/Digest.java index bef8d0a5c9..f780680150 100644 --- a/org.restlet/src/main/java/org/restlet/data/Digest.java +++ b/org.restlet/src/main/java/org/restlet/data/Digest.java @@ -1,126 +1,127 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.Arrays; +import java.util.Objects; import org.restlet.engine.util.SystemUtils; import org.restlet.representation.Representation; -import java.util.Arrays; - /** - * Describes a digest value and the digest algorithm used. Digests can have - * several use cases such as ensuring the integrity of representations exchanges - * between resources, or for authentication purpose. - * + * Describes a digest value and the digest algorithm used. Digests can have several use cases, such + * as ensuring the integrity of representations exchanges between resources, or for authentication + * purposes. + * * @see Representation#getDigest() * @author Jerome Louvel */ public class Digest { - /** Digest algorithm defined in RFC 1319. */ - public static final String ALGORITHM_MD2 = "MD2"; + /** Digest algorithm defined in RFC 1319. */ + public static final String ALGORITHM_MD2 = "MD2"; - /** Digest algorithm defined in RFC 1321. */ - public static final String ALGORITHM_MD5 = "MD5"; + /** Digest algorithm defined in RFC 1321. */ + public static final String ALGORITHM_MD5 = "MD5"; - /** No digest algorithm defined. */ - public static final String ALGORITHM_NONE = "NONE"; + /** No digest algorithm defined. */ + public static final String ALGORITHM_NONE = "NONE"; - /** Digest algorithm defined in Secure Hash Standard, NIST FIPS 180-1. */ - public static final String ALGORITHM_SHA_1 = "SHA-1"; + /** Digest algorithm defined in Secure Hash Standard, NIST FIPS 180-1. */ + public static final String ALGORITHM_SHA_1 = "SHA-1"; - /** NIST approved digest algorithm from SHA-2 family. */ - public static final String ALGORITHM_SHA_256 = "SHA-256"; + /** NIST approved digest algorithm from the SHA-2 family. */ + public static final String ALGORITHM_SHA_256 = "SHA-256"; - /** NIST approved digest algorithm from SHA-2 family. */ - public static final String ALGORITHM_SHA_384 = "SHA-384"; + /** NIST approved digest algorithm from the SHA-2 family. */ + public static final String ALGORITHM_SHA_384 = "SHA-384"; - /** NIST approved digest algorithm from SHA-2 family. */ - public static final String ALGORITHM_SHA_512 = "SHA-512"; + /** NIST approved digest algorithm from the SHA-2 family. */ + public static final String ALGORITHM_SHA_512 = "SHA-512"; - /** - * Digest algorithm for the HTTP DIGEST scheme. This is exactly the A1 value - * specified in RFC2617 which is a MD5 hash of the user name, realm and - * password, separated by a colon character. - */ - public static final String ALGORITHM_HTTP_DIGEST = "HTTP-DIGEST-A1"; + /** + * Digest algorithm for the HTTP DIGEST scheme. This is exactly the A1 value specified in + * RFC2617, which is a MD5 hash of the username, realm, and password, separated by a colon + * character. + */ + public static final String ALGORITHM_HTTP_DIGEST = "HTTP-DIGEST-A1"; - /** The digest algorithm. */ - private final String algorithm; + /** The digest algorithm. */ + private final String algorithm; - /** The digest value. */ - private final byte[] value; + /** The digest value. */ + private final byte[] value; - /** - * Constructor using the MD5 algorithm by default. - * - * @param value The digest value. - */ - public Digest(byte[] value) { - this(ALGORITHM_MD5, value); - } + /** + * Constructor using the MD5 algorithm by default. + * + * @param value The digest value. + */ + public Digest(byte[] value) { + this(ALGORITHM_MD5, value); + } - /** - * Constructor. - * - * @param algorithm The digest algorithm. - * @param value The digest value. - */ - public Digest(String algorithm, byte[] value) { - this.algorithm = algorithm; + /** + * Constructor. + * + * @param algorithm The digest algorithm. + * @param value The digest value. + */ + public Digest(String algorithm, byte[] value) { + this.algorithm = algorithm; - // In Java 6, use Arrays.copyOf. - this.value = new byte[value.length]; + // In Java 6, use Arrays.copyOf. + this.value = new byte[value.length]; System.arraycopy(value, 0, this.value, 0, value.length); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Digest that) { - if (getAlgorithm().equals(that.getAlgorithm())) { - return Arrays.equals(getValue(), that.getValue()); - } - } - return false; - } - - /** - * Returns the digest algorithm. - * - * @return The digest algorithm. - */ - public String getAlgorithm() { - return algorithm; - } - - /** - * Returns the digest value. - * - * @return The digest value. - */ - public byte[] getValue() { - // In Java 6, use Arrays.copyOf. - byte[] result = new byte[this.value.length]; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Digest that)) { + return false; + } + return Objects.equals(getAlgorithm(), that.getAlgorithm()) + && Arrays.equals(getValue(), that.getValue()); + } + + /** + * Returns the digest algorithm. + * + * @return The digest algorithm. + */ + public String getAlgorithm() { + return algorithm; + } + + /** + * Returns the digest value. + * + * @return The digest value. + */ + public byte[] getValue() { + // In Java 6, use Arrays.copyOf. + byte[] result = new byte[this.value.length]; System.arraycopy(this.value, 0, result, 0, this.value.length); - return result; - } - - @Override - public int hashCode() { - return SystemUtils.hashCode(algorithm, value); - } + return result; + } - @Override - public String toString() { - return "Digest [algorithm=" + algorithm + ", value=" + Arrays.toString(value) + "]"; - } + @Override + public int hashCode() { + return SystemUtils.hashCode(algorithm, value); + } + @Override + public String toString() { + return "Digest [algorithm=" + algorithm + ", value=" + Arrays.toString(value) + "]"; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Dimension.java b/org.restlet/src/main/java/org/restlet/data/Dimension.java index bd298ff14a..cd309d3a1b 100644 --- a/org.restlet/src/main/java/org/restlet/data/Dimension.java +++ b/org.restlet/src/main/java/org/restlet/data/Dimension.java @@ -1,25 +1,32 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; import org.restlet.Response; /** - * Dimension on which the representations of a resource may vary. Note that when - * used with HTTP connectors, this class maps to the "Vary" header. - * + * Dimension on which the representations of a resource may vary. Note that when used with HTTP + * connectors, this class maps to the "Vary" header. + * * @see Response#getDimensions() * @author Jerome Louvel * @author Piyush Purang (ppurang@gmail.com) */ public enum Dimension { - AUTHORIZATION, CHARACTER_SET, CLIENT_ADDRESS, CLIENT_AGENT, UNSPECIFIED, ENCODING, LANGUAGE, MEDIA_TYPE, TIME, - ORIGIN + AUTHORIZATION, + CHARACTER_SET, + CLIENT_ADDRESS, + CLIENT_AGENT, + UNSPECIFIED, + ENCODING, + LANGUAGE, + MEDIA_TYPE, + TIME, + ORIGIN } diff --git a/org.restlet/src/main/java/org/restlet/data/Disposition.java b/org.restlet/src/main/java/org/restlet/data/Disposition.java index 97254c4e13..e1ad0fb54f 100644 --- a/org.restlet/src/main/java/org/restlet/data/Disposition.java +++ b/org.restlet/src/main/java/org/restlet/data/Disposition.java @@ -1,209 +1,200 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.Date; import org.restlet.engine.util.DateUtils; import org.restlet.util.Series; -import java.util.Date; - /** - * Describes the presentation of a single entity especially in the case of - * multipart documents. This is an equivalent of the HTTP "Content-Disposition" - * header. - * - * @see Content-Disposition header - * @see The - * Content-Disposition Header Field - * + * Describes the presentation of a single entity, especially in the case of multipart documents. + * This is an equivalent of the HTTP "Content-Disposition" header. + * + * @see Content-Disposition header + * @see The Content-Disposition Header + * Field * @author Thierry Boileau */ public class Disposition { - /** The creation date parameter name as presented by the RFC 2183. */ - public static final String NAME_CREATION_DATE = "creation-date"; - - /** The filename parameter name as presented by the RFC 2183. */ - public static final String NAME_FILENAME = "filename"; - - /** The modification date parameter name as presented by the RFCc 2183. */ - public static final String NAME_MODIFICATION_DATE = "modification-date"; - - /** The read date parameter name as presented by the RFC 2183. */ - public static final String NAME_READ_DATE = "read-date"; - - /** The size parameter name as presented by the RFC 2183. */ - public static final String NAME_SIZE = "size"; - - /** - * Indicates that the part is intended to be separated from the full message. - */ - public static final String TYPE_ATTACHMENT = "attachment"; - - /** - * Indicates that the part is intended to be displayed automatically upon - * display of the full message. - */ - public static final String TYPE_INLINE = "inline"; - - /** Indicates that the part is not intended to be displayed. */ - public static final String TYPE_NONE = "none"; - - /** The list of disposition parameters. */ - private Series parameters; - - /** The disposition type. */ - private String type; - - /** - * Constructor. Instantiated with the TYPE_NONE type. - */ - public Disposition() { - this(Disposition.TYPE_NONE); - } - - /** - * Constructor. - * - * @param type The disposition type. - */ - public Disposition(String type) { - super(); - this.type = type; - } - - /** - * Constructor. - * - * @param type The disposition type. - * @param parameters The list of disposition parameters. - */ - public Disposition(String type, Series parameters) { - this(type); - this.parameters = parameters; - } - - /** - * Adds a Date parameter. - * - * @param name The name of the parameter. - * @param value Its value as a date. - */ - public void addDate(String name, Date value) { - getParameters().add(name, DateUtils.format(value, DateUtils.FORMAT_RFC_822.get(0))); - } - - /** - * Returns the value of the "filename" parameter. - * - * @return The value of the "filename" parameter. - */ - public String getFilename() { - return getParameters().getFirstValue(NAME_FILENAME, true); - } - - /** - * Returns the list of disposition parameters. - * - * @return The list of disposition parameters. - */ - public Series getParameters() { - if (this.parameters == null) { - this.parameters = new Series(Parameter.class); - } - - return this.parameters; - } - - /** - * Returns the disposition type. - * - * @return The disposition type. - */ - public String getType() { - return type; - } - - /** - * Sets the creation date parameter. - * - * @param value The creation date. - */ - public void setCreationDate(Date value) { - setDate(NAME_CREATION_DATE, value); - } - - /** - * Sets a Date parameter. - * - * @param name The name of the parameter. - * @param value Its value as a date. - */ - public void setDate(String name, Date value) { - getParameters().set(name, DateUtils.format(value, DateUtils.FORMAT_RFC_822.get(0)), true); - } - - /** - * Sets the value of the "filename" parameter. - * - * @param fileName The file name value. - */ - public void setFilename(String fileName) { - getParameters().set(Disposition.NAME_FILENAME, fileName, true); - } - - /** - * Sets the modification date parameter. - * - * @param value The modification date. - */ - public void setModificationDate(Date value) { - setDate(NAME_MODIFICATION_DATE, value); - } - - /** - * Sets the list of disposition parameters. - * - * @param parameters The list of disposition parameters. - */ - public void setParameters(Series parameters) { - this.parameters = parameters; - } - - /** - * Sets the read date parameter. - * - * @param value The read date. - */ - public void setReadDate(Date value) { - setDate(NAME_READ_DATE, value); - } - - /** - * Sets the value of the "size" parameter. - * - * @param size The size. - */ - public void setSize(long size) { - getParameters().set(Disposition.NAME_SIZE, Long.toString(size), true); - } - - /** - * Sets the disposition type. - * - * @param type The disposition type. - */ - public void setType(String type) { - this.type = type; - } - + /** The creation date parameter name as presented by the RFC 2183. */ + public static final String NAME_CREATION_DATE = "creation-date"; + + /** The filename parameter name as presented by the RFC 2183. */ + public static final String NAME_FILENAME = "filename"; + + /** The modification date parameter name as presented by the RFCc 2183. */ + public static final String NAME_MODIFICATION_DATE = "modification-date"; + + /** The read date parameter name as presented by the RFC 2183. */ + public static final String NAME_READ_DATE = "read-date"; + + /** The size parameter name as presented by the RFC 2183. */ + public static final String NAME_SIZE = "size"; + + /** Indicates that the part is intended to be separated from the full message. */ + public static final String TYPE_ATTACHMENT = "attachment"; + + /** + * Indicates that the part is intended to be displayed automatically upon display of the full + * message. + */ + public static final String TYPE_INLINE = "inline"; + + /** Indicates that the part is not intended to be displayed. */ + public static final String TYPE_NONE = "none"; + + /** The list of disposition parameters. */ + private Series parameters; + + /** The disposition type. */ + private String type; + + /** Constructor. Instantiated with the TYPE_NONE type. */ + public Disposition() { + this(Disposition.TYPE_NONE); + } + + /** + * Constructor. + * + * @param type The disposition type. + */ + public Disposition(String type) { + super(); + this.type = type; + } + + /** + * Constructor. + * + * @param type The disposition type. + * @param parameters The list of disposition parameters. + */ + public Disposition(String type, Series parameters) { + this(type); + this.parameters = parameters; + } + + /** + * Adds a Date parameter. + * + * @param name The name of the parameter. + * @param value Its value as a date. + */ + public void addDate(String name, Date value) { + getParameters().add(name, DateUtils.format(value, DateUtils.FORMAT_RFC_822.getFirst())); + } + + /** + * Returns the value of the "filename" parameter. + * + * @return The value of the "filename" parameter. + */ + public String getFilename() { + return getParameters().getFirstValue(NAME_FILENAME, true); + } + + /** + * Returns the list of disposition parameters. + * + * @return The list of disposition parameters. + */ + public Series getParameters() { + if (this.parameters == null) { + this.parameters = new Series<>(Parameter.class); + } + + return this.parameters; + } + + /** + * Returns the disposition type. + * + * @return The disposition type. + */ + public String getType() { + return type; + } + + /** + * Sets the creation date parameter. + * + * @param value The creation date. + */ + public void setCreationDate(Date value) { + setDate(NAME_CREATION_DATE, value); + } + + /** + * Sets a Date parameter. + * + * @param name The name of the parameter. + * @param value Its value as a date. + */ + public void setDate(String name, Date value) { + getParameters() + .set(name, DateUtils.format(value, DateUtils.FORMAT_RFC_822.getFirst()), true); + } + + /** + * Sets the value of the "filename" parameter. + * + * @param fileName The file name value. + */ + public void setFilename(String fileName) { + getParameters().set(Disposition.NAME_FILENAME, fileName, true); + } + + /** + * Sets the modification date parameter. + * + * @param value The modification date. + */ + public void setModificationDate(Date value) { + setDate(NAME_MODIFICATION_DATE, value); + } + + /** + * Sets the list of disposition parameters. + * + * @param parameters The list of disposition parameters. + */ + public void setParameters(Series parameters) { + this.parameters = parameters; + } + + /** + * Sets the read date parameter. + * + * @param value The read date. + */ + public void setReadDate(Date value) { + setDate(NAME_READ_DATE, value); + } + + /** + * Sets the value of the "size" parameter. + * + * @param size The size. + */ + public void setSize(long size) { + getParameters().set(Disposition.NAME_SIZE, Long.toString(size), true); + } + + /** + * Sets the disposition type. + * + * @param type The disposition type. + */ + public void setType(String type) { + this.type = type; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Encoding.java b/org.restlet/src/main/java/org/restlet/data/Encoding.java index a622569f39..2b575eaf7c 100644 --- a/org.restlet/src/main/java/org/restlet/data/Encoding.java +++ b/org.restlet/src/main/java/org/restlet/data/Encoding.java @@ -1,139 +1,151 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; /** - * Modifier of a representation's media type. Useful to apply compression - * without losing the identity of the underlying media type. - * + * Modifier of a representation's media-type. Useful to apply compression without losing the + * identity of the underlying media type. + * * @author Jerome Louvel */ public final class Encoding extends Metadata { - /** All encodings acceptable. */ - public static final Encoding ALL = new Encoding("*", "All encodings"); - - /** The common Unix file compression. */ - public static final Encoding COMPRESS = new Encoding("compress", "Common Unix compression"); - - /** The zlib format defined by RFC 1950 and 1951. */ - public static final Encoding DEFLATE = new Encoding("deflate", "Deflate compression using the zlib format"); - - /** The zlib format defined by RFC 1950 and 1951, without wrapping. */ - public static final Encoding DEFLATE_NOWRAP = new Encoding("deflate-no-wrap", - "Deflate compression using the zlib format (without wrapping)"); - - /** The FreeMarker encoding. */ - public static final Encoding FREEMARKER = new Encoding("freemarker", "FreeMarker templated representation"); - - /** The GNU Zip encoding. */ - public static final Encoding GZIP = new Encoding("gzip", "GZip compression"); - - /** The default (identity) encoding. */ - public static final Encoding IDENTITY = new Encoding("identity", "The default encoding with no transformation"); - - /** The Velocity encoding. */ - public static final Encoding VELOCITY = new Encoding("velocity", "Velocity templated representation"); - - /** The Info-Zip encoding. */ - public static final Encoding ZIP = new Encoding("zip", "Zip compression"); - - /** - * Returns the encoding associated to a name. If an existing constant exists - * then it is returned, otherwise a new instance is created. - * - * @param name The name. - * @return The associated encoding. - */ - public static Encoding valueOf(final String name) { - Encoding result = null; - - if ((name != null) && !name.isEmpty()) { - if (name.equalsIgnoreCase(ALL.getName())) { - result = ALL; - } else if (name.equalsIgnoreCase(GZIP.getName())) { - result = GZIP; - } else if (name.equalsIgnoreCase(ZIP.getName())) { - result = ZIP; - } else if (name.equalsIgnoreCase(COMPRESS.getName())) { - result = COMPRESS; - } else if (name.equalsIgnoreCase(DEFLATE.getName())) { - result = DEFLATE; - } else if (name.equalsIgnoreCase(DEFLATE_NOWRAP.getName())) { - result = DEFLATE_NOWRAP; - } else if (name.equalsIgnoreCase(IDENTITY.getName())) { - result = IDENTITY; - } else if (name.equalsIgnoreCase(FREEMARKER.getName())) { - result = FREEMARKER; - } else if (name.equalsIgnoreCase(VELOCITY.getName())) { - result = VELOCITY; - } else { - result = new Encoding(name); - } - } - - return result; - } - - /** - * Constructor. - * - * @param name The name. - */ - public Encoding(final String name) { - this(name, "Encoding applied to a representation"); - } - - /** - * Constructor. - * - * @param name The name. - * @param description The description. - */ - public Encoding(final String name, final String description) { - super(name, description); - } - - /** {@inheritDoc} */ - @Override - public boolean equals(final Object object) { - return (object instanceof Encoding) && getName().equalsIgnoreCase(((Encoding) object).getName()); - } - - @Override - public Metadata getParent() { - return equals(ALL) ? null : ALL; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (getName() == null) ? 0 : getName().toLowerCase().hashCode(); - } - - /** - * Indicates if a given encoding is included in the current one. The test is - * true if both encodings are equal or if the given encoding is within the range - * of the current one. For example, ALL includes all encodings. A null encoding - * is considered as included into the current one. - *

- * Examples: - *

    - *
  • ALL.includes(COMPRESS) returns true
  • - *
  • COMPRESS.includes(ALL) returns false
  • - *
- * - * @param included The encoding to test for inclusion. - * @return True if the given encoding is included in the current one. - * @see #isCompatible(Metadata) - */ - public boolean includes(Metadata included) { - return equals(ALL) || (included == null) || equals(included); - } + /** All encodings acceptable. */ + public static final Encoding ALL = new Encoding("*", "All encodings"); + + /** The common Unix file compression. */ + public static final Encoding COMPRESS = new Encoding("compress", "Common Unix compression"); + + /** The zlib format defined by RFC 1950 and 1951. */ + public static final Encoding DEFLATE = + new Encoding("deflate", "Deflate compression using the zlib format"); + + /** The zlib format defined by RFC 1950 and 1951, without wrapping. */ + public static final Encoding DEFLATE_NOWRAP = + new Encoding( + "deflate-no-wrap", + "Deflate compression using the zlib format (without wrapping)"); + + /** The FreeMarker encoding. */ + public static final Encoding FREEMARKER = + new Encoding("freemarker", "FreeMarker templated representation"); + + /** The GNU Zip encoding. */ + public static final Encoding GZIP = new Encoding("gzip", "GZip compression"); + + /** The default (identity) encoding. */ + public static final Encoding IDENTITY = + new Encoding("identity", "The default encoding with no transformation"); + + /** The Velocity encoding. */ + public static final Encoding VELOCITY = + new Encoding("velocity", "Velocity templated representation"); + + /** The Info-Zip encoding. */ + public static final Encoding ZIP = new Encoding("zip", "Zip compression"); + + /** + * Returns the encoding associated with a name. If an existing constant exists, then it is + * returned; otherwise a new instance is created. + * + * @param name The name. + * @return The associated encoding. + */ + public static Encoding valueOf(final String name) { + Encoding result = null; + + if ((name != null) && !name.isEmpty()) { + if (name.equalsIgnoreCase(ALL.getName())) { + result = ALL; + } else if (name.equalsIgnoreCase(GZIP.getName())) { + result = GZIP; + } else if (name.equalsIgnoreCase(ZIP.getName())) { + result = ZIP; + } else if (name.equalsIgnoreCase(COMPRESS.getName())) { + result = COMPRESS; + } else if (name.equalsIgnoreCase(DEFLATE.getName())) { + result = DEFLATE; + } else if (name.equalsIgnoreCase(DEFLATE_NOWRAP.getName())) { + result = DEFLATE_NOWRAP; + } else if (name.equalsIgnoreCase(IDENTITY.getName())) { + result = IDENTITY; + } else if (name.equalsIgnoreCase(FREEMARKER.getName())) { + result = FREEMARKER; + } else if (name.equalsIgnoreCase(VELOCITY.getName())) { + result = VELOCITY; + } else { + result = new Encoding(name); + } + } + + return result; + } + + /** + * Constructor. + * + * @param name The name. + */ + public Encoding(final String name) { + this(name, "Encoding applied to a representation"); + } + + /** + * Constructor. + * + * @param name The name. + * @param description The description. + */ + public Encoding(final String name, final String description) { + super(name, description); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Encoding that)) { + return false; + } + return getName().equalsIgnoreCase(that.getName()); + } + + @Override + public Metadata getParent() { + return equals(ALL) ? null : ALL; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (getName() == null) ? 0 : getName().toLowerCase().hashCode(); + } + + /** + * Indicates if a given encoding is included in the current one. The test is true if both + * encodings are equal or if the given encoding is within the range of the current one. For + * example, ALL includes all encodings. A null encoding is considered as included in the current + * one. + * + *

Examples: + * + *

    + *
  • ALL.includes(COMPRESS) returns true + *
  • COMPRESS.includes(ALL) returns false + *
+ * + * @param included The encoding to test for inclusion. + * @return True if the given encoding is included in the current one. + * @see #isCompatible(Metadata) + */ + public boolean includes(Metadata included) { + return equals(ALL) || (included == null) || equals(included); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Expectation.java b/org.restlet/src/main/java/org/restlet/data/Expectation.java index 812dc51c3e..924a81ca44 100644 --- a/org.restlet/src/main/java/org/restlet/data/Expectation.java +++ b/org.restlet/src/main/java/org/restlet/data/Expectation.java @@ -1,167 +1,167 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.restlet.engine.header.HeaderConstants; -import org.restlet.engine.util.SystemUtils; -import org.restlet.util.NamedValue; - import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.engine.util.SystemUtils; +import org.restlet.util.NamedValue; /** - * Particular server behavior that is required by a client. Note that when used - * with HTTP connectors, this class maps to the "Expect" header. - * + * Particular server behavior that is required by a client. Note that when used with HTTP + * connectors, this class maps to the "Expect" header. + * * @author Jerome Louvel */ public final class Expectation implements NamedValue { - /** - * Creates a "100-continue" expectation. If a client waits for a 100 - * (Continue) provisional response before sending the request body, it MUST send - * this expectation. A client MUST NOT send this expectation if it does not - * intend to send a request entity. - * - * @return A new "100-continue" expectation. - * @see HTTP - * 1.1 - Expect header - */ - public static Expectation continueResponse() { - return new Expectation(HeaderConstants.EXPECT_CONTINUE); - } - - /** The name. */ - private volatile String name; - - /** The list of parameters. */ - private volatile List parameters; - - /** The value. */ - private volatile String value; - - /** - * Constructor for directives with no value. - * - * @param name The directive name. - */ - public Expectation(String name) { - this(name, null); - } - - /** - * Constructor for directives with a value. - * - * @param name The directive name. - * @param value The directive value. - */ - public Expectation(String name, String value) { - this.name = name; - this.value = value; - } - - /** {@inheritDoc} */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - if (!(obj instanceof Expectation that)) { - return false; - } - - return Objects.equals(getName(), that.getName()) - && Objects.equals(getValue(), that.getValue()) - && getParameters().equals(that.getParameters()); - } - - /** - * Returns the name. - * - * @return The name. - */ - public String getName() { - return name; - } - - /** - * Returns the list of parameters. - * - * @return The list of parameters. - */ - public List getParameters() { - // Lazy initialization with double-check. - List r = this.parameters; - if (r == null) { - synchronized (this) { - r = this.parameters; - if (r == null) { - this.parameters = r = new CopyOnWriteArrayList(); - } - } - } - return r; - } - - /** - * Returns the value. - * - * @return The value. - */ - public String getValue() { - return value; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return SystemUtils.hashCode(getName(), getValue(), getParameters()); - } - - /** - * Sets the name. - * - * @param name The name. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Sets the list of parameters. - * - * @param parameters The list of parameters. - */ - public void setParameters(List parameters) { - synchronized (this) { - List r = getParameters(); - r.clear(); - r.addAll(parameters); - } - } - - /** - * Sets the value. - * - * @param value The value. - */ - public void setValue(String value) { - this.value = value; - } - - @Override - public String toString() { - return "Expectation [name=" + name + ", parameters=" + parameters + ", value=" + value + "]"; - } - + /** + * Creates a "100-continue" expectation. If a client waits for a 100 (Continue) provisional + * response before sending the request body, it MUST send this expectation. A client MUST NOT + * send this expectation if it does not intend to send a request entity. + * + * @return A new "100-continue" expectation. + * @see HTTP 1.1 - + * Expect header + */ + public static Expectation continueResponse() { + return new Expectation(HeaderConstants.EXPECT_CONTINUE); + } + + /** The name. */ + private volatile String name; + + /** The list of parameters. */ + private volatile List parameters; + + /** The value. */ + private volatile String value; + + /** + * Constructor for directives with no value. + * + * @param name The directive name. + */ + public Expectation(String name) { + this(name, null); + } + + /** + * Constructor for directives with a value. + * + * @param name The directive name. + * @param value The directive value. + */ + public Expectation(String name, String value) { + this.name = name; + this.value = value; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Expectation that)) { + return false; + } + + return Objects.equals(getName(), that.getName()) + && Objects.equals(getValue(), that.getValue()) + && Objects.equals(getParameters(), that.getParameters()); + } + + /** + * Returns the name. + * + * @return The name. + */ + public String getName() { + return name; + } + + /** + * Returns the list of parameters. + * + * @return The list of parameters. + */ + public List getParameters() { + // Lazy initialization with double-check. + List r = this.parameters; + if (r == null) { + synchronized (this) { + r = this.parameters; + if (r == null) { + this.parameters = r = new CopyOnWriteArrayList<>(); + } + } + } + return r; + } + + /** + * Returns the value. + * + * @return The value. + */ + public String getValue() { + return value; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return SystemUtils.hashCode(getName(), getValue(), getParameters()); + } + + /** + * Sets the name. + * + * @param name The name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the list of parameters. + * + * @param parameters The list of parameters. + */ + public void setParameters(List parameters) { + synchronized (this) { + List r = getParameters(); + r.clear(); + r.addAll(parameters); + } + } + + /** + * Sets the value. + * + * @param value The value. + */ + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "Expectation [name=" + + name + + ", parameters=" + + parameters + + ", value=" + + value + + "]"; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Form.java b/org.restlet/src/main/java/org/restlet/data/Form.java index 34ea1aebf5..dc7632aa3d 100644 --- a/org.restlet/src/main/java/org/restlet/data/Form.java +++ b/org.restlet/src/main/java/org/restlet/data/Form.java @@ -1,285 +1,272 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.io.IOException; +import java.util.List; import org.restlet.engine.util.FormUtils; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.util.Series; -import java.io.IOException; -import java.util.List; - /** * Form which is a specialized modifiable list of parameters. - * + * * @author Jerome Louvel */ public class Form extends Series { - /** - * Empty constructor. - */ - public Form() { - super(Parameter.class); - } - - /** - * Constructor. - * - * @param initialCapacity The initial list capacity. - */ - public Form(int initialCapacity) { - super(Parameter.class, initialCapacity); - } + /** Empty constructor. */ + public Form() { + super(Parameter.class); + } - /** - * Constructor. - * - * @param delegate The delegate list. - */ - public Form(List delegate) { - super(Parameter.class, delegate); - } + /** + * Constructor. + * + * @param initialCapacity The initial list capacity. + */ + public Form(int initialCapacity) { + super(Parameter.class, initialCapacity); + } - /** - * Constructor. - * - * @param webForm The URL encoded Web form. - */ - public Form(Representation webForm) { - this(webForm, true); - } + /** + * Constructor. + * + * @param delegate The delegate list. + */ + public Form(List delegate) { + super(Parameter.class, delegate); + } - /** - * Constructor. - * - * @param webForm The URL encoded Web form. - */ - public Form(Representation webForm, boolean decode) { - this(); - FormUtils.parse(this, webForm, decode); - } + /** + * Constructor. + * + * @param webForm The URL encoded Web form. + */ + public Form(Representation webForm) { + this(webForm, true); + } - /** - * Constructor. Uses UTF-8 as the character set for encoding non-ASCII - * characters. - * - * @param queryString The Web form parameters as a string. - */ - public Form(String queryString) { - this(queryString, true); - } + /** + * Constructor. + * + * @param webForm The URL encoded Web form. + */ + public Form(Representation webForm, boolean decode) { + this(); + FormUtils.parse(this, webForm, decode); + } - /** - * Constructor. Uses UTF-8 as the character set for encoding non-ASCII - * characters. - * - * @param queryString The Web form parameters as a string. - * @param decode Indicates if the names and values should be automatically - * decoded. - */ - public Form(String queryString, boolean decode) { - this(queryString, CharacterSet.UTF_8, decode); - } + /** + * Constructor. Uses UTF-8 as the character set for encoding non-ASCII characters. + * + * @param queryString The Web form parameters as a string. + */ + public Form(String queryString) { + this(queryString, true); + } - /** - * Constructor. Uses UTF-8 as the character set for encoding non-ASCII - * characters. - * - * @param parametersString The parameters string to parse. - * @param separator The separator character to append between parameters. - */ - public Form(String parametersString, char separator) { - this(parametersString, separator, true); - } + /** + * Constructor. Uses UTF-8 as the character set for encoding non-ASCII characters. + * + * @param queryString The Web form parameters as a string. + * @param decode Indicates if the names and values should be automatically decoded. + */ + public Form(String queryString, boolean decode) { + this(queryString, CharacterSet.UTF_8, decode); + } - /** - * Constructor. Uses UTF-8 as the character set for encoding non-ASCII - * characters. - * - * @param parametersString The parameters string to parse. - * @param separator The separator character to append between parameters. - * @param decode Indicates if the names and values should be - * automatically decoded. - */ - public Form(String parametersString, char separator, boolean decode) { - this(parametersString, CharacterSet.UTF_8, separator, decode); - } + /** + * Constructor. Uses UTF-8 as the character set for encoding non-ASCII characters. + * + * @param parametersString The parameters string to parse. + * @param separator The separator character to append between parameters. + */ + public Form(String parametersString, char separator) { + this(parametersString, separator, true); + } - /** - * Constructor. - * - * @param queryString The Web form parameters as a string. - * @param characterSet The supported character encoding. - */ - public Form(String queryString, CharacterSet characterSet) { - this(queryString, characterSet, true); - } + /** + * Constructor. Uses UTF-8 as the character set for encoding non-ASCII characters. + * + * @param parametersString The parameters string to parse. + * @param separator The separator character to append between parameters. + * @param decode Indicates if the names and values should be automatically decoded. + */ + public Form(String parametersString, char separator, boolean decode) { + this(parametersString, CharacterSet.UTF_8, separator, decode); + } - /** - * Constructor. - * - * @param queryString The Web form parameters as a string. - * @param characterSet The supported character encoding. - * @param decode Indicates if the names and values should be automatically - * decoded. - */ - public Form(String queryString, CharacterSet characterSet, boolean decode) { - this(queryString, characterSet, '&', decode); - } + /** + * Constructor. + * + * @param queryString The Web form parameters as a string. + * @param characterSet The supported character encoding. + */ + public Form(String queryString, CharacterSet characterSet) { + this(queryString, characterSet, true); + } - /** - * Constructor. - * - * @param parametersString The parameters string to parse. - * @param characterSet The supported character encoding. - * @param separator The separator character to append between parameters. - */ - public Form(String parametersString, CharacterSet characterSet, char separator) { - this(parametersString, characterSet, separator, true); - } + /** + * Constructor. + * + * @param queryString The Web form parameters as a string. + * @param characterSet The supported character encoding. + * @param decode Indicates if the names and values should be automatically decoded. + */ + public Form(String queryString, CharacterSet characterSet, boolean decode) { + this(queryString, characterSet, '&', decode); + } - /** - * Constructor. - * - * @param parametersString The parameters string to parse. - * @param characterSet The supported character encoding. - * @param separator The separator character to append between parameters. - * @param decode Indicates if the names and values should be - * automatically decoded. - */ - public Form(String parametersString, CharacterSet characterSet, char separator, boolean decode) { - this(); - FormUtils.parse(this, parametersString, characterSet, decode, separator); - } + /** + * Constructor. + * + * @param parametersString The parameters string to parse. + * @param characterSet The supported character encoding. + * @param separator The separator character to append between parameters. + */ + public Form(String parametersString, CharacterSet characterSet, char separator) { + this(parametersString, characterSet, separator, true); + } - @Override - public Parameter createEntry(String name, String value) { - return new Parameter(name, value); - } + /** + * Constructor. + * + * @param parametersString The parameters string to parse. + * @param characterSet The supported character encoding. + * @param separator The separator character to append between parameters. + * @param decode Indicates if the names and values should be automatically decoded. + */ + public Form( + String parametersString, CharacterSet characterSet, char separator, boolean decode) { + this(); + FormUtils.parse(this, parametersString, characterSet, decode, separator); + } - /** - * Encodes the form using the standard URI encoding mechanism and the UTF-8 - * character set. - * - * @return The encoded form. - * @throws IOException - */ - public String encode() throws IOException { - return encode(CharacterSet.UTF_8); - } + @Override + public Parameter createEntry(String name, String value) { + return new Parameter(name, value); + } - /** - * URL encodes the form. The '&' character is used as a separator. - * - * @param characterSet The supported character encoding. - * @return The encoded form. - * @throws IOException - */ - public String encode(CharacterSet characterSet) throws IOException { - return encode(characterSet, '&'); - } + /** + * Encodes the form using the standard URI encoding mechanism and the UTF-8 character set. + * + * @return The encoded form. + * @throws IOException + */ + public String encode() throws IOException { + return encode(CharacterSet.UTF_8); + } - /** - * URL encodes the form. - * - * @param characterSet The supported character encoding. - * @param separator The separator character to append between parameters. - * @return The encoded form. - * @throws IOException - */ - public String encode(CharacterSet characterSet, char separator) throws IOException { - StringBuilder sb = new StringBuilder(); + /** + * URL encodes the form. The '&' character is used as a separator. + * + * @param characterSet The supported character encoding. + * @return The encoded form. + * @throws IOException + */ + public String encode(CharacterSet characterSet) throws IOException { + return encode(characterSet, '&'); + } - for (int i = 0; i < size(); i++) { - if (i > 0) { - sb.append(separator); - } + /** + * URL encodes the form. + * + * @param characterSet The supported character encoding. + * @param separator The separator character to append between parameters. + * @return The encoded form. + * @throws IOException + */ + public String encode(CharacterSet characterSet, char separator) throws IOException { + StringBuilder sb = new StringBuilder(); - get(i).encode(sb, characterSet); - } + for (int i = 0; i < size(); i++) { + if (i > 0) { + sb.append(separator); + } - return sb.toString(); - } + get(i).encode(sb, characterSet); + } - /** - * Formats the form as a matrix path string. Uses UTF-8 as the character set for - * encoding non-ASCII characters. - * - * @return The form as a matrix string. - * @see Matrix URIs by - * Tim Berners Lee - */ - public String getMatrixString() { - return getMatrixString(CharacterSet.UTF_8); - } + return sb.toString(); + } - /** - * Formats the form as a query string. - * - * @param characterSet The supported character encoding. - * @return The form as a matrix string. - * @see Matrix URIs by - * Tim Berners Lee - */ - public String getMatrixString(CharacterSet characterSet) { - try { - return encode(characterSet, ';'); - } catch (IOException ioe) { - return null; - } - } + /** + * Formats the form as a matrix path string. Uses UTF-8 as the character set for encoding + * non-ASCII characters. + * + * @return The form as a matrix string. + * @see Matrix URIs by Tim + * Berners-Lee + */ + public String getMatrixString() { + return getMatrixString(CharacterSet.UTF_8); + } - /** - * Formats the form as a query string. Uses UTF-8 as the character set for - * encoding non-ASCII characters. - * - * @return The form as a query string. - */ - public String getQueryString() { - return getQueryString(CharacterSet.UTF_8); - } + /** + * Formats the form as a query string. + * + * @param characterSet The supported character encoding. + * @return The form as a matrix string. + * @see Matrix URIs by Tim + * Berners-Lee + */ + public String getMatrixString(CharacterSet characterSet) { + try { + return encode(characterSet, ';'); + } catch (IOException ioe) { + return null; + } + } - /** - * Formats the form as a query string. - * - * @param characterSet The supported character encoding. - * @return The form as a query string. - */ - public String getQueryString(CharacterSet characterSet) { - try { - return encode(characterSet); - } catch (IOException ioe) { - return null; - } - } + /** + * Formats the form as a query string. Uses UTF-8 as the character set for encoding non-ASCII + * characters. + * + * @return The form as a query string. + */ + public String getQueryString() { + return getQueryString(CharacterSet.UTF_8); + } - /** - * Returns the form as a Web representation (MediaType.APPLICATION_WWW_FORM). - * Uses UTF-8 as the character set for encoding non-ASCII characters. - * - * @return The form as a Web representation. - */ - public Representation getWebRepresentation() { - return getWebRepresentation(CharacterSet.UTF_8); - } + /** + * Formats the form as a query string. + * + * @param characterSet The supported character encoding. + * @return The form as a query string. + */ + public String getQueryString(CharacterSet characterSet) { + try { + return encode(characterSet); + } catch (IOException ioe) { + return null; + } + } - /** - * Returns the form as a Web representation (MediaType.APPLICATION_WWW_FORM). - * - * @param characterSet The supported character encoding. - * @return The form as a Web representation. - */ - public Representation getWebRepresentation(CharacterSet characterSet) { - return new StringRepresentation(getQueryString(characterSet), MediaType.APPLICATION_WWW_FORM, null, - characterSet); - } + /** + * Returns the form as a Web representation (MediaType.APPLICATION_WWW_FORM). Uses UTF-8 as the + * character set for encoding non-ASCII characters. + * + * @return The form as a Web representation. + */ + public Representation getWebRepresentation() { + return getWebRepresentation(CharacterSet.UTF_8); + } + /** + * Returns the form as a Web representation (MediaType.APPLICATION_WWW_FORM). + * + * @param characterSet The supported character encoding. + * @return The form as a Web representation. + */ + public Representation getWebRepresentation(CharacterSet characterSet) { + return new StringRepresentation( + getQueryString(characterSet), MediaType.APPLICATION_WWW_FORM, null, characterSet); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Header.java b/org.restlet/src/main/java/org/restlet/data/Header.java index d050d51711..7d4528e82a 100644 --- a/org.restlet/src/main/java/org/restlet/data/Header.java +++ b/org.restlet/src/main/java/org/restlet/data/Header.java @@ -1,109 +1,103 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.Objects; import org.restlet.engine.util.SystemUtils; import org.restlet.util.NamedValue; -import java.util.Objects; - /** * Represents an HTTP header. - * + * * @author Jerome Louvel */ public class Header implements NamedValue { - /** The name. */ - private volatile String name; - - /** The value. */ - private volatile String value; - - /** - * Default constructor. - */ - public Header() { - } - - /** - * Constructor. - * - * @param name The header name. - * @param value The header value. - */ - public Header(String name, String value) { - super(); - this.name = name; - this.value = value; - } - - /** {@inheritDoc} */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Header that)) { - return false; - } - - return Objects.equals(getName(), that.getName()) - && Objects.equals(getValue(), that.getValue()); - } - - /** - * Returns the name. - * - * @return The name. - */ - public String getName() { - return name; - } - - /** - * Returns the value. - * - * @return The value. - */ - public String getValue() { - return value; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return SystemUtils.hashCode(getName(), getValue()); - } - - /** - * Sets the name. - * - * @param name The name. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Sets the value. - * - * @param value The value. - */ - public void setValue(String value) { - this.value = value; - } - - @Override - public String toString() { - return "[" + getName() + ": " + getValue() + "]"; - } - + /** The name. */ + private volatile String name; + + /** The value. */ + private volatile String value; + + /** Default constructor. */ + public Header() {} + + /** + * Constructor. + * + * @param name The header name. + * @param value The header value. + */ + public Header(String name, String value) { + super(); + this.name = name; + this.value = value; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Header that)) { + return false; + } + + return Objects.equals(getName(), that.getName()) + && Objects.equals(getValue(), that.getValue()); + } + + /** + * Returns the name. + * + * @return The name. + */ + public String getName() { + return name; + } + + /** + * Returns the value. + * + * @return The value. + */ + public String getValue() { + return value; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return SystemUtils.hashCode(getName(), getValue()); + } + + /** + * Sets the name. + * + * @param name The name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the value. + * + * @param value The value. + */ + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "[" + getName() + ": " + getValue() + "]"; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Language.java b/org.restlet/src/main/java/org/restlet/data/Language.java index 7c2a4810f4..8ab77dddb2 100644 --- a/org.restlet/src/main/java/org/restlet/data/Language.java +++ b/org.restlet/src/main/java/org/restlet/data/Language.java @@ -1,205 +1,207 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** - * Language used in representations and preferences. A language tag is composed - * of one or more parts: A primary language tag and a possibly empty series of - * sub-tags. When formatted as a string, parts are separated by hyphens. - * + * Language used in representations and preferences. A language tag is composed of one or more + * parts: A primary language tag and a possibly empty series of sub-tags. When formatted as a + * string, hyphens separate parts. + * * @author Jerome Louvel */ public final class Language extends Metadata { - /** All languages acceptable. */ - public static final Language ALL = new Language("*", "All languages"); - - /** - * The default language of the JVM. - * - * @see java.util.Locale#getDefault() - */ - public static final Language DEFAULT = new Language(java.util.Locale.getDefault().getLanguage()); - - /** English language. */ - public static final Language ENGLISH = new Language("en", "English language"); - - /** English language spoken in USA. */ - public static final Language ENGLISH_US = new Language("en-us", "English language in USA"); - - /** French language. */ - public static final Language FRENCH = new Language("fr", "French language"); - - /** French language spoken in France. */ - public static final Language FRENCH_FRANCE = new Language("fr-fr", "French language in France"); - - /** Spanish language. */ - public static final Language SPANISH = new Language("es", "Spanish language"); - - /** - * Returns the language associated to a name. If an existing constant exists - * then it is returned, otherwise a new instance is created. - * - * @param name The name. - * @return The associated language. - */ - public static Language valueOf(final String name) { - Language result = null; - - if ((name != null) && !name.isEmpty()) { - if (name.equalsIgnoreCase(ALL.getName())) { - result = ALL; - } else if (name.equalsIgnoreCase(ENGLISH.getName())) { - result = ENGLISH; - } else if (name.equalsIgnoreCase(ENGLISH_US.getName())) { - result = ENGLISH_US; - } else if (name.equalsIgnoreCase(FRENCH.getName())) { - result = FRENCH; - } else if (name.equalsIgnoreCase(FRENCH_FRANCE.getName())) { - result = FRENCH_FRANCE; - } else if (name.equalsIgnoreCase(SPANISH.getName())) { - result = SPANISH; - } else { - result = new Language(name); - } - } - - return result; - } - - /** The metadata main list of subtags taken from the metadata name. */ - private volatile List subTags; - - /** - * Constructor. - * - * @param name The name. - */ - public Language(final String name) { - this(name, "Language or range of languages"); - } - - /** - * Constructor. - * - * @param name The name. - * @param description The description. - */ - public Language(final String name, final String description) { - super(name, description); - this.subTags = null; - } - - /** {@inheritDoc} */ - @Override - public boolean equals(final Object object) { - return (object instanceof Language) && getName().equalsIgnoreCase(((Language) object).getName()); - } - - @Override - public Language getParent() { - Language result = null; - - if ((getSubTags() != null) && !getSubTags().isEmpty()) { - result = Language.valueOf(getPrimaryTag()); - } else { - result = equals(ALL) ? null : ALL; - } - - return result; - } - - /** - * Returns the primary tag. - * - * @return The primary tag. - */ - public String getPrimaryTag() { - final int separator = getName().indexOf('-'); - - if (separator == -1) { - return getName(); - } - - return getName().substring(0, separator); - } - - /** - * Returns the unmodifiable list of subtags. This list can be empty. - * - * @return The list of subtags for this language Tag. - */ - public List getSubTags() { - // Lazy initialization with double-check. - List v = this.subTags; - if (v == null) { - synchronized (this) { - v = this.subTags; - if (v == null) { - List tokens = new CopyOnWriteArrayList(); - if (getName() != null) { - final String[] tags = getName().split("-"); - if (tags.length > 0) { - for (int i = 1; i < tags.length; i++) { - tokens.add(tags[i]); - } - } - } - - this.subTags = v = Collections.unmodifiableList(tokens); - } - } - } - return v; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (getName() == null) ? 0 : getName().toLowerCase().hashCode(); - } - - /** - * Indicates if a given language is included in the current one. The test is - * true if both languages are equal or if the given language is within the range - * of the current one. For example, ALL includes all languages. A null language - * is considered as included into the current one. - *

- * Examples: - *

    - *
  • ENGLISH.includes(ENGLISH_US) returns true
  • - *
  • ENGLISH_US.includes(ENGLISH) returns false
  • - *
- * - * @param included The language to test for inclusion. - * @return True if the language type is included in the current one. - * @see #isCompatible(Metadata) - */ - public boolean includes(Metadata included) { - boolean result = equals(ALL) || (included == null) || equals(included); - - if (!result && (included instanceof Language includedLanguage)) { - - if (getPrimaryTag().equals(includedLanguage.getPrimaryTag())) { - // Both languages are different - if (getSubTags().equals(includedLanguage.getSubTags())) { - result = true; - } else if (getSubTags().isEmpty()) { - result = true; - } - } - } - - return result; - } + /** All languages acceptable. */ + public static final Language ALL = new Language("*", "All languages"); + + /** + * The default language of the JVM. + * + * @see java.util.Locale#getDefault() + */ + public static final Language DEFAULT = + new Language(java.util.Locale.getDefault().getLanguage()); + + /** English language. */ + public static final Language ENGLISH = new Language("en", "English language"); + + /** English language spoken in the USA. */ + public static final Language ENGLISH_US = new Language("en-us", "English language in USA"); + + /** French language. */ + public static final Language FRENCH = new Language("fr", "French language"); + + /** French language spoken in France. */ + public static final Language FRENCH_FRANCE = new Language("fr-fr", "French language in France"); + + /** Spanish language. */ + public static final Language SPANISH = new Language("es", "Spanish language"); + + /** + * Returns the language associated with a name. If an existing constant exists, then it is + * returned; otherwise a new instance is created. + * + * @param name The name. + * @return The associated language. + */ + public static Language valueOf(final String name) { + Language result = null; + + if ((name != null) && !name.isEmpty()) { + if (name.equalsIgnoreCase(ALL.getName())) { + result = ALL; + } else if (name.equalsIgnoreCase(ENGLISH.getName())) { + result = ENGLISH; + } else if (name.equalsIgnoreCase(ENGLISH_US.getName())) { + result = ENGLISH_US; + } else if (name.equalsIgnoreCase(FRENCH.getName())) { + result = FRENCH; + } else if (name.equalsIgnoreCase(FRENCH_FRANCE.getName())) { + result = FRENCH_FRANCE; + } else if (name.equalsIgnoreCase(SPANISH.getName())) { + result = SPANISH; + } else { + result = new Language(name); + } + } + + return result; + } + + /** The metadata main list of subtag taken from the metadata name. */ + private volatile List subTags; + + /** + * Constructor. + * + * @param name The name. + */ + public Language(final String name) { + this(name, "Language or range of languages"); + } + + /** + * Constructor. + * + * @param name The name. + * @param description The description. + */ + public Language(final String name, final String description) { + super(name, description); + this.subTags = null; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Language that)) { + return false; + } + return getName().equalsIgnoreCase(that.getName()); + } + + @Override + public Language getParent() { + final Language result; + + if ((getSubTags() != null) && !getSubTags().isEmpty()) { + result = Language.valueOf(getPrimaryTag()); + } else { + result = equals(ALL) ? null : ALL; + } + + return result; + } + + /** + * Returns the primary tag. + * + * @return The primary tag. + */ + public String getPrimaryTag() { + final int separator = getName().indexOf('-'); + + if (separator == -1) { + return getName(); + } + + return getName().substring(0, separator); + } + + /** + * Returns the unmodifiable list of subtag. This list can be empty. + * + * @return The list of subtag for this language Tag. + */ + public List getSubTags() { + // Lazy initialization with double-check. + List v = this.subTags; + if (v == null) { + synchronized (this) { + v = this.subTags; + if (v == null) { + List tokens = new CopyOnWriteArrayList<>(); + if (getName() != null) { + final String[] tags = getName().split("-"); + if (tags.length > 0) { + tokens.addAll(Arrays.asList(tags).subList(1, tags.length)); + } + } + + this.subTags = v = Collections.unmodifiableList(tokens); + } + } + } + return v; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (getName() == null) ? 0 : getName().toLowerCase().hashCode(); + } + + /** + * Indicates if a given language is included in the current one. The test is true if both + * languages are equal or if the given language is within the range of the current one. For + * example, ALL includes all languages. A null language is considered as included in the current + * one. + * + *

Examples: + * + *

    + *
  • ENGLISH.includes(ENGLISH_US) returns true + *
  • ENGLISH_US.includes(ENGLISH) returns false + *
+ * + * @param included The language to test for inclusion. + * @return True if the language type is included in the current one. + * @see #isCompatible(Metadata) + */ + public boolean includes(Metadata included) { + if (equals(ALL) || (included == null) || equals(included)) { + return true; + } + + if ((included instanceof Language includedLanguage) + && getPrimaryTag().equals(includedLanguage.getPrimaryTag())) { + // Both languages are different + return getSubTags().equals(includedLanguage.getSubTags()) || getSubTags().isEmpty(); + } + + return false; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/LocalReference.java b/org.restlet/src/main/java/org/restlet/data/LocalReference.java index 0f6a74653c..be4cb8d4aa 100644 --- a/org.restlet/src/main/java/org/restlet/data/LocalReference.java +++ b/org.restlet/src/main/java/org/restlet/data/LocalReference.java @@ -1,187 +1,183 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; import java.io.File; /** - * Reference to a local resource. It has helper methods for the three following - * schemes: {@link Protocol#CLAP}, {@link Protocol#FILE}, {@link Protocol#JAR} - * and {@link Protocol#RIAP}. - * + * Reference to a local resource. It has helper methods for the three following schemes: {@link + * Protocol#CLAP}, {@link Protocol#FILE}, {@link Protocol#JAR} and {@link Protocol#RIAP}. + * * @author Jerome Louvel */ public final class LocalReference extends Reference { - /** - * The resources will be resolved from the classloader associated with the local - * class. This is the same as the {@link #CLAP_CLASS} authority. Examples: - * clap:///rootPkg/subPkg/myClass.class or clap:///rootPkg/file.html - * - * @see java.lang.Class#getClassLoader() - */ - public static final int CLAP_DEFAULT = 0; - - /** - * The resources will be resolved from the classloader associated with the local - * class. This is the default CLAP authority. Examples: - * clap://class/rootPkg/subPkg/myClass.class or clap://class/rootPkg/file.html - * or clap:///rootPkg/file.html - * - * @see java.lang.Class#getClassLoader() - */ - public static final int CLAP_CLASS = 1; - - /** - * The resources will be resolved from the system's classloader. Examples: - * clap://system/rootPkg/subPkg/myClass.class or clap://system/rootPkg/file.html - * - * @see java.lang.ClassLoader#getSystemClassLoader() - */ - public static final int CLAP_SYSTEM = 2; - - /** - * The resources will be resolved from the current thread's classloader. - * Examples: clap://thread/rootPkg/subPkg/myClass.class or - * clap://thread/rootPkg/file.html - * - * @see java.lang.Thread#getContextClassLoader() - */ - public static final int CLAP_THREAD = 3; - - /** - * The resources will be resolved from the current application's root Restlet. - * Example riap://application/myPath/myResource - */ - public static final int RIAP_APPLICATION = 4; - - /** - * The resources will be resolved from the current component's internal - * (private) router. Example riap://component/myAppPath/myResource - */ - public static final int RIAP_COMPONENT = 5; - - /** - * The resources will be resolved from the current component's virtual host. - * Example riap://host/myAppPath/myResource - */ - public static final int RIAP_HOST = 6; - - /** - * Constructor. - * - * @param pkg The package to identify. - */ - public static LocalReference createClapReference(Package pkg) { - return createClapReference(CLAP_DEFAULT, pkg); - } - - /** - * Constructor for CLAP URIs to a given package. - * - * @param authorityType The authority type for the resource path. - * @param pkg The package to identify. - */ - public static LocalReference createClapReference(int authorityType, Package pkg) { - String pkgPath = pkg.getName().replaceAll("\\.", "/"); - return new LocalReference("clap://" + getAuthorityName(authorityType) + "/" + pkgPath); - } - - /** - * Constructor. - * - * @param path The resource path. - */ - public static LocalReference createClapReference(String path) { - return createClapReference(CLAP_DEFAULT, path); - } - - /** - * Constructor. - * - * @param authorityType The authority type for the resource path. - * @param path The resource path. - */ - public static LocalReference createClapReference(int authorityType, String path) { - return new LocalReference("clap://" + getAuthorityName(authorityType) + path); - } - - /** - * Constructor. - * - * @param file The file whose path must be used. - * @return The new local reference. - * @see #createFileReference(String) - */ - public static LocalReference createFileReference(File file) { - return createFileReference(file.getAbsolutePath()); - } - - /** - * Constructor. - * - * @param filePath The local file path. - * @see #createFileReference(String, String) - */ - public static LocalReference createFileReference(String filePath) { - return createFileReference("", filePath); - } - - /** - * Constructor. - * - * @param hostName The authority (can be a host name or the special "localhost" - * or an empty value). - * @param filePath The file path. - */ - public static LocalReference createFileReference(String hostName, String filePath) { - return new LocalReference("file://" + hostName + "/" + normalizePath(filePath)); - } - - /** - * Constructor. - * - * @param jarFile The JAR file reference. - * @param entryPath The entry path inside the JAR file. - */ - public static LocalReference createJarReference(Reference jarFile, String entryPath) { - return new LocalReference("jar:" + jarFile.getTargetRef().toString() + "!/" + entryPath); - } - - /** - * Constructor. - * - * @param authorityType The authority type for the resource path. - * @param path The resource path. - */ - public static LocalReference createRiapReference(int authorityType, String path) { - return new LocalReference("riap://" + getAuthorityName(authorityType) + path); - } - - /** - * Constructor. - * - * @param zipFile The Zip file reference. - * @param entryPath The entry path inside the Zip file. - */ - public static LocalReference createZipReference(Reference zipFile, String entryPath) { - return new LocalReference("zip:" + zipFile.getTargetRef().toString() + "!/" + entryPath); - } - - /** - * Returns an authority name. - * - * @param authority The authority. - * @return The name. - */ - public static String getAuthorityName(int authority) { - return switch (authority) { + /** + * The resources will be resolved from the classloader associated with the local class. This is + * the same as the {@link #CLAP_CLASS} authority. Examples: clap:///rootPkg/subPkg/myClass.class + * or clap:///rootPkg/file.html + * + * @see java.lang.Class#getClassLoader() + */ + public static final int CLAP_DEFAULT = 0; + + /** + * The resources will be resolved from the classloader associated with the local class. This is + * the default CLAP authority. Examples: clap://class/rootPkg/subPkg/myClass.class or + * clap://class/rootPkg/file.html or clap:///rootPkg/file.html + * + * @see java.lang.Class#getClassLoader() + */ + public static final int CLAP_CLASS = 1; + + /** + * The resources will be resolved from the system's classloader. Examples: + * clap://system/rootPkg/subPkg/myClass.class or clap://system/rootPkg/file.html + * + * @see java.lang.ClassLoader#getSystemClassLoader() + */ + public static final int CLAP_SYSTEM = 2; + + /** + * The resources will be resolved from the current thread's classloader. Examples: + * clap://thread/rootPkg/subPkg/myClass.class or clap://thread/rootPkg/file.html + * + * @see java.lang.Thread#getContextClassLoader() + */ + public static final int CLAP_THREAD = 3; + + /** + * The resources will be resolved from the current application's root Restlet. Example + * riap://application/myPath/myResource + */ + public static final int RIAP_APPLICATION = 4; + + /** + * The resources will be resolved from the current component's internal (private) router. + * Example riap://component/myAppPath/myResource + */ + public static final int RIAP_COMPONENT = 5; + + /** + * The resources will be resolved from the current component's virtual host. Example + * riap://host/myAppPath/myResource + */ + public static final int RIAP_HOST = 6; + + /** + * Constructor. + * + * @param pkg The package to identify. + */ + public static LocalReference createClapReference(Package pkg) { + return createClapReference(CLAP_DEFAULT, pkg); + } + + /** + * Constructor for CLAP URIs to a given package. + * + * @param authorityType The authority type for the resource path. + * @param pkg The package to identify. + */ + public static LocalReference createClapReference(int authorityType, Package pkg) { + String pkgPath = pkg.getName().replace(".", "/"); + return new LocalReference("clap://" + getAuthorityName(authorityType) + "/" + pkgPath); + } + + /** + * Constructor. + * + * @param path The resource path. + */ + public static LocalReference createClapReference(String path) { + return createClapReference(CLAP_DEFAULT, path); + } + + /** + * Constructor. + * + * @param authorityType The authority type for the resource path. + * @param path The resource path. + */ + public static LocalReference createClapReference(int authorityType, String path) { + return new LocalReference("clap://" + getAuthorityName(authorityType) + path); + } + + /** + * Constructor. + * + * @param file The file whose path must be used. + * @return The new local reference. + * @see #createFileReference(String) + */ + public static LocalReference createFileReference(File file) { + return createFileReference(file.getAbsolutePath()); + } + + /** + * Constructor. + * + * @param filePath The local file path. + * @see #createFileReference(String, String) + */ + public static LocalReference createFileReference(String filePath) { + return createFileReference("", filePath); + } + + /** + * Constructor. + * + * @param hostName The authority (can be a host name or the special "localhost" or an empty + * value). + * @param filePath The file path. + */ + public static LocalReference createFileReference(String hostName, String filePath) { + return new LocalReference("file://" + hostName + "/" + normalizePath(filePath)); + } + + /** + * Constructor. + * + * @param jarFile The JAR file reference. + * @param entryPath The entry path inside the JAR file. + */ + public static LocalReference createJarReference(Reference jarFile, String entryPath) { + return new LocalReference("jar:" + jarFile.getTargetRef().toString() + "!/" + entryPath); + } + + /** + * Constructor. + * + * @param authorityType The authority type for the resource path. + * @param path The resource path. + */ + public static LocalReference createRiapReference(int authorityType, String path) { + return new LocalReference("riap://" + getAuthorityName(authorityType) + path); + } + + /** + * Constructor. + * + * @param zipFile The Zip file reference. + * @param entryPath The entry path inside the Zip file. + */ + public static LocalReference createZipReference(Reference zipFile, String entryPath) { + return new LocalReference("zip:" + zipFile.getTargetRef().toString() + "!/" + entryPath); + } + + /** + * Returns an authority name. + * + * @param authority The authority. + * @return The name. + */ + public static String getAuthorityName(int authority) { + return switch (authority) { case CLAP_DEFAULT -> ""; case CLAP_CLASS -> "class"; case CLAP_SYSTEM -> "system"; @@ -191,195 +187,194 @@ public static String getAuthorityName(int authority) { case RIAP_HOST -> "host"; default -> null; }; - } - - /** - * Localize a path by converting all the separator characters to the - * system-dependent separator character. - * - * @param path The path to localize. - * @return The localized path. - */ - public static String localizePath(String path) { - final StringBuilder result = new StringBuilder(); - char nextChar; - for (int i = 0; i < path.length(); i++) { - nextChar = path.charAt(i); - if (nextChar == '/') { - // Convert the URI separator to - // the system dependent path separator - result.append(File.separatorChar); - } else { - result.append(nextChar); - } - } - - return result.toString(); - } - - /** - * Normalize a path by converting all the system-dependent separator characters - * to the standard '/' separator character. - * - * @param path The path to normalize. - * @return The normalize path. - */ - public static String normalizePath(String path) { - final StringBuilder result = new StringBuilder(); - char nextChar; - for (int i = 0; i < path.length(); i++) { - nextChar = path.charAt(i); - if ((nextChar == File.separatorChar)) { - // Convert the Windows style path separator - // to the standard path separator - result.append('/'); - } else if (!isUnreserved(nextChar)) { - result.append(Reference.encode("" + nextChar)); - } else { - result.append(nextChar); - } - } - - return result.toString(); - } - - /** - * Constructor. - * - * @param localRef The local reference. - */ - public LocalReference(Reference localRef) { - super(localRef.getTargetRef().toString()); - } - - /** - * Constructor. - * - * @param localUri The local URI. - */ - public LocalReference(String localUri) { - super(localUri); - } - - /** - * Returns the type of authority. - * - * @return The type of authority. - */ - public int getClapAuthorityType() { - int result = 0; - - if (Protocol.CLAP.equals(getSchemeProtocol())) { - final String authority = getAuthority(); - - if (authority != null) { - if (authority.equalsIgnoreCase(getAuthorityName(CLAP_CLASS))) { - result = CLAP_CLASS; - } else if (authority.equalsIgnoreCase(getAuthorityName(CLAP_SYSTEM))) { - result = CLAP_SYSTEM; - } else if (authority.equalsIgnoreCase(getAuthorityName(CLAP_THREAD))) { - result = CLAP_THREAD; - } else { - result = CLAP_DEFAULT; - } - } - } - - return result; - } - - /** - * Gets the local file corresponding to the reference. Only URIs referring to - * the "localhost" or to an empty authority are supported. - * - * @return The local file corresponding to the reference. - */ - public File getFile() { - File result = null; - - if (Protocol.FILE.equals(getSchemeProtocol())) { - final String hostName = getAuthority(); - - if ((hostName == null) || hostName.isEmpty() || hostName.equalsIgnoreCase("localhost")) { - final String filePath = Reference.decode(getPath()); - result = new File(filePath); - } else { - throw new RuntimeException("Can't resolve files on remote host machines"); - } - } - - return result; - } - - /** - * Returns the JAR entry path. - * - * @return The JAR entry path. - */ - public String getJarEntryPath() { - String result = null; - - if (Protocol.JAR.equals(getSchemeProtocol())) { - final String ssp = getSchemeSpecificPart(); - - if (ssp != null) { - final int separatorIndex = ssp.indexOf("!/"); - - if (separatorIndex != -1) { - result = ssp.substring(separatorIndex + 2); - } - } - } - - return result; - } - - /** - * Returns the JAR file reference. - * - * @return The JAR file reference. - */ - public Reference getJarFileRef() { - Reference result = null; - - if (Protocol.JAR.equals(getSchemeProtocol())) { - final String ssp = getSchemeSpecificPart(); - - if (ssp != null) { - final int separatorIndex = ssp.indexOf("!/"); - - if (separatorIndex != -1) { - result = new Reference(ssp.substring(0, separatorIndex)); - } - } - } - - return result; - } - - /** - * Returns the type of authority. - * - * @return The type of authority. - */ - public int getRiapAuthorityType() { - int result = 0; - - if (Protocol.RIAP.equals(getSchemeProtocol())) { - final String authority = getAuthority(); - - if (authority != null) { - if (authority.equalsIgnoreCase(getAuthorityName(RIAP_APPLICATION))) { - result = RIAP_APPLICATION; - } else if (authority.equalsIgnoreCase(getAuthorityName(RIAP_COMPONENT))) { - result = RIAP_COMPONENT; - } else if (authority.equalsIgnoreCase(getAuthorityName(RIAP_HOST))) { - result = RIAP_HOST; - } - } - } - - return result; - } - + } + + /** + * Localize a path by converting all the separator characters to the system-dependent separator + * character. + * + * @param path The path to localize. + * @return The localized path. + */ + public static String localizePath(String path) { + final StringBuilder result = new StringBuilder(); + char nextChar; + for (int i = 0; i < path.length(); i++) { + nextChar = path.charAt(i); + if (nextChar == '/') { + // Convert the URI separator to + // the system dependent path separator + result.append(File.separatorChar); + } else { + result.append(nextChar); + } + } + + return result.toString(); + } + + /** + * Normalize a path by converting all the system-dependent separator characters to the standard + * '/' separator character. + * + * @param path The path to normalize. + * @return The normalized path. + */ + public static String normalizePath(String path) { + final StringBuilder result = new StringBuilder(); + char nextChar; + for (int i = 0; i < path.length(); i++) { + nextChar = path.charAt(i); + if ((nextChar == File.separatorChar)) { + // Convert the Windows style path separator + // to the standard path separator + result.append('/'); + } else if (!isUnreserved(nextChar)) { + result.append(Reference.encode("" + nextChar)); + } else { + result.append(nextChar); + } + } + + return result.toString(); + } + + /** + * Constructor. + * + * @param localRef The local reference. + */ + public LocalReference(Reference localRef) { + super(localRef.getTargetRef().toString()); + } + + /** + * Constructor. + * + * @param localUri The local URI. + */ + public LocalReference(String localUri) { + super(localUri); + } + + /** + * Returns the type of authority. + * + * @return The type of authority. + */ + public int getClapAuthorityType() { + int result = CLAP_DEFAULT; + + if (Protocol.CLAP.equals(getSchemeProtocol())) { + final String authority = getAuthority(); + + if (authority != null) { + if (authority.equalsIgnoreCase(getAuthorityName(CLAP_CLASS))) { + result = CLAP_CLASS; + } else if (authority.equalsIgnoreCase(getAuthorityName(CLAP_SYSTEM))) { + result = CLAP_SYSTEM; + } else if (authority.equalsIgnoreCase(getAuthorityName(CLAP_THREAD))) { + result = CLAP_THREAD; + } + } + } + + return result; + } + + /** + * Gets the local file corresponding to the reference. Only URIs referring to the "localhost" or + * to an empty authority are supported. + * + * @return The local file corresponding to the reference. + */ + public File getFile() { + File result = null; + + if (Protocol.FILE.equals(getSchemeProtocol())) { + final String hostName = getAuthority(); + + if ((hostName == null) + || hostName.isEmpty() + || hostName.equalsIgnoreCase("localhost")) { + final String filePath = Reference.decode(getPath()); + result = new File(filePath); + } else { + throw new RuntimeException("Can't resolve files on remote host machines"); + } + } + + return result; + } + + /** + * Returns the JAR entry path. + * + * @return The JAR entry path. + */ + public String getJarEntryPath() { + String result = null; + + if (Protocol.JAR.equals(getSchemeProtocol())) { + final String ssp = getSchemeSpecificPart(); + + if (ssp != null) { + final int separatorIndex = ssp.indexOf("!/"); + + if (separatorIndex != -1) { + result = ssp.substring(separatorIndex + 2); + } + } + } + + return result; + } + + /** + * Returns the JAR file reference. + * + * @return The JAR file reference. + */ + public Reference getJarFileRef() { + Reference result = null; + + if (Protocol.JAR.equals(getSchemeProtocol())) { + final String ssp = getSchemeSpecificPart(); + + if (ssp != null) { + final int separatorIndex = ssp.indexOf("!/"); + + if (separatorIndex != -1) { + result = new Reference(ssp.substring(0, separatorIndex)); + } + } + } + + return result; + } + + /** + * Returns the type of authority. + * + * @return The type of authority. + */ + public int getRiapAuthorityType() { + int result = 0; + + if (Protocol.RIAP.equals(getSchemeProtocol())) { + final String authority = getAuthority(); + + if (authority != null) { + if (authority.equalsIgnoreCase(getAuthorityName(RIAP_APPLICATION))) { + result = RIAP_APPLICATION; + } else if (authority.equalsIgnoreCase(getAuthorityName(RIAP_COMPONENT))) { + result = RIAP_COMPONENT; + } else if (authority.equalsIgnoreCase(getAuthorityName(RIAP_HOST))) { + result = RIAP_HOST; + } + } + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/MediaType.java b/org.restlet/src/main/java/org/restlet/data/MediaType.java index 0e2126df3a..185fb30786 100644 --- a/org.restlet/src/main/java/org/restlet/data/MediaType.java +++ b/org.restlet/src/main/java/org/restlet/data/MediaType.java @@ -1,538 +1,516 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.engine.header.HeaderWriter; import org.restlet.engine.util.StringUtils; import org.restlet.engine.util.SystemUtils; import org.restlet.util.Series; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Level; - /** - * Metadata used to specify the format of representations. The - * {@link #getName()} method returns a full String representation of the media - * type including the parameters. - * - * @see MIME types on Wikipedia + * Metadata used to specify the format of representations. The {@link #getName()} method returns a + * full String representation of the media type including the parameters. + * * @author Jerome Louvel + * @see MIME types on Wikipedia */ public final class MediaType extends Metadata { /** * Illegal ASCII characters as defined in RFC 1521.
* Keep the underscore for the ordering - * + * * @see RFC 1521 */ - private static final String _TSPECIALS = "()<>@,;:/[]?=\\\""; + private static final String TSPECIALS = "()<>@,;:/[]?=\\\""; /** - * The known media types registered with {@link #register(String, String)}, - * retrievable using {@link #valueOf(String)}.
- * Keep the underscore for the ordering. + * The known media types registered with {@link #register(String, String)}, retrievable using + * {@link #valueOf(String)}. */ - private static volatile Map _types = null; + private static final Map TYPES = new ConcurrentHashMap<>(); public static final MediaType ALL = register("*/*", "All media"); - public static final MediaType APPLICATION_ALL = register("application/*", - "All application documents"); - - public static final MediaType APPLICATION_ALL_JSON = register( - "application/*+json", "All application/*+json documents"); - - public static final MediaType APPLICATION_ALL_XML = register( - "application/*+xml", "All application/*+xml documents"); + public static final MediaType APPLICATION_ALL = + register("application/*", "All application documents"); - public static final MediaType APPLICATION_ATOM = register( - "application/atom+xml", "Atom document"); + public static final MediaType APPLICATION_ALL_JSON = + register("application/*+json", "All application/*+json documents"); - public static final MediaType APPLICATION_ATOMPUB_CATEGORY = register( - "application/atomcat+xml", "Atom category document"); + public static final MediaType APPLICATION_ALL_XML = + register("application/*+xml", "All application/*+xml documents"); - public static final MediaType APPLICATION_ATOMPUB_SERVICE = register( - "application/atomsvc+xml", "Atom service document"); + public static final MediaType APPLICATION_ATOM = + register("application/atom+xml", "Atom document"); - public static final MediaType APPLICATION_CAB = register( - "application/vnd.ms-cab-compressed", "Microsoft Cabinet archive"); + public static final MediaType APPLICATION_ATOMPUB_CATEGORY = + register("application/atomcat+xml", "Atom category document"); - public static final MediaType APPLICATION_COMPRESS = register( - "application/x-compress", "Compressed file"); + public static final MediaType APPLICATION_ATOMPUB_SERVICE = + register("application/atomsvc+xml", "Atom service document"); - public static final MediaType APPLICATION_ECORE = register( - "application/x-ecore+xmi+xml", "EMOF ECore metamodel"); + public static final MediaType APPLICATION_CAB = + register("application/vnd.ms-cab-compressed", "Microsoft Cabinet archive"); - public static final MediaType APPLICATION_EXCEL = register( - "application/vnd.ms-excel", "Microsoft Excel document"); + public static final MediaType APPLICATION_COMPRESS = + register("application/x-compress", "Compressed file"); - public static final MediaType APPLICATION_FLASH = register( - "application/x-shockwave-flash", "Shockwave Flash object"); + public static final MediaType APPLICATION_ECORE = + register("application/x-ecore+xmi+xml", "EMOF ECore metamodel"); - public static final MediaType APPLICATION_GNU_TAR = register( - "application/x-gtar", "GNU Tar archive"); + public static final MediaType APPLICATION_EXCEL = + register("application/vnd.ms-excel", "Microsoft Excel document"); - public static final MediaType APPLICATION_GNU_ZIP = register( - "application/x-gzip", "GNU Zip archive"); + public static final MediaType APPLICATION_FLASH = + register("application/x-shockwave-flash", "Shockwave Flash object"); - public static final MediaType APPLICATION_HTTP_COOKIES = register( - "application/x-http-cookies", "HTTP cookies"); + public static final MediaType APPLICATION_GNU_TAR = + register("application/x-gtar", "GNU Tar archive"); - public static final MediaType APPLICATION_JAVA = register( - "application/java", "Java class"); + public static final MediaType APPLICATION_GNU_ZIP = + register("application/x-gzip", "GNU Zip archive"); - public static final MediaType APPLICATION_JAVA_ARCHIVE = register( - "application/java-archive", "Java archive"); + public static final MediaType APPLICATION_HTTP_COOKIES = + register("application/x-http-cookies", "HTTP cookies"); - public static final MediaType APPLICATION_JAVA_OBJECT = register( - "application/x-java-serialized-object", "Java serialized object"); + public static final MediaType APPLICATION_JAVA = register("application/java", "Java class"); - public static final MediaType APPLICATION_JAVA_OBJECT_GWT = register( - "text/x-gwt-rpc", "Java serialized object (using GWT-RPC encoder)"); + public static final MediaType APPLICATION_JAVA_ARCHIVE = + register("application/java-archive", "Java archive"); - public static final MediaType APPLICATION_JAVA_OBJECT_XML = register( - "text/x-gwt-rpc+xml", - "Java serialized object (using JavaBeans XML encoder)"); + public static final MediaType APPLICATION_JAVA_OBJECT = + register("application/x-java-serialized-object", "Java serialized object"); - public static final MediaType APPLICATION_JAVASCRIPT = register( - "application/x-javascript", "Javascript document"); + public static final MediaType APPLICATION_JAVA_OBJECT_GWT = + register("text/x-gwt-rpc", "Java serialized object (using GWT-RPC encoder)"); - public static final MediaType APPLICATION_JNLP = register( - "application/x-java-jnlp-file", "JNLP"); + public static final MediaType APPLICATION_JAVA_OBJECT_XML = + register("text/x-gwt-rpc+xml", "Java serialized object (using JavaBeans XML encoder)"); - public static final MediaType APPLICATION_JSON = register( - "application/json", "JavaScript Object Notation document"); + public static final MediaType APPLICATION_JAVASCRIPT = + register("application/x-javascript", "Javascript document"); - public static final MediaType APPLICATION_JSON_ACTIVITY = register( - "application/activity+json", "Activity Streams JSON document"); + public static final MediaType APPLICATION_JNLP = + register("application/x-java-jnlp-file", "JNLP"); - public static final MediaType APPLICATION_JSON_PATCH = register( - "application/json-patch", "JSON patch document"); + public static final MediaType APPLICATION_JSON = + register("application/json", "JavaScript Object Notation document"); - public static final MediaType APPLICATION_JSON_SMILE = register( - "application/x-json-smile", - "JavaScript Object Notation smile document"); + public static final MediaType APPLICATION_JSON_ACTIVITY = + register("application/activity+json", "Activity Streams JSON document"); - public static final MediaType APPLICATION_KML = register( - "application/vnd.google-earth.kml+xml", - "Google Earth/Maps KML document"); + public static final MediaType APPLICATION_JSON_PATCH = + register("application/json-patch", "JSON patch document"); - public static final MediaType APPLICATION_KMZ = register( - "application/vnd.google-earth.kmz", - "Google Earth/Maps KMZ document"); + public static final MediaType APPLICATION_JSON_SMILE = + register("application/x-json-smile", "JavaScript Object Notation smile document"); - public static final MediaType APPLICATION_LATEX = register( - "application/x-latex", "LaTeX"); + public static final MediaType APPLICATION_KML = + register("application/vnd.google-earth.kml+xml", "Google Earth/Maps KML document"); - public static final MediaType APPLICATION_MAC_BINHEX40 = register( - "application/mac-binhex40", "Mac binhex40"); + public static final MediaType APPLICATION_KMZ = + register("application/vnd.google-earth.kmz", "Google Earth/Maps KMZ document"); - public static final MediaType APPLICATION_MATHML = register( - "application/mathml+xml", "MathML XML document"); + public static final MediaType APPLICATION_LATEX = register("application/x-latex", "LaTeX"); - public static final MediaType APPLICATION_MSML = register( - "application/msml+xml", "Media Server Markup Language"); + public static final MediaType APPLICATION_MAC_BINHEX40 = + register("application/mac-binhex40", "Mac binhex40"); - public static final MediaType APPLICATION_MSOFFICE_DOCM = register( - "application/vnd.ms-word.document.macroEnabled.12", - "Office Word 2007 macro-enabled document"); + public static final MediaType APPLICATION_MATHML = + register("application/mathml+xml", "MathML XML document"); - public static final MediaType APPLICATION_MSOFFICE_DOCX = register( - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "Microsoft Office Word 2007 document"); + public static final MediaType APPLICATION_MSML = + register("application/msml+xml", "Media Server Markup Language"); - public static final MediaType APPLICATION_MSOFFICE_DOTM = register( - "application/vnd.ms-word.template.macroEnabled.12", - "Office Word 2007 macro-enabled document template"); + public static final MediaType APPLICATION_MSOFFICE_DOCM = + register( + "application/vnd.ms-word.document.macroEnabled.12", + "Office Word 2007 macro-enabled document"); - public static final MediaType APPLICATION_MSOFFICE_DOTX = register( - "application/vnd.openxmlformats-officedocument.wordprocessingml.template", - "Office Word 2007 template"); + public static final MediaType APPLICATION_MSOFFICE_DOCX = + register( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "Microsoft Office Word 2007 document"); - public static final MediaType APPLICATION_MSOFFICE_ONETOC = register( - "application/onenote", "Microsoft Office OneNote 2007 TOC"); + public static final MediaType APPLICATION_MSOFFICE_DOTM = + register( + "application/vnd.ms-word.template.macroEnabled.12", + "Office Word 2007 macro-enabled document template"); - public static final MediaType APPLICATION_MSOFFICE_ONETOC2 = register( - "application/onenote", "Office OneNote 2007 TOC"); + public static final MediaType APPLICATION_MSOFFICE_DOTX = + register( + "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + "Office Word 2007 template"); - public static final MediaType APPLICATION_MSOFFICE_POTM = register( - "application/vnd.ms-powerpoint.template.macroEnabled.12", - "Office PowerPoint 2007 macro-enabled presentation template"); + public static final MediaType APPLICATION_MSOFFICE_ONETOC = + register("application/onenote", "Microsoft Office OneNote 2007 TOC"); - public static final MediaType APPLICATION_MSOFFICE_POTX = register( - "application/vnd.openxmlformats-officedocument.presentationml.template", - "Office PowerPoint 2007 template"); + public static final MediaType APPLICATION_MSOFFICE_ONETOC2 = + register("application/onenote", "Office OneNote 2007 TOC"); - public static final MediaType APPLICATION_MSOFFICE_PPAM = register( - "application/vnd.ms-powerpoint.addin.macroEnabled.12", - "Office PowerPoint 2007 add-in"); + public static final MediaType APPLICATION_MSOFFICE_POTM = + register( + "application/vnd.ms-powerpoint.template.macroEnabled.12", + "Office PowerPoint 2007 macro-enabled presentation template"); - public static final MediaType APPLICATION_MSOFFICE_PPSM = register( - "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", - "Office PowerPoint 2007 macro-enabled slide show"); + public static final MediaType APPLICATION_MSOFFICE_POTX = + register( + "application/vnd.openxmlformats-officedocument.presentationml.template", + "Office PowerPoint 2007 template"); - public static final MediaType APPLICATION_MSOFFICE_PPSX = register( - "application/vnd.openxmlformats-officedocument.presentationml.slideshow", - "Office PowerPoint 2007 slide show"); + public static final MediaType APPLICATION_MSOFFICE_PPAM = + register( + "application/vnd.ms-powerpoint.addin.macroEnabled.12", + "Office PowerPoint 2007 add-in"); - public static final MediaType APPLICATION_MSOFFICE_PPTM = register( - "application/vnd.ms-powerpoint.presentation.macroEnabled.12", - "Office PowerPoint 2007 macro-enabled presentation"); + public static final MediaType APPLICATION_MSOFFICE_PPSM = + register( + "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", + "Office PowerPoint 2007 macro-enabled slide show"); - public static final MediaType APPLICATION_MSOFFICE_PPTX = register( - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "Microsoft Office PowerPoint 2007 presentation"); + public static final MediaType APPLICATION_MSOFFICE_PPSX = + register( + "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + "Office PowerPoint 2007 slide show"); - public static final MediaType APPLICATION_MSOFFICE_SLDM = register( - "application/vnd.ms-powerpoint.slide.macroEnabled.12", - "Office PowerPoint 2007 macro-enabled slide"); + public static final MediaType APPLICATION_MSOFFICE_PPTM = + register( + "application/vnd.ms-powerpoint.presentation.macroEnabled.12", + "Office PowerPoint 2007 macro-enabled presentation"); - public static final MediaType APPLICATION_MSOFFICE_SLDX = register( - "application/vnd.openxmlformats-officedocument.presentationml.slide", - "Office PowerPoint 2007 slide"); + public static final MediaType APPLICATION_MSOFFICE_PPTX = + register( + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "Microsoft Office PowerPoint 2007 presentation"); - public static final MediaType APPLICATION_MSOFFICE_XLAM = register( - "application/vnd.ms-excel.addin.macroEnabled.12", - "Office Excel 2007 add-in"); + public static final MediaType APPLICATION_MSOFFICE_SLDM = + register( + "application/vnd.ms-powerpoint.slide.macroEnabled.12", + "Office PowerPoint 2007 macro-enabled slide"); - public static final MediaType APPLICATION_MSOFFICE_XLSB = register( - "application/vnd.ms-excel.sheet.binary.macroEnabled.12", - "Office Excel 2007 binary workbook"); + public static final MediaType APPLICATION_MSOFFICE_SLDX = + register( + "application/vnd.openxmlformats-officedocument.presentationml.slide", + "Office PowerPoint 2007 slide"); - public static final MediaType APPLICATION_MSOFFICE_XLSM = register( - "application/vnd.ms-excel.sheet.macroEnabled.12", - "Office Excel 2007 macro-enabled workbook"); + public static final MediaType APPLICATION_MSOFFICE_XLAM = + register("application/vnd.ms-excel.addin.macroEnabled.12", "Office Excel 2007 add-in"); - public static final MediaType APPLICATION_MSOFFICE_XLSX = register( - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "Microsoft Office Excel 2007 workbook"); + public static final MediaType APPLICATION_MSOFFICE_XLSB = + register( + "application/vnd.ms-excel.sheet.binary.macroEnabled.12", + "Office Excel 2007 binary workbook"); - public static final MediaType APPLICATION_MSOFFICE_XLTM = register( - "application/vnd.ms-excel.template.macroEnabled.12", - "Office Excel 2007 macro-enabled workbook template"); + public static final MediaType APPLICATION_MSOFFICE_XLSM = + register( + "application/vnd.ms-excel.sheet.macroEnabled.12", + "Office Excel 2007 macro-enabled workbook"); + + public static final MediaType APPLICATION_MSOFFICE_XLSX = + register( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "Microsoft Office Excel 2007 workbook"); + + public static final MediaType APPLICATION_MSOFFICE_XLTM = + register( + "application/vnd.ms-excel.template.macroEnabled.12", + "Office Excel 2007 macro-enabled workbook template"); + + public static final MediaType APPLICATION_MSOFFICE_XLTX = + register( + "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + "Office Excel 2007 template"); + + public static final MediaType APPLICATION_OCTET_STREAM = + register("application/octet-stream", "Raw octet stream"); + + public static final MediaType APPLICATION_OPENOFFICE_ODB = + register("application/vnd.oasis.opendocument.database", "OpenDocument Database"); + + public static final MediaType APPLICATION_OPENOFFICE_ODC = + register("application/vnd.oasis.opendocument.chart", "OpenDocument Chart"); + + public static final MediaType APPLICATION_OPENOFFICE_ODF = + register("application/vnd.oasis.opendocument.formula", "OpenDocument Formula"); + + public static final MediaType APPLICATION_OPENOFFICE_ODG = + register("application/vnd.oasis.opendocument.graphics", "OpenDocument Drawing"); - public static final MediaType APPLICATION_MSOFFICE_XLTX = register( - "application/vnd.openxmlformats-officedocument.spreadsheetml.template", - "Office Excel 2007 template"); + public static final MediaType APPLICATION_OPENOFFICE_ODI = + register("application/vnd.oasis.opendocument.image", "OpenDocument Image "); - public static final MediaType APPLICATION_OCTET_STREAM = register( - "application/octet-stream", "Raw octet stream"); + public static final MediaType APPLICATION_OPENOFFICE_ODM = + register( + "application/vnd.oasis.opendocument.text-master", + "OpenDocument Master Document"); - public static final MediaType APPLICATION_OPENOFFICE_ODB = register( - "application/vnd.oasis.opendocument.database", - "OpenDocument Database"); + public static final MediaType APPLICATION_OPENOFFICE_ODP = + register( + "application/vnd.oasis.opendocument.presentation", + "OpenDocument Presentation "); - public static final MediaType APPLICATION_OPENOFFICE_ODC = register( - "application/vnd.oasis.opendocument.chart", "OpenDocument Chart"); + public static final MediaType APPLICATION_OPENOFFICE_ODS = + register("application/vnd.oasis.opendocument.spreadsheet", "OpenDocument Spreadsheet"); - public static final MediaType APPLICATION_OPENOFFICE_ODF = register( - "application/vnd.oasis.opendocument.formula", - "OpenDocument Formula"); + public static final MediaType APPLICATION_OPENOFFICE_ODT = + register("application/vnd.oasis.opendocument.text ", "OpenDocument Text"); - public static final MediaType APPLICATION_OPENOFFICE_ODG = register( - "application/vnd.oasis.opendocument.graphics", - "OpenDocument Drawing"); + public static final MediaType APPLICATION_OPENOFFICE_OTG = + register( + "application/vnd.oasis.opendocument.graphics-template", + "OpenDocument Drawing Template"); - public static final MediaType APPLICATION_OPENOFFICE_ODI = register( - "application/vnd.oasis.opendocument.image", "OpenDocument Image "); + public static final MediaType APPLICATION_OPENOFFICE_OTH = + register("application/vnd.oasis.opendocument.text-web", "HTML Document Template"); - public static final MediaType APPLICATION_OPENOFFICE_ODM = register( - "application/vnd.oasis.opendocument.text-master", - "OpenDocument Master Document"); + public static final MediaType APPLICATION_OPENOFFICE_OTP = + register( + "application/vnd.oasis.opendocument.presentation-template", + "OpenDocument Presentation Template"); - public static final MediaType APPLICATION_OPENOFFICE_ODP = register( - "application/vnd.oasis.opendocument.presentation", - "OpenDocument Presentation "); + public static final MediaType APPLICATION_OPENOFFICE_OTS = + register( + "application/vnd.oasis.opendocument.spreadsheet-template", + "OpenDocument Spreadsheet Template"); - public static final MediaType APPLICATION_OPENOFFICE_ODS = register( - "application/vnd.oasis.opendocument.spreadsheet", - "OpenDocument Spreadsheet"); + public static final MediaType APPLICATION_OPENOFFICE_OTT = + register( + "application/vnd.oasis.opendocument.text-template", + "OpenDocument Text Template"); - public static final MediaType APPLICATION_OPENOFFICE_ODT = register( - "application/vnd.oasis.opendocument.text ", "OpenDocument Text"); + public static final MediaType APPLICATION_OPENOFFICE_OXT = + register("application/vnd.openofficeorg.extension", "OpenOffice.org extension"); - public static final MediaType APPLICATION_OPENOFFICE_OTG = register( - "application/vnd.oasis.opendocument.graphics-template", - "OpenDocument Drawing Template"); + public static final MediaType APPLICATION_PDF = + register("application/pdf", "Adobe PDF document"); - public static final MediaType APPLICATION_OPENOFFICE_OTH = register( - "application/vnd.oasis.opendocument.text-web", - "HTML Document Template"); + public static final MediaType APPLICATION_POSTSCRIPT = + register("application/postscript", "Postscript document"); - public static final MediaType APPLICATION_OPENOFFICE_OTP = register( - "application/vnd.oasis.opendocument.presentation-template", - "OpenDocument Presentation Template"); + public static final MediaType APPLICATION_POWERPOINT = + register("application/vnd.ms-powerpoint", "Microsoft Powerpoint document"); - public static final MediaType APPLICATION_OPENOFFICE_OTS = register( - "application/vnd.oasis.opendocument.spreadsheet-template", - "OpenDocument Spreadsheet Template"); + public static final MediaType APPLICATION_PROJECT = + register("application/vnd.ms-project", "Microsoft Project document"); - public static final MediaType APPLICATION_OPENOFFICE_OTT = register( - "application/vnd.oasis.opendocument.text-template", - "OpenDocument Text Template"); + public static final MediaType APPLICATION_RDF_TRIG = + register( + "application/x-trig", + "Plain text serialized Resource Description Framework document"); - public static final MediaType APPLICATION_OPENOFFICE_OXT = register( - "application/vnd.openofficeorg.extension", - "OpenOffice.org extension"); + public static final MediaType APPLICATION_RDF_TRIX = + register( + "application/trix", + "Simple XML serialized Resource Description Framework document"); - public static final MediaType APPLICATION_PDF = register("application/pdf", - "Adobe PDF document"); + public static final MediaType APPLICATION_RDF_XML = + register( + "application/rdf+xml", + "Normalized XML serialized Resource Description Framework document"); - public static final MediaType APPLICATION_POSTSCRIPT = register( - "application/postscript", "Postscript document"); + public static final MediaType APPLICATION_RELAXNG_COMPACT = + register( + "application/relax-ng-compact-syntax", + "Relax NG Schema document, Compact syntax"); - public static final MediaType APPLICATION_POWERPOINT = register( - "application/vnd.ms-powerpoint", "Microsoft Powerpoint document"); + public static final MediaType APPLICATION_RELAXNG_XML = + register("application/x-relax-ng+xml", "Relax NG Schema document, XML syntax"); - public static final MediaType APPLICATION_PROJECT = register( - "application/vnd.ms-project", "Microsoft Project document"); + public static final MediaType APPLICATION_RSS = + register("application/rss+xml", "Really Simple Syndication document"); - public static final MediaType APPLICATION_RDF_TRIG = register( - "application/x-trig", - "Plain text serialized Resource Description Framework document"); + public static final MediaType APPLICATION_RTF = + register("application/rtf", "Rich Text Format document"); - public static final MediaType APPLICATION_RDF_TRIX = register( - "application/trix", - "Simple XML serialized Resource Description Framework document"); + public static final MediaType APPLICATION_SDP = + register("application/sdp", "Session Description Protocol"); - public static final MediaType APPLICATION_RDF_XML = register( - "application/rdf+xml", - "Normalized XML serialized Resource Description Framework document"); + public static final MediaType APPLICATION_SPARQL_RESULTS_JSON = + register("application/sparql-results+json", "SPARQL Query Results JSON document"); - public static final MediaType APPLICATION_RELAXNG_COMPACT = register( - "application/relax-ng-compact-syntax", - "Relax NG Schema document, Compact syntax"); + public static final MediaType APPLICATION_SPARQL_RESULTS_XML = + register("application/sparql-results+xml", "SPARQL Query Results XML document"); - public static final MediaType APPLICATION_RELAXNG_XML = register( - "application/x-relax-ng+xml", - "Relax NG Schema document, XML syntax"); + public static final MediaType APPLICATION_SPSS_SAV = + register("application/x-spss-sav", "SPSS Data"); - public static final MediaType APPLICATION_RSS = register( - "application/rss+xml", "Really Simple Syndication document"); + public static final MediaType APPLICATION_SPSS_SPS = + register("application/x-spss-sps", "SPSS Script Syntax"); - public static final MediaType APPLICATION_RTF = register("application/rtf", - "Rich Text Format document"); + public static final MediaType APPLICATION_STATA_STA = + register("application/x-stata", "Stata data file"); - public static final MediaType APPLICATION_SDP = register("application/sdp", - "Session Description Protocol"); + public static final MediaType APPLICATION_STUFFIT = + register("application/x-stuffit", "Stuffit archive"); - public static final MediaType APPLICATION_SPARQL_RESULTS_JSON = register( - "application/sparql-results+json", - "SPARQL Query Results JSON document"); + public static final MediaType APPLICATION_TAR = register("application/x-tar", "Tar archive"); - public static final MediaType APPLICATION_SPARQL_RESULTS_XML = register( - "application/sparql-results+xml", - "SPARQL Query Results XML document"); + public static final MediaType APPLICATION_TEX = register("application/x-tex", "Tex file"); - public static final MediaType APPLICATION_SPSS_SAV = register( - "application/x-spss-sav", "SPSS Data"); + public static final MediaType APPLICATION_TROFF_MAN = + register("application/x-troff-man", "LaTeX"); - public static final MediaType APPLICATION_SPSS_SPS = register( - "application/x-spss-sps", "SPSS Script Syntax"); + public static final MediaType APPLICATION_VOICEXML = + register("application/voicexml+xml", "VoiceXML"); - public static final MediaType APPLICATION_STATA_STA = register( - "application/x-stata", "Stata data file"); + public static final MediaType APPLICATION_W3C_SCHEMA = + register("application/x-xsd+xml", "W3C XML Schema document"); - public static final MediaType APPLICATION_STUFFIT = register( - "application/x-stuffit", "Stuffit archive"); + public static final MediaType APPLICATION_W3C_XSLT = + register("application/xslt+xml", "W3C XSLT Stylesheet"); - public static final MediaType APPLICATION_TAR = register( - "application/x-tar", "Tar archive"); + public static final MediaType APPLICATION_WADL = + register( + "application/vnd.sun.wadl+xml", + "Web Application Description Language document"); - public static final MediaType APPLICATION_TEX = register( - "application/x-tex", "Tex file"); + public static final MediaType APPLICATION_WORD = + register("application/msword", "Microsoft Word document"); - public static final MediaType APPLICATION_TROFF_MAN = register( - "application/x-troff-man", "LaTeX"); + public static final MediaType APPLICATION_WWW_FORM = + register("application/x-www-form-urlencoded", "Web form (URL encoded)"); - public static final MediaType APPLICATION_VOICEXML = register( - "application/voicexml+xml", "VoiceXML"); + public static final MediaType APPLICATION_XHTML = + register("application/xhtml+xml", "XHTML document"); - public static final MediaType APPLICATION_W3C_SCHEMA = register( - "application/x-xsd+xml", "W3C XML Schema document"); + public static final MediaType APPLICATION_XMI = register("application/xmi+xml", "XMI document"); - public static final MediaType APPLICATION_W3C_XSLT = register( - "application/xslt+xml", "W3C XSLT Stylesheet"); + public static final MediaType APPLICATION_XML = register("application/xml", "XML document"); - public static final MediaType APPLICATION_WADL = register( - "application/vnd.sun.wadl+xml", - "Web Application Description Language document"); + public static final MediaType APPLICATION_XML_DTD = register("application/xml-dtd", "XML DTD"); - public static final MediaType APPLICATION_WORD = register( - "application/msword", "Microsoft Word document"); + public static final MediaType APPLICATION_XQUERY = + register("application/xquery", "XQuery document"); - public static final MediaType APPLICATION_WWW_FORM = register( - "application/x-www-form-urlencoded", "Web form (URL encoded)"); + public static final MediaType APPLICATION_XUL = + register("application/vnd.mozilla.xul+xml", "XUL document"); - public static final MediaType APPLICATION_XHTML = register( - "application/xhtml+xml", "XHTML document"); + public static final MediaType APPLICATION_YAML = + register("application/x-yaml", "YAML document"); - public static final MediaType APPLICATION_XMI = register( - "application/xmi+xml", "XMI document"); - - public static final MediaType APPLICATION_XML = register("application/xml", - "XML document"); - - public static final MediaType APPLICATION_XML_DTD = register( - "application/xml-dtd", "XML DTD"); - - public static final MediaType APPLICATION_XQUERY = register( - "application/xquery", "XQuery document"); - - public static final MediaType APPLICATION_XUL = register( - "application/vnd.mozilla.xul+xml", "XUL document"); - - public static final MediaType APPLICATION_YAML = register( - "application/x-yaml", "YAML document"); - - public static final MediaType APPLICATION_ZIP = register("application/zip", - "Zip archive"); + public static final MediaType APPLICATION_ZIP = register("application/zip", "Zip archive"); public static final MediaType AUDIO_ALL = register("audio/*", "All audios"); - public static final MediaType AUDIO_BASIC = register("audio/basic", - "AU audio"); + public static final MediaType AUDIO_BASIC = register("audio/basic", "AU audio"); - public static final MediaType AUDIO_MIDI = register("audio/midi", - "MIDI audio"); + public static final MediaType AUDIO_MIDI = register("audio/midi", "MIDI audio"); - public static final MediaType AUDIO_MPEG = register("audio/mpeg", - "MPEG audio (MP3)"); + public static final MediaType AUDIO_MPEG = register("audio/mpeg", "MPEG audio (MP3)"); - public static final MediaType AUDIO_REAL = register("audio/x-pn-realaudio", - "Real audio"); + public static final MediaType AUDIO_REAL = register("audio/x-pn-realaudio", "Real audio"); - public static final MediaType AUDIO_WAV = register("audio/x-wav", - "Waveform audio"); + public static final MediaType AUDIO_WAV = register("audio/x-wav", "Waveform audio"); public static final MediaType IMAGE_ALL = register("image/*", "All images"); - public static final MediaType IMAGE_BMP = register("image/bmp", - "Windows bitmap"); + public static final MediaType IMAGE_BMP = register("image/bmp", "Windows bitmap"); - public static final MediaType IMAGE_GIF = register("image/gif", - "GIF image"); + public static final MediaType IMAGE_GIF = register("image/gif", "GIF image"); - public static final MediaType IMAGE_ICON = register("image/x-icon", - "Windows icon (Favicon)"); + public static final MediaType IMAGE_ICON = register("image/x-icon", "Windows icon (Favicon)"); - public static final MediaType IMAGE_JPEG = register("image/jpeg", - "JPEG image"); + public static final MediaType IMAGE_JPEG = register("image/jpeg", "JPEG image"); - public static final MediaType IMAGE_PNG = register("image/png", - "PNG image"); + public static final MediaType IMAGE_PNG = register("image/png", "PNG image"); - public static final MediaType IMAGE_SVG = register("image/svg+xml", - "Scalable Vector Graphics"); + public static final MediaType IMAGE_SVG = register("image/svg+xml", "Scalable Vector Graphics"); - public static final MediaType IMAGE_TIFF = register("image/tiff", - "TIFF image"); + public static final MediaType IMAGE_TIFF = register("image/tiff", "TIFF image"); - public static final MediaType MESSAGE_ALL = register("message/*", - "All messages"); + public static final MediaType MESSAGE_ALL = register("message/*", "All messages"); - public static final MediaType MESSAGE_HTTP = register("message/http", - "HTTP message"); + public static final MediaType MESSAGE_HTTP = register("message/http", "HTTP message"); public static final MediaType MODEL_ALL = register("model/*", "All models"); public static final MediaType MODEL_VRML = register("model/vrml", "VRML"); - public static final MediaType MULTIPART_ALL = register("multipart/*", - "All multipart data"); + public static final MediaType MULTIPART_ALL = register("multipart/*", "All multipart data"); - public static final MediaType MULTIPART_FORM_DATA = register( - "multipart/form-data", "Multipart form data"); + public static final MediaType MULTIPART_FORM_DATA = + register("multipart/form-data", "Multipart form data"); public static final MediaType TEXT_ALL = register("text/*", "All texts"); - public static final MediaType TEXT_CALENDAR = register("text/calendar", - "iCalendar event"); + public static final MediaType TEXT_CALENDAR = register("text/calendar", "iCalendar event"); - public static final MediaType TEXT_CSS = register("text/css", - "CSS stylesheet"); + public static final MediaType TEXT_CSS = register("text/css", "CSS stylesheet"); - public static final MediaType TEXT_CSV = register("text/csv", - "Comma-separated Values"); + public static final MediaType TEXT_CSV = register("text/csv", "Comma-separated Values"); - public static final MediaType TEXT_DAT = register("text/x-fixed-field", - "Fixed-width Values"); + public static final MediaType TEXT_DAT = register("text/x-fixed-field", "Fixed-width Values"); - public static final MediaType TEXT_HTML = register("text/html", - "HTML document"); + public static final MediaType TEXT_HTML = register("text/html", "HTML document"); - public static final MediaType TEXT_J2ME_APP_DESCRIPTOR = register( - "text/vnd.sun.j2me.app-descriptor", "J2ME Application Descriptor"); + public static final MediaType TEXT_J2ME_APP_DESCRIPTOR = + register("text/vnd.sun.j2me.app-descriptor", "J2ME Application Descriptor"); - public static final MediaType TEXT_JAVASCRIPT = register("text/javascript", - "Javascript document"); + public static final MediaType TEXT_JAVASCRIPT = + register("text/javascript", "Javascript document"); - public static final MediaType TEXT_PLAIN = register("text/plain", - "Plain text"); + public static final MediaType TEXT_PLAIN = register("text/plain", "Plain text"); - public static final MediaType TEXT_RDF_N3 = register("text/n3", - "N3 serialized Resource Description Framework document"); + public static final MediaType TEXT_RDF_N3 = + register("text/n3", "N3 serialized Resource Description Framework document"); - public static final MediaType TEXT_RDF_NTRIPLES = register("text/n-triples", - "N-Triples serialized Resource Description Framework document"); + public static final MediaType TEXT_RDF_NTRIPLES = + register( + "text/n-triples", + "N-Triples serialized Resource Description Framework document"); - public static final MediaType TEXT_TSV = register( - "text/tab-separated-values", "Tab-separated Values"); + public static final MediaType TEXT_TSV = + register("text/tab-separated-values", "Tab-separated Values"); - public static final MediaType TEXT_TURTLE = register("text/turtle", - "Plain text serialized Resource Description Framework document"); + public static final MediaType TEXT_TURTLE = + register( + "text/turtle", "Plain text serialized Resource Description Framework document"); - public static final MediaType TEXT_URI_LIST = register("text/uri-list", - "List of URIs"); + public static final MediaType TEXT_URI_LIST = register("text/uri-list", "List of URIs"); - public static final MediaType TEXT_VCARD = register("text/x-vcard", - "vCard"); + public static final MediaType TEXT_VCARD = register("text/x-vcard", "vCard"); public static final MediaType TEXT_XML = register("text/xml", "XML text"); - public static final MediaType TEXT_YAML = register("text/x-yaml", - "YAML document"); + public static final MediaType TEXT_YAML = register("text/x-yaml", "YAML document"); public static final MediaType VIDEO_ALL = register("video/*", "All videos"); - public static final MediaType VIDEO_AVI = register("video/x-msvideo", - "AVI video"); + public static final MediaType VIDEO_AVI = register("video/x-msvideo", "AVI video"); - public static final MediaType VIDEO_MP4 = register("video/mp4", - "MPEG-4 video"); + public static final MediaType VIDEO_MP4 = register("video/mp4", "MPEG-4 video"); - public static final MediaType VIDEO_MPEG = register("video/mpeg", - "MPEG video"); + public static final MediaType VIDEO_MPEG = register("video/mpeg", "MPEG video"); - public static final MediaType VIDEO_QUICKTIME = register("video/quicktime", - "Quicktime video"); + public static final MediaType VIDEO_QUICKTIME = register("video/quicktime", "Quicktime video"); - public static final MediaType VIDEO_WMV = register("video/x-ms-wmv", - "Windows movie"); + public static final MediaType VIDEO_WMV = register("video/x-ms-wmv", "Windows movie"); /** - * Returns the first of the most specific media type of the given array of - * {@link MediaType}s. - *

- * Examples: + * Returns the first of the most specific media type of the given array of {@link MediaType}s. + * + *

Examples: + * *

    - *
  • "text/plain" is more specific than "text/*" or "image/*"
  • - *
  • "text/html" is same specific as "application/pdf" or "image/jpg"
  • - *
  • "text/*" is same specific than "application/*" or "image/*"
  • - *
  • "*/*" is the most unspecific MediaType
  • + *
  • "text/plain" is more specific than "text/*" or "image/*" + *
  • "text/html" is as specific as "application/pdf" or "image/jpg" + *
  • "text/*" is as specific as "application/*" or "image/*" + *
  • "*" is the most unspecific MediaType *
- * + * * @param mediaTypes An array of media types. * @return The most concrete MediaType. * @throws IllegalArgumentException If the array is null or empty. @@ -540,8 +518,7 @@ public final class MediaType extends Metadata { public static MediaType getMostSpecific(MediaType... mediaTypes) throws IllegalArgumentException { if ((mediaTypes == null) || (mediaTypes.length == 0)) { - throw new IllegalArgumentException( - "You must give at least one MediaType"); + throw new IllegalArgumentException("You must give at least one MediaType"); } if (mediaTypes.length == 1) { @@ -553,41 +530,24 @@ public static MediaType getMostSpecific(MediaType... mediaTypes) for (int i = 1; i < mediaTypes.length; i++) { MediaType mediaType = mediaTypes[i]; - if (mediaType != null) { - if (mediaType.getMainType().equals("*")) { - continue; - } - - if (mostSpecific.getMainType().equals("*")) { - mostSpecific = mediaType; - continue; - } + if (mediaType == null || "*".equals(mediaType.getMainType())) { + continue; + } - if (mostSpecific.getSubType().contains("*")) { - mostSpecific = mediaType; - continue; - } + if ("*".equals(mostSpecific.getMainType())) { + mostSpecific = mediaType; + } else if (mostSpecific.getSubType() != null + && mostSpecific.getSubType().contains("*")) { + mostSpecific = mediaType; } } return mostSpecific; } - /** - * Returns the known media types map. - * - * @return the known media types map. - */ - private static Map getTypes() { - if (_types == null) { - _types = new HashMap(); - } - return _types; - } - /** * Normalizes the specified token. - * + * * @param token Token to normalize. * @return The normalized token. * @throws IllegalArgumentException if token is not legal. @@ -598,14 +558,13 @@ private static String normalizeToken(String token) { // Makes sure we're not dealing with a "*" token. token = token.trim(); - if (token.isEmpty() || "*".equals(token)) - return "*"; + if (token.isEmpty() || "*".equals(token)) return "*"; // Makes sure the token is RFC compliant. length = token.length(); for (int i = 0; i < length; i++) { c = token.charAt(i); - if (c <= 32 || c >= 127 || _TSPECIALS.indexOf(c) != -1) + if (c <= 32 || c >= 127 || TSPECIALS.indexOf(c) != -1) throw new IllegalArgumentException("Illegal token: " + token); } @@ -614,13 +573,12 @@ private static String normalizeToken(String token) { /** * Normalizes the specified media type. - * - * @param name The name of the type to normalize. + * + * @param name The name of the type to normalize. * @param parameters The parameters of the type to normalize. * @return The normalized type. */ - private static String normalizeType(String name, - Series parameters) { + private static String normalizeType(String name, Series parameters) { int slashIndex; int colonIndex; String mainType; @@ -628,10 +586,9 @@ private static String normalizeType(String name, StringBuilder params = null; // Ignore null names (backward compatibility). - if (name == null) - return null; + if (name == null) return null; - // Check presence of parameters + // Check the presence of parameters if ((colonIndex = name.indexOf(';')) != -1) { params = new StringBuilder(name.substring(colonIndex)); name = name.substring(0, colonIndex); @@ -642,7 +599,7 @@ private static String normalizeType(String name, mainType = normalizeToken(name); subType = "*"; } else { - // Normalizes the main and sub types. + // Normalizes the main and subtypes. mainType = normalizeToken(name.substring(0, slashIndex)); subType = normalizeToken(name.substring(slashIndex + 1)); } @@ -653,14 +610,14 @@ private static String normalizeType(String name, if (params == null) { params = new StringBuilder(); } - HeaderWriter hw = new HeaderWriter() { - @Override - public HeaderWriter append(Parameter value) { - return appendExtension(value); - } - }; - for (int i = 0; i < parameters.size(); i++) { - Parameter p = parameters.get(i); + HeaderWriter hw = + new HeaderWriter<>() { + @Override + public HeaderWriter append(Parameter value) { + return appendExtension(value); + } + }; + for (Parameter p : parameters) { hw.appendParameterSeparator(); hw.appendSpace(); hw.append(p); @@ -668,39 +625,33 @@ public HeaderWriter append(Parameter value) { params.append(hw.toString()); hw.close(); } catch (IOException e) { - Context.getCurrentLogger().log(Level.INFO, - "Unable to parse the media type parameter", e); + Context.getCurrentLogger() + .log(Level.INFO, "Unable to parse the media type parameter", e); } } - return (params == null) ? mainType + '/' + subType + return (params == null) + ? mainType + '/' + subType : mainType + '/' + subType + params.toString(); } /** - * Register a media type as a known type that can later be retrieved using - * {@link #valueOf(String)}. If the type already exists, the existing type - * is returned, otherwise a new instance is created. - * - * @param name The name. + * Register a media type as a known type that can later be retrieved using {@link + * #valueOf(String)}. If the type already exists, the existing type is returned, otherwise a new + * instance is created. + * + * @param name The name. * @param description The description. * @return The registered media type */ - public static synchronized MediaType register(String name, - String description) { - - if (!getTypes().containsKey(name)) { - final MediaType type = new MediaType(name, description); - getTypes().put(name, type); - } - - return getTypes().get(name); + public static synchronized MediaType register(String name, String description) { + return TYPES.computeIfAbsent(name, n -> new MediaType(n, description)); } /** - * Returns the media type associated to a name. If an existing constant - * exists then it is returned, otherwise a new instance is created. - * + * Returns the media type associated with a name. If an existing constant exists, then it is + * returned; otherwise a new instance is created. + * * @param name The name. * @return The associated media type. */ @@ -708,7 +659,7 @@ public static MediaType valueOf(String name) { MediaType result = null; if (!StringUtils.isNullOrEmpty(name)) { - result = getTypes().get(name); + result = TYPES.get(name); if (result == null) { result = new MediaType(name); } @@ -722,9 +673,9 @@ public static MediaType valueOf(String name) { /** * Constructor that clones an original media type. - * - * @param original The original media type to clone. - * @param paramName The name of the unique parameter to set. + * + * @param original The original media type to clone. + * @param paramName The name of the unique parameter to set. * @param paramValue The value of the unique parameter to set. */ public MediaType(MediaType original, String paramName, String paramValue) { @@ -733,8 +684,8 @@ public MediaType(MediaType original, String paramName, String paramValue) { /** * Constructor that clones an original media type. - * - * @param original The original media type to clone. + * + * @param original The original media type to clone. * @param parameter The unique parameter to set. */ public MediaType(MediaType original, Parameter parameter) { @@ -742,20 +693,22 @@ public MediaType(MediaType original, Parameter parameter) { } /** - * Constructor that clones an original media type by extracting its parent - * media type then adding a new set of parameters. - * - * @param original The original media type to clone. + * Constructor that clones an original media type by extracting its parent media type then + * adding a new set of parameters. + * + * @param original The original media type to clone. * @param parameters The list of parameters to set. */ public MediaType(MediaType original, Series parameters) { - this((original == null) ? null : original.getName(), parameters, + this( + (original == null) ? null : original.getName(), + parameters, (original == null) ? null : original.getDescription()); } /** * Constructor. - * + * * @param name The name. */ public MediaType(String name) { @@ -764,8 +717,8 @@ public MediaType(String name) { /** * Constructor. - * - * @param name The name. + * + * @param name The name. * @param parameters The list of parameters. */ public MediaType(String name, Series parameters) { @@ -774,26 +727,24 @@ public MediaType(String name, Series parameters) { /** * Constructor. - * - * @param name The name. - * @param parameters The list of parameters. + * + * @param name The name. + * @param parameters The list of parameters. * @param description The description. */ @SuppressWarnings("unchecked") - public MediaType(String name, Series parameters, - String description) { + public MediaType(String name, Series parameters, String description) { super(normalizeType(name, parameters), description); if (parameters != null) { - this.parameters = (Series) Series - .unmodifiableSeries(parameters); + this.parameters = (Series) Series.unmodifiableSeries(parameters); } } /** * Constructor. - * - * @param name The name. + * + * @param name The name. * @param description The description. */ public MediaType(String name, String description) { @@ -807,36 +758,28 @@ public boolean equals(Object obj) { } /** - * Test the equality of two media types, with the possibility to ignore the - * parameters. - * - * @param obj The object to compare to. - * @param ignoreParameters Indicates if parameters should be ignored during - * comparison. + * Test the equality of two media types, with the possibility to ignore the parameters. + * + * @param obj The object to compare to. + * @param ignoreParameters Indicates if parameters should be ignored during comparison. * @return True if both media types are equal. */ public boolean equals(Object obj, boolean ignoreParameters) { - boolean result = (obj == this); - - // if obj == this no need to go further - if (!result) { - // if obj isn't a mediatype or is null don't evaluate further - if (obj instanceof MediaType) { - final MediaType that = (MediaType) obj; - if (getMainType().equals(that.getMainType()) - && getSubType().equals(that.getSubType())) { - result = ignoreParameters - || getParameters().equals(that.getParameters()); - } - } + if (obj == this) { + return true; + } + if (!(obj instanceof MediaType that)) { + return false; } - return result; + return Objects.equals(getMainType(), that.getMainType()) + && Objects.equals(getSubType(), that.getSubType()) + && (ignoreParameters || getParameters().equals(that.getParameters())); } /** * Returns the main type. - * + * * @return The main type. */ public String getMainType() { @@ -861,9 +804,9 @@ public String getMainType() { } /** - * Returns the unmodifiable list of parameters corresponding to subtype - * modifiers. Creates a new instance if no one has been set. - * + * Returns the unmodifiable list of parameters corresponding to subtype modifiers. Creates a new + * instance if no one has been set. + * * @return The list of parameters. */ @SuppressWarnings("unchecked") @@ -880,17 +823,15 @@ public Series getParameters() { int index = getName().indexOf(';'); if (index != -1) { - params = new Form( - getName().substring(index + 1).trim(), ';'); + params = new Form(getName().substring(index + 1).trim(), ';'); } } if (params == null) { - params = new Series(Parameter.class); + params = new Series<>(Parameter.class); } - this.parameters = p = (Series) Series - .unmodifiableSeries(params); + this.parameters = p = (Series) Series.unmodifiableSeries(params); } } } @@ -899,33 +840,32 @@ public Series getParameters() { /** * {@inheritDoc}
- * In case the media type has parameters, this method returns the - * concatenation of the main type and the subtype. If the subtype is not - * equal to "*", it returns the concatenation of the main type and "*". - * Otherwise, it returns either the {@link #ALL} media type if it is already - * the {@link #ALL} media type, or null. + * In case the media type has parameters, this method returns the concatenation of the main type + * and the subtype. If the subtype is not equal to "*", it returns the concatenation of the main + * type and "*". Otherwise, it returns either the {@link #ALL} media type if it is already the + * {@link #ALL} media type, or null. */ @Override public MediaType getParent() { - MediaType result = null; + final MediaType result; - if (getParameters().size() > 0) { - result = MediaType.valueOf(getMainType() + "/" + getSubType()); - } else { + if (getParameters().isEmpty()) { if (getSubType().equals("*")) { result = equals(ALL) ? null : ALL; } else { result = MediaType.valueOf(getMainType() + "/*"); } + } else { + result = MediaType.valueOf(getMainType() + "/" + getSubType()); } return result; } /** - * Returns the sub-type. - * - * @return The sub-type. + * Returns the subtype. + * + * @return The subtype. */ public String getSubType() { String result = null; @@ -956,9 +896,9 @@ public int hashCode() { } /** - * Indicates if a given media type is included in the current one @see - * {@link #includes(Metadata, boolean)}. It ignores the parameters. - * + * Indicates if a given media type is included in the current one @see {@link + * #includes(Metadata, boolean)}. It ignores the parameters. + * * @param included The media type to test for inclusion. * @return True if the given media type is included in the current one. * @see #isCompatible(Metadata) @@ -969,19 +909,19 @@ public boolean includes(Metadata included) { } /** - * Indicates if a given media type is included in the current one @see - * {@link #includes(Metadata, boolean)}. The test is true if both types are - * equal or if the given media type is within the range of the current one. - * For example, ALL includes all media types. Parameters are ignored for - * this comparison. A null media type is considered as included into the - * current one. It ignores the parameters. - *

- * Examples: + * Indicates if a given media type is included in the current one @see {@link + * #includes(Metadata, boolean)}. The test is true if both types are equal or if the given media + * type is within the range of the current one. For example, ALL includes all media types. + * Parameters are ignored for this comparison. A null media type is considered as included into + * the current one. It ignores the parameters. + * + *

Examples: + * *

    - *
  • TEXT_ALL.includes(TEXT_PLAIN) returns true
  • - *
  • TEXT_PLAIN.includes(TEXT_ALL) returns false
  • + *
  • TEXT_ALL.includes(TEXT_PLAIN) returns true + *
  • TEXT_PLAIN.includes(TEXT_ALL) returns false *
- * + * * @param included The media type to test for inclusion. * @return True if the given media type is included in the current one. * @see #isCompatible(Metadata) @@ -989,8 +929,7 @@ public boolean includes(Metadata included) { public boolean includes(Metadata included, boolean ignoreParameters) { boolean result = equals(ALL) || equals(included); - if (!result && (included instanceof MediaType)) { - MediaType includedMediaType = (MediaType) included; + if (!result && (included instanceof final MediaType includedMediaType)) { if (getMainType().equals(includedMediaType.getMainType())) { // Both media types are different @@ -1002,23 +941,23 @@ public boolean includes(Metadata included, boolean ignoreParameters) { // Media type A includes media type B if for each param // name/value pair in A, B contains the same name/value. result = true; - for (int i = 0; result - && i < getParameters().size(); i++) { + for (int i = 0; result && i < getParameters().size(); i++) { Parameter param = getParameters().get(i); - Parameter includedParam = includedMediaType - .getParameters().getFirst(param.getName()); + Parameter includedParam = + includedMediaType.getParameters().getFirst(param.getName()); // If there was no param with the same name, or the // param with the same name had a different value, // then no match. - result = (includedParam != null && param.getValue() - .equals(includedParam.getValue())); + result = + (includedParam != null + && param.getValue().equals(includedParam.getValue())); } } } else if (getSubType().equals("*")) { result = true; - } else if (getSubType().startsWith("*+") && includedMediaType - .getSubType().endsWith(getSubType().substring(2))) { + } else if (getSubType().startsWith("*+") + && includedMediaType.getSubType().endsWith(getSubType().substring(2))) { result = true; } } @@ -1028,13 +967,12 @@ public boolean includes(Metadata included, boolean ignoreParameters) { } /** - * Checks if the current media type is concrete. A media type is concrete if - * neither the main type nor the sub-type are equal to "*". - * + * Checks if the current media type is concrete. A media type is concrete if neither the main + * type nor the subtype are equal to "*". + * * @return True if this media type is concrete. */ public boolean isConcrete() { return !getName().contains("*"); } - } diff --git a/org.restlet/src/main/java/org/restlet/data/Metadata.java b/org.restlet/src/main/java/org/restlet/data/Metadata.java index 1063cb541c..9bc1ac9bef 100644 --- a/org.restlet/src/main/java/org/restlet/data/Metadata.java +++ b/org.restlet/src/main/java/org/restlet/data/Metadata.java @@ -1,136 +1,141 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.util.Objects; + /** - * Representations metadata for content negotiation. "Metadata is in the form of - * name-value pairs, where the name corresponds to a standard that defines the - * value's structure and semantics. Response messages may include both - * representation metadata and resource metadata: information about the resource - * that is not specific to the supplied representation." Roy T. Fielding - * + * Representations metadata for content negotiation. "Metadata is in the form of name-value pairs, + * where the name corresponds to a standard that defines the value's structure and semantics. + * Response messages may include both representation metadata and resource metadata: information + * about the resource that is not specific to the supplied representation." Roy T. Fielding + * * @see Preference - * @see Source dissertation + * @see Source + * dissertation * @author Jerome Louvel */ public abstract class Metadata { - /** The description of this metadata. */ - private final String description; + /** The description of this metadata. */ + private final String description; - /** The metadata name like "text/html" or "compress" or "iso-8851-1". */ - private final String name; + /** The metadata name like "text/html" or "compress" or "iso-8851-1". */ + private final String name; - /** - * Constructor. - * - * @param name The unique name. - */ - public Metadata(String name) { - this(name, null); - } + /** + * Constructor. + * + * @param name The unique name. + */ + protected Metadata(String name) { + this(name, null); + } - /** - * Constructor. - * - * @param name The unique name. - * @param description The description. - */ - public Metadata(String name, String description) { - this.name = name; - this.description = description; - } + /** + * Constructor. + * + * @param name The unique name. + * @param description The description. + */ + protected Metadata(String name, String description) { + this.name = name; + this.description = description; + } - /** {@inheritDoc} */ - @Override - public boolean equals(Object object) { - return (object instanceof Metadata) && ((Metadata) object).getName().equals(getName()); - } + /** {@inheritDoc} */ + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof Metadata that)) { + return false; + } + return Objects.equals(getName(), that.getName()); + } - /** - * Returns the description. - * - * @return The description. - */ - public String getDescription() { - return this.description; - } + /** + * Returns the description. + * + * @return The description. + */ + public String getDescription() { + return this.description; + } - /** - * Returns the name (ex: "text/html" or "compress" or "iso-8851-1"). - * - * @return The name (ex: "text/html" or "compress" or "iso-8851-1"). - */ - public String getName() { - return this.name; - } + /** + * Returns the name (ex: "text/html" or "compress" or "iso-8851-1"). + * + * @return The name (ex: "text/html" or "compress" or "iso-8851-1"). + */ + public String getName() { + return this.name; + } - /** - * Returns the parent metadata if available or null. - * - * @return The parent metadata. - */ - public abstract Metadata getParent(); + /** + * Returns the parent metadata if available or null. + * + * @return The parent metadata. + */ + public abstract Metadata getParent(); - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (getName() == null) ? 0 : getName().hashCode(); - } + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (getName() == null) ? 0 : getName().hashCode(); + } - /** - * Indicates if a given metadata is included in the current one. The test is - * true if both metadata are equal or if the given metadata is within the range - * of the current one. For example, {@link MediaType#ALL} includes all media - * types. - *

- * Examples: - *

    - *
  • TEXT_ALL.includes(TEXT_PLAIN) returns true
  • - *
  • TEXT_PLAIN.includes(TEXT_ALL) returns false
  • - *
- * - * @param included The metadata to test for inclusion. - * @return True if the given metadata is included in the current one. - * @see #isCompatible(Metadata) - */ - public abstract boolean includes(Metadata included); + /** + * Indicates if a given metadata is included in the current one. The test is true if both + * metadata is equal or if the given metadata is within the range of the current one. For + * example, {@link MediaType#ALL} includes all media types. + * + *

Examples: + * + *

    + *
  • TEXT_ALL.includes(TEXT_PLAIN) returns true + *
  • TEXT_PLAIN.includes(TEXT_ALL) returns false + *
+ * + * @param included The metadata to test for inclusion. + * @return True if the given metadata is included in the current one. + * @see #isCompatible(Metadata) + */ + public abstract boolean includes(Metadata included); - /** - * Checks if this metadata is compatible with the given metadata. - *

- * Examples: - *

    - *
  • TEXT_ALL.isCompatible(TEXT_PLAIN) returns true
  • - *
  • TEXT_PLAIN.isCompatible(TEXT_ALL) returns true
  • - *
  • TEXT_PLAIN.isCompatible(APPLICATION_ALL) returns false
  • - *
- * - * @param otherMetadata The other metadata to compare. - * @return True if the metadata are compatible. - * @see #includes(Metadata) - */ - public boolean isCompatible(Metadata otherMetadata) { - return (otherMetadata != null) - && (includes(otherMetadata) || otherMetadata.includes(this)); - } + /** + * Checks if this metadata is compatible with the given metadata. + * + *

Examples: + * + *

    + *
  • TEXT_ALL.isCompatible(TEXT_PLAIN) returns true + *
  • TEXT_PLAIN.isCompatible(TEXT_ALL) returns true + *
  • TEXT_PLAIN.isCompatible(APPLICATION_ALL) returns false + *
+ * + * @param otherMetadata The other metadata to compare. + * @return True if the metadata is compatible. + * @see #includes(Metadata) + */ + public boolean isCompatible(Metadata otherMetadata) { + return (otherMetadata != null) && (includes(otherMetadata) || otherMetadata.includes(this)); + } - /** - * Returns the metadata name. - * - * @return The metadata name. - */ - @Override - public String toString() { - return getName(); - } + /** + * Returns the metadata name. + * + * @return The metadata name. + */ + @Override + public String toString() { + return getName(); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Method.java b/org.restlet/src/main/java/org/restlet/data/Method.java index 0ee42f8729..378d1cff1e 100644 --- a/org.restlet/src/main/java/org/restlet/data/Method.java +++ b/org.restlet/src/main/java/org/restlet/data/Method.java @@ -1,371 +1,395 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.restlet.engine.Engine; - -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import org.restlet.engine.Engine; /** * Method to execute when handling a call. - * + * * @author Jerome Louvel */ public final class Method implements Comparable { - /** Map of registered methods. */ - private static final Map _methods = new ConcurrentHashMap(); - - /** - * Pseudo-method use to match all methods. - */ - public static final Method ALL = new Method("*", "Pseudo-method use to match all methods."); - - private static final String BASE_HTTP = "http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html"; - - /** - * Used with a proxy that can dynamically switch to being a tunnel. - * - * @see HTTP RFC - * - 9.9 CONNECT - */ - public static final Method CONNECT = new Method("CONNECT", - "Used with a proxy that can dynamically switch to being a tunnel", BASE_HTTP + "#sec9.9", false, false); - - /** - * Requests that the origin server deletes the resource identified by the - * request URI. - * - * @see HTTP RFC - * - 9.7 DELETE - */ - public static final Method DELETE = new Method("DELETE", - "Requests that the origin server deletes the resource identified by the request URI", BASE_HTTP + "#sec9.7", - false, true); - - /** - * Retrieves whatever information (in the form of an entity) that is identified - * by the request URI. - * - * @see HTTP RFC - * - 9.3 GET - */ - public static final Method GET = new Method("GET", - "Retrieves whatever information (in the form of an entity) that is identified by the request URI", - BASE_HTTP + "#sec9.3", true, true); - - /** - * Identical to GET except that the server must not return a message body in the - * response but only the message header. - * - * @see HTTP RFC - * - 9.4 HEAD - */ - public static final Method HEAD = new Method("HEAD", - "Identical to GET except that the server must not return a message body in the response", - BASE_HTTP + "#sec9.4", true, true); - - /** - * Requests for information about the communication options available on the - * request/response chain identified by the URI. - * - * @see HTTP RFC - * - 9.2 OPTIONS - */ - public static final Method OPTIONS = new Method("OPTIONS", - "Requests for information about the communication options available on the request/response chain identified by the URI", - BASE_HTTP + "#sec9.2", true, true); - - /** - * Requests that the origin server applies partial modifications contained in - * the entity enclosed in the request to the resource identified by the request - * URI. - * - * @see HTTP PATCH RFC 5789 - */ - public static final Method PATCH = new Method("PATCH", - "Requests that the origin server applies partial modifications to the resource identified by the request URI", - "http://tools.ietf.org/html/rfc5789", false, false); - - /** - * Requests that the origin server accepts the entity enclosed in the request as - * a new subordinate of the resource identified by the request URI. - * - * @see HTTP RFC - * - 9.5 POST - */ - public static final Method POST = new Method("POST", - "Requests that the origin server accepts the entity enclosed in the request as a new subordinate of the resource identified by the request URI", - BASE_HTTP + "#sec9.5", false, false); - - /** - * Requests that the enclosed entity be stored under the supplied request URI. - * - * @see - */ - public static final Method PUT = new Method("PUT", - "Requests that the enclosed entity be stored under the supplied request URI", BASE_HTTP + "#sec9.6", false, - true); - - /** - * Used to invoke a remote, application-layer loop-back of the request message. - * - * @see HTTP RFC - * - 9.8 TRACE - */ - public static final Method TRACE = new Method("TRACE", - "Used to invoke a remote, application-layer loop-back of the request message", BASE_HTTP + "#sec9.8", true, - true); - - /** - * Adds a new Method to the list of registered methods. - * - * @param method The method to register. - */ - public static void register(Method method) { - String name = (method == null) ? null : method.getName().toLowerCase(); - if ((name != null) && !name.isEmpty()) { - _methods.put(name, method); - } - } - - /** - * Sorts the given list of methods by name. - * - * @param methods The methods to sort. - */ - public static void sort(List methods) { - Collections.sort(methods, new Comparator() { - public int compare(Method m1, Method m2) { - return m1.getName().compareTo(m2.getName()); - } - }); - } - - /** - * Returns the method associated to a given method name. If an existing constant - * exists then it is returned, otherwise a new instance is created. - * - * @param name The method name. - * @return The associated method. - */ - public static Method valueOf(final String name) { - Method result = null; - - if ((name != null) && !name.isEmpty()) { - result = Method._methods.get(name.toLowerCase()); - if (result == null) { - result = new Method(name); - } - } - - return result; - } - - /** The description. */ - private final String description; - - /** - * Indicates if the side effects of several requests is the same as a single - * request. - */ - private volatile boolean idempotent; - - /** The name. */ - private volatile String name; - - /** Indicates if the method replies with a response. */ - private final boolean replying; - - /** - * Indicates if it should have the significance of taking an action other than - * retrieval. - */ - private final boolean safe; - - /** The URI of the specification describing the method. */ - private volatile String uri; - - static { - // Let the engine register all methods (the default ones and the ones to - // be discovered) as soon as the Method class is loaded or at least - // used. - Engine.getInstance(); - } - - /** - * Constructor for unsafe and non idempotent methods. - * - * @param name The technical name of the method. - * @see org.restlet.data.Method#valueOf(String) - */ - public Method(final String name) { - this(name, null); - } - - /** - * Constructor for unsafe and non idempotent methods. - * - * @param name The technical name of the method. - * @param description The description. - * @see org.restlet.data.Method#valueOf(String) - */ - public Method(String name, String description) { - this(name, description, null, false, false); - } - - /** - * Constructor for unsafe and non idempotent methods. - * - * @param name The technical name. - * @param description The description. - * @param uri The URI of the specification describing the method. - * @see org.restlet.data.Method#valueOf(String) - */ - public Method(String name, String description, String uri) { - this(name, description, uri, false, false); - } - - /** - * Constructor for methods that reply to requests with responses. - * - * @param name The technical name. - * @param description The description. - * @param uri The URI of the specification describing the method. - * @param safe Indicates if the method is safe. - * @param idempotent Indicates if the method is idempotent. - * @see org.restlet.data.Method#valueOf(String) - */ - public Method(String name, String description, String uri, boolean safe, boolean idempotent) { - this(name, description, uri, safe, idempotent, true); - } - - /** - * Constructor. - * - * @param name The technical name. - * @param description The description. - * @param uri The URI of the specification describing the method. - * @param safe Indicates if the method is safe. - * @param idempotent Indicates if the method is idempotent. - * @param replying Indicates if the method replies with a response. - * @see org.restlet.data.Method#valueOf(String) - */ - public Method(String name, String description, String uri, boolean safe, boolean idempotent, boolean replying) { - this.name = name; - this.description = description; - this.uri = uri; - this.safe = safe; - this.idempotent = idempotent; - this.replying = replying; - } - - /** - * Compares this method to another. Based on the method name. - * - * @param o The other method. - */ - public int compareTo(Method o) { - if (o != null) { - return this.getName().compareTo(o.getName()); - } - return 1; - } - - /** {@inheritDoc} */ - @Override - public boolean equals(final Object object) { - return (object instanceof Method) && ((Method) object).getName().equals(getName()); - } - - /** - * Returns the description. - * - * @return The description. - */ - public String getDescription() { - return this.description; - } - - /** - * Returns the name. - * - * @return The name. - */ - public String getName() { - return name; - } - - /** - * Returns the URI of the specification describing the method. - * - * @return The URI of the specification describing the method. - */ - public String getUri() { - return this.uri; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (getName() == null) ? 0 : getName().hashCode(); - } - - /** - * Indicates if the side-effects of several requests is the same as a single - * request. - * - * @return True if the method is idempotent. - */ - public boolean isIdempotent() { - return idempotent; - } - - /** - * Indicates if the method replies with a response. - * - * @return True if the method replies with a response. - */ - public boolean isReplying() { - return replying; - } - - /** - * Indicates if it should have the significance of taking an action other than - * retrieval. - * - * @return True if the method is safe. - */ - public boolean isSafe() { - return safe; - } - - /** - * Returns the name. - * - * @return The name. - */ - @Override - public String toString() { - return getName(); - } + /** Map of registered methods. */ + private static final Map _methods = new ConcurrentHashMap<>(); + + /** Pseudo-method use to match all methods. */ + public static final Method ALL = new Method("*", "Pseudo-method use to match all methods."); + + private static final String BASE_HTTP = "http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html"; + + /** + * Used with a proxy that can dynamically switch to being a tunnel. + * + * @see HTTP RFC - 9.9 + * CONNECT + */ + public static final Method CONNECT = + new Method( + "CONNECT", + "Used with a proxy that can dynamically switch to being a tunnel", + BASE_HTTP + "#sec9.9", + false, + false); + + /** + * Requests that the origin server deletes the resource identified by the request URI. + * + * @see HTTP RFC - 9.7 + * DELETE + */ + public static final Method DELETE = + new Method( + "DELETE", + "Requests that the origin server deletes the resource identified by the request URI", + BASE_HTTP + "#sec9.7", + false, + true); + + /** + * Retrieves whatever information (in the form of an entity) that is identified by the request + * URI. + * + * @see HTTP RFC - 9.3 + * GET + */ + public static final Method GET = + new Method( + "GET", + "Retrieves whatever information (in the form of an entity) that is identified by the request URI", + BASE_HTTP + "#sec9.3", + true, + true); + + /** + * Identical to GET except that the server must not return a message body in the response but + * only the message header. + * + * @see HTTP RFC - 9.4 + * HEAD + */ + public static final Method HEAD = + new Method( + "HEAD", + "Identical to GET except that the server must not return a message body in the response", + BASE_HTTP + "#sec9.4", + true, + true); + + /** + * Requests for information about the communication options available on the request/response + * chain identified by the URI. + * + * @see HTTP RFC - 9.2 + * OPTIONS + */ + public static final Method OPTIONS = + new Method( + "OPTIONS", + "Requests for information about the communication options available on the request/response chain identified by the URI", + BASE_HTTP + "#sec9.2", + true, + true); + + /** + * Requests that the origin server applies partial modifications contained in the entity + * enclosed in the request to the resource identified by the request URI. + * + * @see HTTP PATCH RFC 5789 + */ + public static final Method PATCH = + new Method( + "PATCH", + "Requests that the origin server applies partial modifications to the resource identified by the request URI", + "http://tools.ietf.org/html/rfc5789", + false, + false); + + /** + * Requests that the origin server accepts the entity enclosed in the request as a new + * subordinate of the resource identified by the request URI. + * + * @see HTTP RFC - 9.5 + * POST + */ + public static final Method POST = + new Method( + "POST", + "Requests that the origin server accepts the entity enclosed in the request as a new subordinate of the resource identified by the request URI", + BASE_HTTP + "#sec9.5", + false, + false); + + /** + * Requests that the enclosed entity be stored under the supplied request URI. + * + * @see + */ + public static final Method PUT = + new Method( + "PUT", + "Requests that the enclosed entity be stored under the supplied request URI", + BASE_HTTP + "#sec9.6", + false, + true); + + /** + * Used to invoke a remote, application-layer loop-back of the request message. + * + * @see HTTP RFC - 9.8 + * TRACE + */ + public static final Method TRACE = + new Method( + "TRACE", + "Used to invoke a remote, application-layer loop-back of the request message", + BASE_HTTP + "#sec9.8", + true, + true); + + /** + * Adds a new Method to the list of registered methods. + * + * @param method The method to register. + */ + public static void register(Method method) { + String name = (method == null) ? null : method.getName().toLowerCase(); + if ((name != null) && !name.isEmpty()) { + _methods.put(name, method); + } + } + + /** + * Sorts the given list of methods by name. + * + * @param methods The methods to sort. + */ + public static void sort(List methods) { + methods.sort(Comparator.comparing(Method::getName)); + } + + /** + * Returns the method associated with a given method name. If an existing constant exists, then + * it is returned, otherwise a new instance is created. + * + * @param name The method name. + * @return The associated method. + */ + public static Method valueOf(final String name) { + Method result = null; + + if ((name != null) && !name.isEmpty()) { + result = Method._methods.get(name.toLowerCase()); + if (result == null) { + result = new Method(name); + } + } + + return result; + } + + /** The description. */ + private final String description; + + /** Indicates if the side effects of several requests is the same as a single request. */ + private volatile boolean idempotent; + + /** The name. */ + private volatile String name; + + /** Indicates if the method replies with a response. */ + private final boolean replying; + + /** Indicates if it should have the significance of taking an action other than retrieval. */ + private final boolean safe; + + /** The URI of the specification describing the method. */ + private volatile String uri; + + static { + // Let the engine register all methods (the default ones and the ones to + // be discovered) as soon as the Method class is loaded or at least + // used. + Engine.getInstance(); + } + + /** + * Constructor for unsafe and non-idempotent methods. + * + * @param name The technical name of the method. + * @see org.restlet.data.Method#valueOf(String) + */ + public Method(final String name) { + this(name, null); + } + + /** + * Constructor for unsafe and non-idempotent methods. + * + * @param name The technical name of the method. + * @param description The description. + * @see org.restlet.data.Method#valueOf(String) + */ + public Method(String name, String description) { + this(name, description, null, false, false); + } + + /** + * Constructor for unsafe and non-idempotent methods. + * + * @param name The technical name. + * @param description The description. + * @param uri The URI of the specification describing the method. + * @see org.restlet.data.Method#valueOf(String) + */ + public Method(String name, String description, String uri) { + this(name, description, uri, false, false); + } + + /** + * Constructor for methods that reply to requests with responses. + * + * @param name The technical name. + * @param description The description. + * @param uri The URI of the specification describing the method. + * @param safe Indicates if the method is safe. + * @param idempotent Indicates if the method is idempotent. + * @see org.restlet.data.Method#valueOf(String) + */ + public Method(String name, String description, String uri, boolean safe, boolean idempotent) { + this(name, description, uri, safe, idempotent, true); + } + + /** + * Constructor. + * + * @param name The technical name. + * @param description The description. + * @param uri The URI of the specification describing the method. + * @param safe Indicates if the method is safe. + * @param idempotent Indicates if the method is idempotent. + * @param replying Indicates if the method replies with a response. + * @see org.restlet.data.Method#valueOf(String) + */ + public Method( + String name, + String description, + String uri, + boolean safe, + boolean idempotent, + boolean replying) { + this.name = name; + this.description = description; + this.uri = uri; + this.safe = safe; + this.idempotent = idempotent; + this.replying = replying; + } + + /** + * Compares this method to another. Based on the method name. + * + * @param o The other method. + */ + public int compareTo(Method o) { + if (o != null) { + return this.getName().compareTo(o.getName()); + } + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Method that)) { + return false; + } + return Objects.equals(getName(), that.getName()); + } + + /** + * Returns the description. + * + * @return The description. + */ + public String getDescription() { + return this.description; + } + + /** + * Returns the name. + * + * @return The name. + */ + public String getName() { + return name; + } + + /** + * Returns the URI of the specification describing the method. + * + * @return The URI of the specification describing the method. + */ + public String getUri() { + return this.uri; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (getName() == null) ? 0 : getName().hashCode(); + } + + /** + * Indicates if the side effects of several requests is the same as a single request. + * + * @return True if the method is idempotent. + */ + public boolean isIdempotent() { + return idempotent; + } + + /** + * Indicates if the method replies with a response. + * + * @return True if the method replies with a response. + */ + public boolean isReplying() { + return replying; + } + + /** + * Indicates if it should have the significance of taking an action other than retrieval. + * + * @return True if the method is safe. + */ + public boolean isSafe() { + return safe; + } + + /** + * Returns the name. + * + * @return The name. + */ + @Override + public String toString() { + return getName(); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Parameter.java b/org.restlet/src/main/java/org/restlet/data/Parameter.java index 8334d8d17f..afd199c605 100644 --- a/org.restlet/src/main/java/org/restlet/data/Parameter.java +++ b/org.restlet/src/main/java/org/restlet/data/Parameter.java @@ -1,41 +1,37 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import java.io.IOException; +import java.util.Objects; import org.restlet.engine.util.SystemUtils; import org.restlet.util.NamedValue; import org.restlet.util.Series; -import java.io.IOException; -import java.util.Objects; - /** - * Multi-usage parameter. Note that the name and value properties are thread - * safe, stored in volatile members. - * + * Multi-usage parameter. Note that the name and value properties are thread-safe, stored in + * volatile members. + * * @author Jerome Louvel */ public class Parameter implements Comparable, NamedValue { - /** The first object. */ - private volatile String name; + /** The first object. */ + private volatile String name; - /** The second object. */ - private volatile String value; + /** The second object. */ + private volatile String value; /** - * Creates a series that includes the current parameter as the initial - * entry. - * - * @return A series that includes the current parameter as the initial - * entry. + * Creates a series that includes the current parameter as the initial entry. + * + * @return A series that includes the current parameter as the initial entry. */ public Series createSeries() { Series result = new Form(); @@ -43,139 +39,135 @@ public Series createSeries() { return result; } - /** - * Creates a parameter. - * - * @param name The parameter name buffer. - * @param value The parameter value buffer (can be null). - * @return The created parameter. - * @throws IOException - */ - public static Parameter create(CharSequence name, CharSequence value) { - if (value != null) { - return new Parameter(name.toString(), value.toString()); - } else { - return new Parameter(name.toString(), null); - } - } - - /** - * Default constructor. - */ - public Parameter() { - this(null, null); - } - - /** - * Preferred constructor. - * - * @param name The name. - * @param value The value. - */ - public Parameter(String name, String value) { - this.name = name; - this.value = value; - } - - /* - * (non-Javadoc) - * - * @see org.restlet.data.NamedValue#compareTo(org.restlet.data.Parameter) - */ - public int compareTo(Parameter o) { - return getName().compareTo(o.getName()); - } - - /** - * Encodes the parameter into the target buffer. - * - * @param buffer The target buffer. - * @param characterSet The character set to use. - * @throws IOException - */ - public void encode(Appendable buffer, CharacterSet characterSet) throws IOException { - if (getName() != null) { - buffer.append(Reference.encode(getName(), characterSet)); - - if (getValue() != null) { - buffer.append('='); - buffer.append(Reference.encode(getValue(), characterSet)); - } - } - } - - /** - * Encodes the parameter as a string. - * - * @param characterSet The character set to use. - * @return The encoded string? - * @throws IOException - */ - public String encode(CharacterSet characterSet) throws IOException { - StringBuilder sb = new StringBuilder(); - encode(sb, characterSet); - return sb.toString(); - } - - /** {@inheritDoc} */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof Parameter that) { - return Objects.equals(getName(), that.getName()) - && Objects.equals(getValue(), that.getValue()); + /** + * Creates a parameter. + * + * @param name The parameter name buffer. + * @param value The parameter value buffer (can be null). + * @return The created parameter. + * @throws IOException + */ + public static Parameter create(CharSequence name, CharSequence value) { + if (value != null) { + return new Parameter(name.toString(), value.toString()); + } else { + return new Parameter(name.toString(), null); } - return false; + } + + /** Default constructor. */ + public Parameter() { + this(null, null); + } + /** + * Preferred constructor. + * + * @param name The name. + * @param value The value. + */ + public Parameter(String name, String value) { + this.name = name; + this.value = value; } - /* - * (non-Javadoc) - * - * @see org.restlet.data.NamedValue#getName() - */ - public String getName() { - return this.name; - } - - /* - * (non-Javadoc) - * - * @see org.restlet.data.NamedValue#getValue() - */ - public String getValue() { - return this.value; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return SystemUtils.hashCode(getName(), getValue()); - } - - /* - * (non-Javadoc) - * - * @see org.restlet.data.NamedValue#setName(java.lang.String) - */ - public void setName(String name) { - this.name = name; - } - - /* - * (non-Javadoc) - * - * @see org.restlet.data.NamedValue#setValue(java.lang.String) - */ - public void setValue(String value) { - this.value = value; - } - - @Override - public String toString() { - return "[" + getName() + "=" + getValue() + "]"; - } + /* + * (non-Javadoc) + * + * @see org.restlet.data.NamedValue#compareTo(org.restlet.data.Parameter) + */ + public int compareTo(Parameter o) { + return getName().compareTo(o.getName()); + } + /** + * Encodes the parameter into the target buffer. + * + * @param buffer The target buffer. + * @param characterSet The character set to use. + * @throws IOException + */ + public void encode(Appendable buffer, CharacterSet characterSet) throws IOException { + if (getName() != null) { + buffer.append(Reference.encode(getName(), characterSet)); + + if (getValue() != null) { + buffer.append('='); + buffer.append(Reference.encode(getValue(), characterSet)); + } + } + } + + /** + * Encodes the parameter as a string. + * + * @param characterSet The character set to use. + * @return The encoded string? + * @throws IOException + */ + public String encode(CharacterSet characterSet) throws IOException { + StringBuilder sb = new StringBuilder(); + encode(sb, characterSet); + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Parameter that)) { + return false; + } + return Objects.equals(getName(), that.getName()) + && Objects.equals(getValue(), that.getValue()); + } + + /* + * (non-Javadoc) + * + * @see org.restlet.data.NamedValue#getName() + */ + public String getName() { + return this.name; + } + + /* + * (non-Javadoc) + * + * @see org.restlet.data.NamedValue#getValue() + */ + public String getValue() { + return this.value; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return SystemUtils.hashCode(getName(), getValue()); + } + + /* + * (non-Javadoc) + * + * @see org.restlet.data.NamedValue#setName(java.lang.String) + */ + public void setName(String name) { + this.name = name; + } + + /* + * (non-Javadoc) + * + * @see org.restlet.data.NamedValue#setValue(java.lang.String) + */ + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "[" + getName() + "=" + getValue() + "]"; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Preference.java b/org.restlet/src/main/java/org/restlet/data/Preference.java index 8d97568de8..72b2f01375 100644 --- a/org.restlet/src/main/java/org/restlet/data/Preference.java +++ b/org.restlet/src/main/java/org/restlet/data/Preference.java @@ -1,137 +1,133 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; import org.restlet.util.Series; /** * Metadata preference definition. - * + * * @author Jerome Louvel */ public final class Preference { - /** The metadata associated with this preference. */ - private volatile T metadata; - - /** The modifiable list of parameters. */ - private volatile Series parameters; - - /** The quality/preference level. */ - private volatile float quality; - - /** - * Constructor. - */ - public Preference() { - this(null, 1F, null); - } - - /** - * Constructor. - * - * @param metadata The associated metadata. - */ - public Preference(T metadata) { - this(metadata, 1F, null); - } - - /** - * Constructor. - * - * @param metadata The associated metadata. - * @param quality The quality/preference level. - */ - public Preference(T metadata, float quality) { - this(metadata, quality, null); - } - - /** - * Constructor. - * - * @param metadata The associated metadata. - * @param quality The quality/preference level. - * @param parameters The list of parameters. - */ - public Preference(T metadata, float quality, Series parameters) { - this.metadata = metadata; - this.quality = quality; - this.parameters = parameters; - } - - /** - * Returns the metadata associated with this preference. - * - * @return The metadata associated with this preference. - */ - public T getMetadata() { - return this.metadata; - } - - /** - * Returns the modifiable list of parameters. Creates a new instance if no one - * has been set. - * - * @return The modifiable list of parameters. - */ - public Series getParameters() { - // Lazy initialization with double-check. - Series p = this.parameters; - if (p == null) { - synchronized (this) { - p = this.parameters; - if (p == null) { - this.parameters = p = new Series(Parameter.class); - } - } - } - return p; - } - - /** - * Returns the quality/preference level. - * - * @return The quality/preference level. - */ - public float getQuality() { - return this.quality; - } - - /** - * Sets the metadata associated with this preference. - * - * @param metadata The metadata associated with this preference. - */ - public void setMetadata(T metadata) { - this.metadata = metadata; - } - - /** - * Sets the modifiable list of parameters. - * - * @param parameters The modifiable list of parameters. - */ - public void setParameters(Series parameters) { - this.parameters = parameters; - } - - /** - * Sets the quality/preference level. - * - * @param quality The quality/preference level. - */ - public void setQuality(float quality) { - this.quality = quality; - } - - @Override - public String toString() { - return (getMetadata() == null) ? "" : (getMetadata().getName() + ":" + getQuality()); - } + /** The metadata associated with this preference. */ + private volatile T metadata; + + /** The modifiable list of parameters. */ + private volatile Series parameters; + + /** The quality/preference level. */ + private volatile float quality; + + /** Constructor. */ + public Preference() { + this(null, 1F, null); + } + + /** + * Constructor. + * + * @param metadata The associated metadata. + */ + public Preference(T metadata) { + this(metadata, 1F, null); + } + + /** + * Constructor. + * + * @param metadata The associated metadata. + * @param quality The quality/preference level. + */ + public Preference(T metadata, float quality) { + this(metadata, quality, null); + } + + /** + * Constructor. + * + * @param metadata The associated metadata. + * @param quality The quality/preference level. + * @param parameters The list of parameters. + */ + public Preference(T metadata, float quality, Series parameters) { + this.metadata = metadata; + this.quality = quality; + this.parameters = parameters; + } + + /** + * Returns the metadata associated with this preference. + * + * @return The metadata associated with this preference. + */ + public T getMetadata() { + return this.metadata; + } + + /** + * Returns the modifiable list of parameters. Creates a new instance if no one has been set. + * + * @return The modifiable list of parameters. + */ + public Series getParameters() { + // Lazy initialization with double-check. + Series p = this.parameters; + if (p == null) { + synchronized (this) { + p = this.parameters; + if (p == null) { + this.parameters = p = new Series<>(Parameter.class); + } + } + } + return p; + } + + /** + * Returns the quality/preference level. + * + * @return The quality/preference level. + */ + public float getQuality() { + return this.quality; + } + + /** + * Sets the metadata associated with this preference. + * + * @param metadata The metadata associated with this preference. + */ + public void setMetadata(T metadata) { + this.metadata = metadata; + } + + /** + * Sets the modifiable list of parameters. + * + * @param parameters The modifiable list of parameters. + */ + public void setParameters(Series parameters) { + this.parameters = parameters; + } + + /** + * Sets the quality/preference level. + * + * @param quality The quality/preference level. + */ + public void setQuality(float quality) { + this.quality = quality; + } + + @Override + public String toString() { + return (getMetadata() == null) ? "" : (getMetadata().getName() + ":" + getQuality()); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Product.java b/org.restlet/src/main/java/org/restlet/data/Product.java index 034573b151..d3c11731dc 100644 --- a/org.restlet/src/main/java/org/restlet/data/Product.java +++ b/org.restlet/src/main/java/org/restlet/data/Product.java @@ -1,101 +1,97 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; /** - * Product tokens are used to allow communicating applications to identify - * themselves by software name and version. - * + * Product tokens are used to allow communicating applications to identify themselves by software + * name and version. + * * @author Thierry Boileau - * @see User-Agent - * @see Product - * Tokens + * @see User-Agent + * @see Product Tokens */ public class Product { - /** Comment. */ - private volatile String comment; + /** Comment. */ + private volatile String comment; - /** Product name. */ - private volatile String name; + /** Product name. */ + private volatile String name; - /** Version number. */ - private volatile String version; + /** Version number. */ + private volatile String version; - /** - * Constructor. - * - * @param name The product name. - * @param version The product version. - * @param comment The product comment. - */ - public Product(String name, String version, String comment) { - super(); - this.name = name; - this.version = version; - this.comment = comment; - } + /** + * Constructor. + * + * @param name The product name. + * @param version The product version. + * @param comment The product comment. + */ + public Product(String name, String version, String comment) { + super(); + this.name = name; + this.version = version; + this.comment = comment; + } - /** - * Returns the facultative comment. - * - * @return The facultative comment. - */ - public String getComment() { - return this.comment; - } + /** + * Returns the facultative comment. + * + * @return The facultative comment. + */ + public String getComment() { + return this.comment; + } - /** - * Returns the product name. - * - * @return The product name. - */ - public String getName() { - return this.name; - } + /** + * Returns the product name. + * + * @return The product name. + */ + public String getName() { + return this.name; + } - /** - * Returns the version of the product. - * - * @return The version of the product. - */ - public String getVersion() { - return this.version; - } + /** + * Returns the version of the product. + * + * @return The version of the product. + */ + public String getVersion() { + return this.version; + } - /** - * Sets the facultative comment. - * - * @param comment The facultative comment. - */ - public void setComment(String comment) { - this.comment = comment; - } + /** + * Sets the facultative comment. + * + * @param comment The facultative comment. + */ + public void setComment(String comment) { + this.comment = comment; + } - /** - * Sets the product name. - * - * @param name The product name. - */ - public void setName(String name) { - this.name = name; - } + /** + * Sets the product name. + * + * @param name The product name. + */ + public void setName(String name) { + this.name = name; + } - /** - * Sets the version of the product. - * - * @param version The version of the product. - */ - public void setVersion(String version) { - this.version = version; - } + /** + * Sets the version of the product. + * + * @param version The version of the product. + */ + public void setVersion(String version) { + this.version = version; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Protocol.java b/org.restlet/src/main/java/org/restlet/data/Protocol.java index a87ed066a5..04e0d034ac 100644 --- a/org.restlet/src/main/java/org/restlet/data/Protocol.java +++ b/org.restlet/src/main/java/org/restlet/data/Protocol.java @@ -1,349 +1,389 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; import org.restlet.engine.util.StringUtils; /** - * Protocol used by client and server connectors. Connectors enable the - * communication between components by implementing standard protocols. - * + * Protocol used by client and server connectors. Connectors enable the communication between + * components by implementing standard protocols. + * * @author Jerome Louvel */ public final class Protocol { - /** Indicates that the port number is undefined. */ - public static final int UNKNOWN_PORT = -1; - - /** All-protocols wildcard. */ - public static final Protocol ALL = new Protocol("all", "ALL", "Wildcard for all protocols", UNKNOWN_PORT); - - /** - * CLAP (ClassLoader Access Protocol) is a custom scheme to access to - * representations via classloaders. Example URI: - * "clap://thread/org/restlet/Restlet.class".
- *
- * In order to work, CLAP requires a client connector provided by the core - * Restlet engine. - * - * @see org.restlet.data.LocalReference - */ - public static final Protocol CLAP = new Protocol("clap", "CLAP", "Class Loader Access Protocol", UNKNOWN_PORT, - true); - - /** - * FILE is a standard scheme to access to representations stored in the file - * system (locally most of the time). Example URI: - * "file:///D/root/index.html".
- *
- * In order to work, FILE requires a client connector provided by the core - * Restlet engine. - * - * @see org.restlet.data.LocalReference - */ - public static final Protocol FILE = new Protocol("file", "FILE", "Local File System Protocol", UNKNOWN_PORT, true); - - /** FTP protocol. */ - public static final Protocol FTP = new Protocol("ftp", "FTP", "File Transfer Protocol", 21); - - /** HTTP protocol. */ - public static final Protocol HTTP = new Protocol("http", "HTTP", "HyperText Transport Protocol", 80, "1.1"); - - /** HTTPS protocol (via SSL socket). */ - public static final Protocol HTTPS = new Protocol("https", "HTTPS", "HTTP", "HyperText Transport Protocol (Secure)", - 443, true, "1.1"); - - /** - * JAR (Java ARchive) is a common scheme to access to representations inside - * archive files. Example URI: - * "jar:http://www.foo.com/bar/baz.jar!/COM/foo/Quux.class". - * - * @see org.restlet.data.LocalReference#createJarReference(Reference, String) - */ - public static final Protocol JAR = new Protocol("jar", "JAR", "Java ARchive", UNKNOWN_PORT, true); - - /** JDBC protocol. */ - public static final Protocol JDBC = new Protocol("jdbc", "JDBC", "Java DataBase Connectivity", UNKNOWN_PORT); - - /** - * RIAP (Restlet Internal Access Protocol) is a custom scheme to access - * representations via internal calls to virtual hosts/components. Example URIs: - * "riap://component/myAppPath/myResource" and - * "riap://application/myResource".
- *
- * In order to work, RIAP doesn't require any client connector and is - * automatically supported by the Restlet engine. - * - * @see org.restlet.data.LocalReference - */ - public static final Protocol RIAP = new Protocol("riap", "RIAP", "Restlet Internal Access Protocol", UNKNOWN_PORT, - true); - - /** Local Web Archive access protocol. */ - public static final Protocol WAR = new Protocol("war", "WAR", "Web Archive Access Protocol", UNKNOWN_PORT, true); - - /** - * ZIP is a special scheme to access to representations inside Zip archive - * files. Example URI: "zip:file:///tmp/test.zip!/test.txt". - * - * @see org.restlet.data.LocalReference#createZipReference(Reference, String) - */ - public static final Protocol ZIP = new Protocol("zip", "ZIP", "Zip Archive Access Protocol", UNKNOWN_PORT, true); - - /** - * Creates the protocol associated to a URI scheme name. If an existing constant - * exists then it is returned, otherwise a new instance is created. - * - * @param name The scheme name. - * @return The associated protocol. - */ - public static Protocol valueOf(String name) { - Protocol result = null; - - if (!StringUtils.isNullOrEmpty(name)) { - if (name.equalsIgnoreCase(CLAP.getSchemeName())) { - result = CLAP; - } else if (name.equalsIgnoreCase(FILE.getSchemeName())) { - result = FILE; - } else if (name.equalsIgnoreCase(FTP.getSchemeName())) { - result = FTP; - } else if (name.equalsIgnoreCase(HTTP.getSchemeName())) { - result = HTTP; - } else if (name.equalsIgnoreCase(HTTPS.getSchemeName())) { - result = HTTPS; - } else if (name.equalsIgnoreCase(JAR.getSchemeName())) { - result = JAR; - } else if (name.equalsIgnoreCase(JDBC.getSchemeName())) { - result = JDBC; - } else if (name.equalsIgnoreCase(RIAP.getSchemeName())) { - result = RIAP; - } else if (name.equalsIgnoreCase(WAR.getSchemeName())) { - result = WAR; - } else if (name.equalsIgnoreCase(ZIP.getSchemeName())) { - result = ZIP; - } else { - result = new Protocol(name); - } - } - - return result; - } - - /** - * Creates the protocol associated to a URI scheme name. If an existing constant - * exists then it is returned, otherwise a new instance is created. - * - * @param name The scheme name. - * @param version The version number. - * @return The associated protocol. - */ - public static Protocol valueOf(String name, String version) { - Protocol result = valueOf(name); - - if (!version.equals(result.getVersion())) { - result = new Protocol(result.getSchemeName(), result.getName(), result.getTechnicalName(), - result.getDescription(), result.getDefaultPort(), result.isConfidential(), version); - } - - return result; - } - - /** The confidentiality. */ - private final boolean confidential; - - /** The default port if known or -1. */ - private final int defaultPort; - - /** The description. */ - private final String description; - - /** The name. */ - private final String name; - - /** The scheme name. */ - private final String schemeName; - - /** The technical name that appears on the wire. */ - private final String technicalName; - - /** The version. */ - private final String version; - - /** - * Constructor. - * - * @param schemeName The scheme name. - */ - public Protocol(String schemeName) { - this(schemeName, schemeName.toUpperCase(), schemeName.toUpperCase() + " Protocol", UNKNOWN_PORT); - } - - /** - * Constructor. - * - * @param schemeName The scheme name. - * @param name The unique name. - * @param description The description. - * @param defaultPort The default port. - */ - public Protocol(String schemeName, String name, String description, int defaultPort) { - this(schemeName, name, description, defaultPort, false); - } - - /** - * Constructor. - * - * @param schemeName The scheme name. - * @param name The unique name. - * @param description The description. - * @param defaultPort The default port. - * @param confidential The confidentiality. - */ - public Protocol(String schemeName, String name, String description, int defaultPort, boolean confidential) { - this(schemeName, name, description, defaultPort, confidential, null); - } - - /** - * Constructor. - * - * @param schemeName The scheme name. - * @param name The unique name. - * @param description The description. - * @param defaultPort The default port. - * @param confidential The confidentiality. - * @param version The version. - */ - public Protocol(String schemeName, String name, String description, int defaultPort, boolean confidential, - String version) { - this(schemeName, name, name, description, defaultPort, confidential, version); - } - - /** - * Constructor. - * - * @param schemeName The scheme name. - * @param name The unique name. - * @param description The description. - * @param defaultPort The default port. - * @param version The version. - */ - public Protocol(String schemeName, String name, String description, int defaultPort, String version) { - this(schemeName, name, description, defaultPort, false, version); - } - - /** - * Constructor. - * - * @param schemeName The scheme name. - * @param name The unique name. - * @param technicalName The technical name that appears on the wire. - * @param description The description. - * @param defaultPort The default port. - * @param confidential The confidentiality. - * @param version The version. - */ - public Protocol(String schemeName, String name, String technicalName, String description, int defaultPort, - boolean confidential, String version) { - this.name = name; - this.description = description; - this.schemeName = schemeName; - this.technicalName = technicalName; - this.defaultPort = defaultPort; - this.confidential = confidential; - this.version = version; - } - - /** {@inheritDoc} */ - @Override - public boolean equals(final Object object) { - return (object instanceof Protocol) && getName().equalsIgnoreCase(((Protocol) object).getName()); - } - - /** - * Returns the default port number. - * - * @return The default port number. - */ - public int getDefaultPort() { - return this.defaultPort; - } - - /** - * Returns the description. - * - * @return The description. - */ - public String getDescription() { - return this.description; - } - - /** - * Returns the name. - * - * @return The name. - */ - public String getName() { - return name; - } - - /** - * Returns the URI scheme name. - * - * @return The URI scheme name. - */ - public String getSchemeName() { - return this.schemeName; - } - - /** - * Returns the technical name that appears on the wire. - * - * @return The technical name that appears on the wire. - */ - public String getTechnicalName() { - return technicalName; - } - - /** - * Returns the version. - * - * @return The version. - */ - public String getVersion() { - return version; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return (getName() == null) ? 0 : getName().toLowerCase().hashCode(); - } - - /** - * Indicates if the protocol guarantees the confidentially of the messages - * exchanged, for example via a SSL-secured connection. - * - * @return True if the protocol is confidential. - */ - public boolean isConfidential() { - return this.confidential; - } - - /** - * Returns the name. - * - * @return The name. - */ - @Override - public String toString() { - return getName() + ((getVersion() == null) ? "" : "/" + getVersion()); - } - + /** Indicates that the port number is undefined. */ + public static final int UNKNOWN_PORT = -1; + + /** All-protocols wildcard. */ + public static final Protocol ALL = + new Protocol("all", "ALL", "Wildcard for all protocols", UNKNOWN_PORT); + + /** + * CLAP (ClassLoader Access Protocol) is a custom scheme to access to representations via + * classloaders. Example URI: "clap://thread/org/restlet/Restlet.class".
+ *
+ * To work, CLAP requires a client connector provided by the core Restlet engine. + * + * @see org.restlet.data.LocalReference + */ + public static final Protocol CLAP = + new Protocol("clap", "CLAP", "Class Loader Access Protocol", UNKNOWN_PORT, true); + + /** + * FILE is a standard scheme to access to representations stored in the file system (locally + * most of the time). Example URI: "file:///D/root/index.html".
+ *
+ * To work, FILE requires a client connector provided by the core Restlet engine. + * + * @see org.restlet.data.LocalReference + */ + public static final Protocol FILE = + new Protocol("file", "FILE", "Local File System Protocol", UNKNOWN_PORT, true); + + /** FTP protocol. */ + public static final Protocol FTP = new Protocol("ftp", "FTP", "File Transfer Protocol", 21); + + /** HTTP protocol. */ + public static final Protocol HTTP = + new Protocol("http", "HTTP", "HyperText Transport Protocol", 80, "1.1"); + + /** HTTPS protocol (via SSL socket). */ + public static final Protocol HTTPS = + new Protocol( + "https", + "HTTPS", + "HTTP", + "HyperText Transport Protocol (Secure)", + 443, + true, + "1.1"); + + /** + * JAR (Java ARchive) is a common scheme to access to representations inside archive files. + * Example URI: "jar:http://www.foo.com/bar/baz.jar!/COM/foo/Quux.class". + * + * @see org.restlet.data.LocalReference#createJarReference(Reference, String) + */ + public static final Protocol JAR = + new Protocol("jar", "JAR", "Java ARchive", UNKNOWN_PORT, true); + + /** JDBC protocol. */ + public static final Protocol JDBC = + new Protocol("jdbc", "JDBC", "Java DataBase Connectivity", UNKNOWN_PORT); + + /** + * RIAP (Restlet Internal Access Protocol) is a custom scheme to access representations via + * internal calls to virtual hosts/components. Example URIs: + * "riap://component/myAppPath/myResource" and "riap://application/myResource".
+ *
+ * To work, RIAP doesn't require any client connector and is automatically supported by the + * Restlet engine. + * + * @see org.restlet.data.LocalReference + */ + public static final Protocol RIAP = + new Protocol("riap", "RIAP", "Restlet Internal Access Protocol", UNKNOWN_PORT, true); + + /** Local Web Archive access protocol. */ + public static final Protocol WAR = + new Protocol("war", "WAR", "Web Archive Access Protocol", UNKNOWN_PORT, true); + + /** + * ZIP is a special scheme to access to representations inside Zip archive files. Example URI: + * "zip:file:///tmp/test.zip!/test.txt". + * + * @see org.restlet.data.LocalReference#createZipReference(Reference, String) + */ + public static final Protocol ZIP = + new Protocol("zip", "ZIP", "Zip Archive Access Protocol", UNKNOWN_PORT, true); + + /** + * Creates the protocol associated with a URI scheme name. If an existing constant exists, then + * it is returned, otherwise a new instance is created. + * + * @param name The scheme name. + * @return The associated protocol. + */ + public static Protocol valueOf(String name) { + Protocol result = null; + + if (!StringUtils.isNullOrEmpty(name)) { + if (name.equalsIgnoreCase(CLAP.getSchemeName())) { + result = CLAP; + } else if (name.equalsIgnoreCase(FILE.getSchemeName())) { + result = FILE; + } else if (name.equalsIgnoreCase(FTP.getSchemeName())) { + result = FTP; + } else if (name.equalsIgnoreCase(HTTP.getSchemeName())) { + result = HTTP; + } else if (name.equalsIgnoreCase(HTTPS.getSchemeName())) { + result = HTTPS; + } else if (name.equalsIgnoreCase(JAR.getSchemeName())) { + result = JAR; + } else if (name.equalsIgnoreCase(JDBC.getSchemeName())) { + result = JDBC; + } else if (name.equalsIgnoreCase(RIAP.getSchemeName())) { + result = RIAP; + } else if (name.equalsIgnoreCase(WAR.getSchemeName())) { + result = WAR; + } else if (name.equalsIgnoreCase(ZIP.getSchemeName())) { + result = ZIP; + } else { + result = new Protocol(name); + } + } + + return result; + } + + /** + * Creates the protocol associated with a URI scheme name. If an existing constant exists, then + * it is returned, otherwise a new instance is created. + * + * @param name The scheme name. + * @param version The version number. + * @return The associated protocol. + */ + public static Protocol valueOf(String name, String version) { + Protocol result = valueOf(name); + + if (result != null && !version.equals(result.getVersion())) { + result = + new Protocol( + result.getSchemeName(), + result.getName(), + result.getTechnicalName(), + result.getDescription(), + result.getDefaultPort(), + result.isConfidential(), + version); + } + + return result; + } + + /** The confidentiality. */ + private final boolean confidential; + + /** The default port if known or -1. */ + private final int defaultPort; + + /** The description. */ + private final String description; + + /** The name. */ + private final String name; + + /** The scheme name. */ + private final String schemeName; + + /** The technical name that appears on the wire. */ + private final String technicalName; + + /** The version. */ + private final String version; + + /** + * Constructor. + * + * @param schemeName The scheme name. + */ + public Protocol(String schemeName) { + this( + schemeName, + schemeName.toUpperCase(), + schemeName.toUpperCase() + " Protocol", + UNKNOWN_PORT); + } + + /** + * Constructor. + * + * @param schemeName The scheme name. + * @param name The unique name. + * @param description The description. + * @param defaultPort The default port. + */ + public Protocol(String schemeName, String name, String description, int defaultPort) { + this(schemeName, name, description, defaultPort, false); + } + + /** + * Constructor. + * + * @param schemeName The scheme name. + * @param name The unique name. + * @param description The description. + * @param defaultPort The default port. + * @param confidential The confidentiality. + */ + public Protocol( + String schemeName, + String name, + String description, + int defaultPort, + boolean confidential) { + this(schemeName, name, description, defaultPort, confidential, null); + } + + /** + * Constructor. + * + * @param schemeName The scheme name. + * @param name The unique name. + * @param description The description. + * @param defaultPort The default port. + * @param confidential The confidentiality. + * @param version The version. + */ + public Protocol( + String schemeName, + String name, + String description, + int defaultPort, + boolean confidential, + String version) { + this(schemeName, name, name, description, defaultPort, confidential, version); + } + + /** + * Constructor. + * + * @param schemeName The scheme name. + * @param name The unique name. + * @param description The description. + * @param defaultPort The default port. + * @param version The version. + */ + public Protocol( + String schemeName, String name, String description, int defaultPort, String version) { + this(schemeName, name, description, defaultPort, false, version); + } + + /** + * Constructor. + * + * @param schemeName The scheme name. + * @param name The unique name. + * @param technicalName The technical name that appears on the wire. + * @param description The description. + * @param defaultPort The default port. + * @param confidential The confidentiality. + * @param version The version. + */ + public Protocol( + String schemeName, + String name, + String technicalName, + String description, + int defaultPort, + boolean confidential, + String version) { + this.name = name; + this.description = description; + this.schemeName = schemeName; + this.technicalName = technicalName; + this.defaultPort = defaultPort; + this.confidential = confidential; + this.version = version; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Protocol that)) { + return false; + } + return getName().equalsIgnoreCase(that.getName()); + } + + /** + * Returns the default port number. + * + * @return The default port number. + */ + public int getDefaultPort() { + return this.defaultPort; + } + + /** + * Returns the description. + * + * @return The description. + */ + public String getDescription() { + return this.description; + } + + /** + * Returns the name. + * + * @return The name. + */ + public String getName() { + return name; + } + + /** + * Returns the URI scheme name. + * + * @return The URI scheme name. + */ + public String getSchemeName() { + return this.schemeName; + } + + /** + * Returns the technical name that appears on the wire. + * + * @return The technical name that appears on the wire. + */ + public String getTechnicalName() { + return technicalName; + } + + /** + * Returns the version. + * + * @return The version. + */ + public String getVersion() { + return version; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (getName() == null) ? 0 : getName().toLowerCase().hashCode(); + } + + /** + * Indicates if the protocol guarantees the confidentially of the messages exchanged, for + * example via a SSL-secured connection. + * + * @return True if the protocol is confidential. + */ + public boolean isConfidential() { + return this.confidential; + } + + /** + * Returns the name. + * + * @return The name. + */ + @Override + public String toString() { + return getName() + ((getVersion() == null) ? "" : "/" + getVersion()); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Range.java b/org.restlet/src/main/java/org/restlet/data/Range.java index 03c65a0da8..fccc3d5343 100644 --- a/org.restlet/src/main/java/org/restlet/data/Range.java +++ b/org.restlet/src/main/java/org/restlet/data/Range.java @@ -1,242 +1,238 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; import org.restlet.engine.util.SystemUtils; /** * Describes a range of bytes. - * + * * @author Jerome Louvel */ public class Range { - /** - * Index for the first byte (or range unit) of an entity. - */ - public final static long INDEX_FIRST = 0; - - /** - * Index for the last byte (or range unit) of an entity. - */ - public final static long INDEX_LAST = -1; - - public final static String RANGE_BYTES_UNIT = "bytes"; - - /** - * Maximum size available from the index. - */ - public final static long SIZE_MAX = -1; - - /** - * Indicates if the unit of the given range is "bytes". - * - * @param range The range. - * @return true if the unit of the given range is "bytes". - */ - public static boolean isBytesRange(Range range) { - return RANGE_BYTES_UNIT.equals(range.getUnitName()); - } - - /** - * Index from which to start the range. If the index is superior or equal to - * zero, the index will define the start of the range. If its value is - * {@value #INDEX_LAST} (-1), then it defines the end of the range. The default - * value is {@link #INDEX_FIRST} (0), starting at the first byte. - */ - private volatile long index; - - /** - * Total size of the instance in number of bytes (or range unit). In the case of - * "bytes" range, this attribute is ignored, as the instance size is taken from - * the entity. - */ - private volatile long instanceSize; - - /** - * Size of the range in number of bytes (or range unit). If the size is the - * maximum available from the index, then use the {@value #SIZE_MAX} constant. - */ - private volatile long size; - - /** - * Specifies the unit of the range. The HTTP/1.1 protocol specifies only - * 'bytes', but other ranges are allowed {@link RFC 2616} - */ - private volatile String unitName; - - /** - * Default constructor defining a range starting on the first byte and with a - * maximum size, i.e., covering the whole entity. - */ - public Range() { - this(INDEX_FIRST, SIZE_MAX); - } - - /** - * Constructor defining a range starting on the first byte and with the given - * size. - * - * @param size Size of the range in number of bytes. - */ - public Range(long size) { - this(INDEX_FIRST, size); - } - - /** - * Constructor. Sets the name of the range unit as "bytes" by default. - * - * @param index Index from which to start the range - * @param size Size of the range in number of bytes. - */ - public Range(long index, long size) { - this.index = index; - this.size = size; - this.unitName = RANGE_BYTES_UNIT; - } - - /** - * Constructor. Sets the name of the range unit as "bytes" by default. - * - * @param index Index from which to start the range - * @param size Size of the range in number of bytes. - * @param instanceSize Size of the instance in number of bytes. - * @param unitName Unit of the range. - */ - public Range(long index, long size, long instanceSize, String unitName) { - this.index = index; - this.instanceSize = instanceSize; - this.size = size; - this.unitName = unitName; - } - - @Override - public boolean equals(Object object) { - return (object instanceof Range) && ((Range) object).getIndex() == getIndex() - && ((Range) object).getSize() == getSize(); - } - - /** - * Returns the index from which to start the range. If the index is superior or - * equal to zero, the index will define the start of the range. If its value is - * {@value #INDEX_LAST} (-1), then it defines the end of the range. The default - * value is {@link #INDEX_FIRST} (0), starting at the first byte. - * - * @return The index from which to start the range. - */ - public long getIndex() { - return index; - } - - /** - * Returns the total size of the instance in number of bytes (or range unit). In - * the case of "bytes" range, this attribute is ignored, as the instance size is - * taken from the entity. - * - * @return The total size of the instance. - */ - public long getInstanceSize() { - return instanceSize; - } - - /** - * Returns the size of the range in number of bytes. If the size is the maximum - * available from the index, then use the {@value #SIZE_MAX} constant. - * - * @return The size of the range in number of bytes. - */ - public long getSize() { - return size; - } - - /** - * Returns the name of the range unit. - * - * @return The name of the range unit. - */ - public String getUnitName() { - return unitName; - } - - @Override - public int hashCode() { - return SystemUtils.hashCode(index, instanceSize, size, unitName); - } - - /** - * Indicates if the given index is included in the range. - * - * @param position The position to test. - * @param totalSize - * - * @return True if the given index is included in the range, false otherwise. - */ - public boolean isIncluded(long position, long totalSize) { - boolean result = false; - - if (getIndex() == INDEX_LAST) { - // The range starts from the end - result = (0 <= position) && (position < totalSize); - - if (result) { - result = position >= (totalSize - getSize()); - } - } else { - // The range starts from the beginning - result = position >= getIndex(); - - if (result && (getSize() != SIZE_MAX)) { - result = position < getIndex() + getSize(); - } - } - - return result; - } - - /** - * Sets the index from which to start the range. If the index is superior or - * equal to zero, the index will define the start of the range. If its value is - * {@value #INDEX_LAST} (-1), then it defines the end of the range. The default - * value is {@link #INDEX_FIRST} (0), starting at the first byte - * - * @param index The index from which to start the range. - */ - public void setIndex(long index) { - this.index = index; - } - - /** - * Sets the total size of the instance in number of bytes (or range unit). - * - * @param instanceSize The total size of the instance. - */ - public void setInstanceSize(long instanceSize) { - this.instanceSize = instanceSize; - } - - /** - * Sets the size of the range in number of bytes. If the size is the maximum - * available from the index, then use the {@value #SIZE_MAX} constant. - * - * @param size The size of the range in number of bytes. - */ - public void setSize(long size) { - this.size = size; - } - - /** - * Sets the name of the range unit. - * - * @param unitName The name of the range unit. - */ - public void setUnitName(String unitName) { - this.unitName = unitName; - } + /** Index for the first byte (or range unit) of an entity. */ + public static final long INDEX_FIRST = 0; + + /** Index for the last byte (or range unit) of an entity. */ + public static final long INDEX_LAST = -1; + + public static final String RANGE_BYTES_UNIT = "bytes"; + + /** Maximum size available from the index. */ + public static final long SIZE_MAX = -1; + + /** + * Indicates if the unit of the given range is "bytes". + * + * @param range The range. + * @return true if the unit of the given range is "bytes". + */ + public static boolean isBytesRange(Range range) { + return RANGE_BYTES_UNIT.equals(range.getUnitName()); + } + + /** + * Index from which to start the range. If the index is superior or equal to zero, the index + * will define the start of the range. If its value is {@value #INDEX_LAST} (-1), then it + * defines the end of the range. The default value is {@link #INDEX_FIRST} (0), starting at the + * first byte. + */ + private volatile long index; + + /** + * Total size of the instance in number of bytes (or range unit). In the case of "bytes" range, + * this attribute is ignored, as the instance size is taken from the entity. + */ + private volatile long instanceSize; + + /** + * Size of the range in number of bytes (or range unit). If the size is the maximum available + * from the index, then use the {@value #SIZE_MAX} constant. + */ + private volatile long size; + + /** + * Specifies the unit of the range. The HTTP/1.1 protocol specifies only 'bytes', but other + * ranges are allowed {@link RFC 2616} + */ + private volatile String unitName; + + /** + * Default constructor defining a range starting on the first byte and with a maximum size, + * i.e., covering the whole entity. + */ + public Range() { + this(INDEX_FIRST, SIZE_MAX); + } + + /** + * Constructor defining a range starting on the first byte and with the given size. + * + * @param size Size of the range in number of bytes. + */ + public Range(long size) { + this(INDEX_FIRST, size); + } + + /** + * Constructor. Sets the name of the range unit as "bytes" by default. + * + * @param index Index from which to start the range + * @param size Size of the range in number of bytes. + */ + public Range(long index, long size) { + this.index = index; + this.size = size; + this.unitName = RANGE_BYTES_UNIT; + } + + /** + * Constructor. Sets the name of the range unit as "bytes" by default. + * + * @param index Index from which to start the range + * @param size Size of the range in number of bytes. + * @param instanceSize Size of the instance in number of bytes. + * @param unitName Unit of the range. + */ + public Range(long index, long size, long instanceSize, String unitName) { + this.index = index; + this.instanceSize = instanceSize; + this.size = size; + this.unitName = unitName; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Range that)) { + return false; + } + return getIndex() == that.getIndex() && getSize() == that.getSize(); + } + + /** + * Returns the index from which to start the range. If the index is superior or equal to zero, + * the index will define the start of the range. If its value is {@value #INDEX_LAST} (-1), then + * it defines the end of the range. The default value is {@link #INDEX_FIRST} (0), starting at + * the first byte. + * + * @return The index from which to start the range. + */ + public long getIndex() { + return index; + } + + /** + * Returns the total size of the instance in number of bytes (or range unit). In the case of the + * "bytes" range, this attribute is ignored, as the instance size is taken from the entity. + * + * @return The total size of the instance. + */ + public long getInstanceSize() { + return instanceSize; + } + + /** + * Returns the size of the range in number of bytes. If the size is the maximum available from + * the index, then use the {@value #SIZE_MAX} constant. + * + * @return The size of the range in number of bytes. + */ + public long getSize() { + return size; + } + + /** + * Returns the name of the range unit. + * + * @return The name of the range unit. + */ + public String getUnitName() { + return unitName; + } + + @Override + public int hashCode() { + return SystemUtils.hashCode(index, instanceSize, size, unitName); + } + + /** + * Indicates if the given index is included in the range. + * + * @param position The position to test. + * @param totalSize + * @return True if the given index is included in the range, false otherwise. + */ + public boolean isIncluded(long position, long totalSize) { + boolean result = false; + + if (getIndex() == INDEX_LAST) { + // The range starts from the end + result = (0 <= position) && (position < totalSize); + + if (result) { + result = position >= (totalSize - getSize()); + } + } else { + // The range starts from the beginning + result = position >= getIndex(); + + if (result && (getSize() != SIZE_MAX)) { + result = position < getIndex() + getSize(); + } + } + + return result; + } + + /** + * Sets the index from which to start the range. If the index is superior or equal to zero, the + * index will define the start of the range. If its value is {@value #INDEX_LAST} (-1), then it + * defines the end of the range. The default value is {@link #INDEX_FIRST} (0), starting at the + * first byte + * + * @param index The index from which to start the range. + */ + public void setIndex(long index) { + this.index = index; + } + + /** + * Sets the total size of the instance in number of bytes (or range unit). + * + * @param instanceSize The total size of the instance. + */ + public void setInstanceSize(long instanceSize) { + this.instanceSize = instanceSize; + } + + /** + * Sets the size of the range in number of bytes. If the size is the maximum available from the + * index, then use the {@value #SIZE_MAX} constant. + * + * @param size The size of the range in number of bytes. + */ + public void setSize(long size) { + this.size = size; + } + + /** + * Sets the name of the range unit. + * + * @param unitName The name of the range unit. + */ + public void setUnitName(String unitName) { + this.unitName = unitName; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/RecipientInfo.java b/org.restlet/src/main/java/org/restlet/data/RecipientInfo.java index 942c5d3ef3..011941c3f3 100644 --- a/org.restlet/src/main/java/org/restlet/data/RecipientInfo.java +++ b/org.restlet/src/main/java/org/restlet/data/RecipientInfo.java @@ -1,105 +1,99 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; /** - * Describes an intermediary via which the call went through. The intermediary - * is typically a proxy or a cache.
+ * Describes an intermediary via which the call went through. The intermediary is typically a proxy + * or a cache.
*
- * Note that when used with HTTP connectors, this class maps to the "Via" - * header. - * + * Note that when used with HTTP connectors, this class maps to the "Via" header. + * * @author Jerome Louvel */ public class RecipientInfo { - /** The protocol used to communicate with the recipient. */ - private volatile Protocol protocol; - - /** The optional comment, typically the software agent name. */ - private volatile String comment; + /** The protocol used to communicate with the recipient. */ + private volatile Protocol protocol; - /** The host name and port number or a pseudonym. */ - private volatile String name; + /** The optional comment, typically the software agent name. */ + private volatile String comment; - /** - * Default constructor. - */ - public RecipientInfo() { - } + /** The host name and port number or a pseudonym. */ + private volatile String name; - /** - * Constructor. - * - * @param protocol The protocol used to communicate with the recipient. - * @param name The host name and port number or a pseudonym. - * @param agent The software agent. - */ - public RecipientInfo(Protocol protocol, String name, String agent) { - this.protocol = protocol; - this.name = name; - this.comment = agent; - } + /** Default constructor. */ + public RecipientInfo() {} - /** - * Returns the optional comment, typically the software agent name. - * - * @return The optional comment. - */ - public String getComment() { - return comment; - } + /** + * Constructor. + * + * @param protocol The protocol used to communicate with the recipient. + * @param name The host name and port number or a pseudonym. + * @param agent The software agent. + */ + public RecipientInfo(Protocol protocol, String name, String agent) { + this.protocol = protocol; + this.name = name; + this.comment = agent; + } - /** - * Returns the host name and port number or a pseudonym. - * - * @return The host name and port number or a pseudonym. - */ - public String getName() { - return name; - } + /** + * Returns the optional comment, typically the software agent name. + * + * @return The optional comment. + */ + public String getComment() { + return comment; + } - /** - * Returns the protocol used to communicate with the recipient. - * - * @return The protocol used to communicate with the recipient. - */ - public Protocol getProtocol() { - return protocol; - } + /** + * Returns the host name and port number or a pseudonym. + * + * @return The host name and port number or a pseudonym. + */ + public String getName() { + return name; + } - /** - * Sets the optional comment, typically the software agent name. - * - * @param comment The optional comment. - */ - public void setComment(String comment) { - this.comment = comment; - } + /** + * Returns the protocol used to communicate with the recipient. + * + * @return The protocol used to communicate with the recipient. + */ + public Protocol getProtocol() { + return protocol; + } - /** - * Sets the host name and port number or a pseudonym. - * - * @param name The host name and port number or a pseudonym. - */ - public void setName(String name) { - this.name = name; - } + /** + * Sets the optional comment, typically the software agent name. + * + * @param comment The optional comment. + */ + public void setComment(String comment) { + this.comment = comment; + } - /** - * Sets the protocol used to communicate with the recipient. - * - * @param protocol The protocol used to communicate with the recipient. - */ - public void setProtocol(Protocol protocol) { - this.protocol = protocol; - } + /** + * Sets the host name and port number or a pseudonym. + * + * @param name The host name and port number or a pseudonym. + */ + public void setName(String name) { + this.name = name; + } + /** + * Sets the protocol used to communicate with the recipient. + * + * @param protocol The protocol used to communicate with the recipient. + */ + public void setProtocol(Protocol protocol) { + this.protocol = protocol; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Reference.java b/org.restlet/src/main/java/org/restlet/data/Reference.java index d245508721..6b96c4ee46 100644 --- a/org.restlet/src/main/java/org/restlet/data/Reference.java +++ b/org.restlet/src/main/java/org/restlet/data/Reference.java @@ -1,2815 +1,2820 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.restlet.Context; - import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.logging.Level; +import org.restlet.Context; /** - * Reference to a Uniform Resource Identifier (URI). Contrary to the - * java.net.URI class, this interface represents mutable references. It strictly - * conforms to the RFC 3986 specifying URIs and follow its naming - * conventions.
- * + * Reference to a Uniform Resource Identifier (URI). Contrary to the java.net.URI class, this + * interface represents mutable references. It strictly conforms to the RFC 3986 specifying URIs and + * follow its naming conventions.
+ * *

  * URI reference        = absolute-reference | relative-reference
- * 
+ *
  * absolute-reference   = scheme ":" scheme-specific-part [ "#" fragment ]
  * scheme-specific-part = ( hierarchical-part [ "?" query ] ) | opaque-part
  * hierarchical-part    = ( "//" authority path-abempty ) | path-absolute | path-rootless | path-empty
  * authority            = [ user-info "@" ] host-domain [ ":" host-port ]
- * 
+ *
  * relative-reference   = relative-part [ "?" query ] [ "#" fragment ]
  * relative-part        = ( "//" authority path-abempty ) | path-absolute | path-noscheme | path-empty
- * 
+ *
  * path-abempty         = begins with "/" or is empty
  * path-absolute        = begins with "/" but not "//"
  * path-noscheme        = begins with a non-colon segment
  * path-rootless        = begins with a segment
  * path-empty           = zero characters
  * 
- * - *

- * Note that this class doesn't encode or decode the reserved characters. It - * assumes that the URIs or the URI parts passed in are properly encoded using - * the standard URI encoding mechanism. You can use the static "encode()" and - * "decode()" methods for this purpose. Note that if an invalid URI character is - * detected by the constructor or one of the setters, a trace will be logged and - * the character will be automatically encoded. - *

- *

- * The fundamental point to underline is the difference between an URI - * "reference" and an URI. Contrary to an URI (the target identifier of a REST - * resource), an URI reference can be relative (with or without query and - * fragment part). This relative URI reference can then be resolved against a - * base reference via the getTargetRef() method which will return a new resolved - * Reference instance, an absolute URI reference with no base reference and with - * no dot-segments (the path segments "." and ".."). - *

- *

- * You can also apply the getTargetRef() method on absolute references in order - * to solve the dot-segments. Note that applying the getRelativeRef() method on - * an absolute reference returns the current reference relatively to a base - * reference, if any, and solves the dot-segments. - *

- *

- * The Reference stores its data as a single string, the one passed to the - * constructor. This string can always be obtained using the toString() method. - * A couple of integer indexes are maintained to improve the extraction time of - * various reference properties (URI components). - *

- *

- * When you modify a specific component of the URI reference, via the setPath() - * method for example, the internal string is simply regenerated by updating - * only the relevant part. We try as much as possible to protect the bytes given - * to the Reference class instead of transparently parsing and normalizing the - * URI data. Our idea is to protect encodings and special characters in all case - * and reduce the memory size taken by this class while making Reference - * instances mutable. - *

- *

- * Because the base reference is only a property of the Reference ("baseRef"). - * When you use the "Reference(base, path)" constructor, it is equivalent to - * doing:
+ * + *

Note that this class doesn't encode or decode the reserved characters. It assumes that the + * URIs or the URI parts passed in are properly encoded using the standard URI encoding mechanism. + * You can use the static "encode()" and "decode()" methods for this purpose. Note that if an + * invalid URI character is detected by the constructor or one of the setters, a trace will be + * logged and the character will be automatically encoded. + * + *

The fundamental point to underline is the difference between a URI "reference" and a URI. + * Contrary to a URI (the target identifier of a REST resource), a URI reference can be relative + * (with or without query and fragment part). This relative URI reference can then be resolved + * against a base reference via the getTargetRef() method which will return a new resolved Reference + * instance, an absolute URI reference with no base reference and with no dot-segments (the path + * segments "." and ".."). + * + *

You can also apply the getTargetRef() method on absolute references to solve the dot-segments. + * Note that applying the getRelativeRef() method on an absolute reference returns the current + * reference relatively to a base reference, if any, and solves the dot-segments. + * + *

The Reference stores its data as a single string, the one passed to the constructor. This + * string can always be obtained using the toString() method. A couple of integer indexes are + * maintained to improve the extraction time of various reference properties (URI components). + * + *

When you modify a specific component of the URI reference, via the setPath() method, for + * example, the internal string is simply regenerated by updating only the relevant part. We try as + * much as possible to protect the bytes given to the Reference class instead of transparently + * parsing and normalizing the URI data. Our idea is to protect encodings and special characters in + * all cases and reduce the memory size taken by this class while making Reference instances + * mutable. + * + *

Because the base reference is only a property of the Reference ("baseRef"). When you use the + * "Reference(base, path)" constructor, it is equivalent to doing:
* ref = new Reference(path);
* ref.setBaseRef(base); - *

- *

- * The base ref is not automatically resolved or "merged" with the rest of the - * reference information (the path here). For example, this let's you reuse a - * single reference as the base of several relative references. If you modify - * the base reference, all relative references are still accurate. - *

- * Note that the name and value properties are thread safe, stored in volatile - * members. - * + * + *

The base ref is not automatically resolved or "merged" with the rest of the reference + * information (the path here). For example, this lets you reuse a single reference as the base of + * several relative references. If you modify the base reference, all relative references are still + * accurate. Note that the name and value properties are thread-safe, stored in volatile members. + * * @author Jerome Louvel * @see RFC 3986 */ public class Reference { - /** Helps to map characters and their validity as URI characters. */ - private static final boolean[] charValidityMap = new boolean[127]; - - static { - // Initialize the map of valid characters. - for (int character = 0; character < 127; character++) { - charValidityMap[character] = isReserved(character) || isUnreserved(character) || (character == '%') - || (character == '{') || (character == '}'); - } - } - - /** - * Decodes a given string using the standard URI encoding mechanism and the - * UTF-8 character set. - * - * @param toDecode The string to decode. - * @return The decoded string. - */ - public static String decode(String toDecode) { - return decode(toDecode, CharacterSet.UTF_8); - } - - /** - * Decodes a given string using the standard URI encoding mechanism. If the - * provided character set is null, the string is returned but not decoded. - * Note: The - * - * World Wide Web Consortium Recommendation states that UTF-8 should be - * used. Not doing so may introduce incompatibilities. - * - * @param toDecode The string to decode. - * @param characterSet The name of a supported character encoding. - * @return The decoded string or null if the named character encoding is not - * supported. - */ - public static String decode(String toDecode, CharacterSet characterSet) { - String result = null; - try { - result = (characterSet == null) ? toDecode : java.net.URLDecoder.decode(toDecode, characterSet.getName()); - } catch (UnsupportedEncodingException uee) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to decode the string with the UTF-8 character set.", - uee); - } - - return result; - } - - /** - * Encodes a given string using the standard URI encoding mechanism and the - * UTF-8 character set. - * - * @param toEncode The string to encode. - * @return The encoded string. - */ - public static String encode(String toEncode) { - return encode(toEncode, true, CharacterSet.UTF_8); - } - - /** - * Encodes a given string using the standard URI encoding mechanism and the - * UTF-8 character set. Useful to prevent the usage of '+' to encode spaces (%20 - * instead). The '*' characters are encoded as %2A and %7E are replaced by '~'. - * - * @param toEncode The string to encode. - * @param queryString True if the string to encode is part of a query string - * instead of a HTML form post. - * @return The encoded string. - */ - public static String encode(String toEncode, boolean queryString) { - return encode(toEncode, queryString, CharacterSet.UTF_8); - } - - /** - * Encodes a given string using the standard URI encoding mechanism and the - * UTF-8 character set. Useful to prevent the usage of '+' to encode spaces (%20 - * instead). The '*' characters are encoded as %2A and %7E are replaced by '~'. - * - * @param toEncode The string to encode. - * @param queryString True if the string to encode is part of a query string - * instead of a HTML form post. - * @param characterSet The supported character encoding. - * @return The encoded string. - */ - public static String encode(String toEncode, boolean queryString, CharacterSet characterSet) { - - String result = null; - - try { - result = (characterSet == null) ? toEncode : java.net.URLEncoder.encode(toEncode, characterSet.getName()); - } catch (UnsupportedEncodingException uee) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to encode the string with the UTF-8 character set.", - uee); - } - - if (result != null && queryString) { - result = result.replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); - } - - return result; - } - - /** - * Encodes a given string using the standard URI encoding mechanism. If the - * provided character set is null, the string is returned but not encoded. - * - * Note: The - * - * World Wide Web Consortium Recommendation states that UTF-8 should be - * used. Not doing so may introduce incompatibilities. - * - * @param toEncode The string to encode. - * @param characterSet The supported character encoding. - * @return The encoded string or null if the named character encoding is not - * supported. - */ - public static String encode(String toEncode, CharacterSet characterSet) { - return encode(toEncode, true, characterSet); - } - - /** - * Indicates if the given character is alphabetical (a-z or A-Z). - * - * @param character The character to test. - * @return True if the given character is alphabetical (a-z or A-Z). - */ - private static boolean isAlpha(int character) { - return isUpperCase(character) || isLowerCase(character); - } - - /** - * Indicates if the given character is a digit (0-9). - * - * @param character The character to test. - * @return True if the given character is a digit (0-9). - */ - private static boolean isDigit(int character) { - return (character >= '0') && (character <= '9'); - } - - /** - * Indicates if the given character is a generic URI component delimiter - * character. - * - * @param character The character to test. - * @return True if the given character is a generic URI component delimiter - * character. - */ - public static boolean isGenericDelimiter(int character) { - return (character == ':') || (character == '/') || (character == '?') || (character == '#') - || (character == '[') || (character == ']') || (character == '@'); - } - - /** - * Indicates if the given character is lower case (a-z). - * - * @param character The character to test. - * @return True if the given character is lower case (a-z). - */ - private static boolean isLowerCase(int character) { - return (character >= 'a') && (character <= 'z'); - } - - /** - * Indicates if the given character is a reserved URI character. - * - * @param character The character to test. - * @return True if the given character is a reserved URI character. - */ - public static boolean isReserved(int character) { - return isGenericDelimiter(character) || isSubDelimiter(character); - } - - /** - * Indicates if the given character is an URI subcomponent delimiter character. - * - * @param character The character to test. - * @return True if the given character is an URI subcomponent delimiter - * character. - */ - public static boolean isSubDelimiter(int character) { - return (character == '!') || (character == '$') || (character == '&') || (character == '\'') - || (character == '(') || (character == ')') || (character == '*') || (character == '+') - || (character == ',') || (character == ';') || (character == '='); - } - - /** - * Indicates if the given character is an unreserved URI character. - * - * @param character The character to test. - * @return True if the given character is an unreserved URI character. - */ - public static boolean isUnreserved(int character) { - return isAlpha(character) || isDigit(character) || (character == '-') || (character == '.') - || (character == '_') || (character == '~'); - } - - /** - * Indicates if the given character is upper case (A-Z). - * - * @param character The character to test. - * @return True if the given character is upper case (A-Z). - */ - private static boolean isUpperCase(int character) { - return (character >= 'A') && (character <= 'Z'); - } - - /** - * Indicates if the given character is a valid URI character. - * - * @param character The character to test. - * @return True if the given character is a valid URI character. - */ - public static boolean isValid(int character) { - return character >= 0 && character < 127 && charValidityMap[character]; - } - - /** - * Creates a reference string from its parts. - * - * @param scheme The scheme ("http", "https" or "ftp"). - * @param hostName The host name or IP address. - * @param hostPort The host port (default ports are correctly ignored). - * @param path The path component for hierarchical identifiers. - * @param query The optional query component for hierarchical identifiers. - * @param fragment The optional fragment identifier. - * @return The reference as String. - */ - public static String toString(String scheme, String hostName, Integer hostPort, String path, String query, - String fragment) { - String host = hostName; - - // Appends the host port number - if (hostPort != null) { - final int defaultPort = Protocol.valueOf(scheme).getDefaultPort(); - if (hostPort != defaultPort) { - host = hostName + ':' + hostPort; - } - } - - return toString(scheme, host, path, query, fragment); - } - - /** - * Creates a relative reference string from its parts. - * - * @param relativePart The relative part component. - * @param query The optional query component for hierarchical - * identifiers. - * @param fragment The optional fragment identifier. - * @return The relative reference as a String. - */ - public static String toString(String relativePart, String query, String fragment) { - final StringBuilder sb = new StringBuilder(); - - // Append the path - if (relativePart != null) { - sb.append(relativePart); - } - - // Append the query string - if (query != null) { - sb.append('?').append(query); - } - - // Append the fragment identifier - if (fragment != null) { - sb.append('#').append(fragment); - } - - // Actually construct the reference - return sb.toString(); - } - - /** - * Creates a reference string from its parts. - * - * @param scheme The scheme ("http", "https" or "ftp"). - * @param host The host name or IP address plus the optional port number. - * @param path The path component for hierarchical identifiers. - * @param query The optional query component for hierarchical identifiers. - * @param fragment The optional fragment identifier. - * @return The reference a String. - */ - public static String toString(String scheme, String host, String path, String query, String fragment) { - final StringBuilder sb = new StringBuilder(); - - if (scheme != null) { - // Append the scheme and host name - sb.append(scheme.toLowerCase()).append("://").append(host); - } - - // Append the path - if (path != null) { - sb.append(path); - } - - // Append the query string - if (query != null) { - sb.append('?').append(query); - } - - // Append the fragment identifier - if (fragment != null) { - sb.append('#').append(fragment); - } - - // Actually construct the reference - return sb.toString(); - } - - /** The base reference for relative references. */ - private volatile Reference baseRef; - - /** The fragment separator index. */ - private volatile int fragmentIndex; - - /** The internal reference. */ - private volatile String internalRef; - - /** The query separator index. */ - private volatile int queryIndex; - - /** The scheme separator index. */ - private volatile int schemeIndex; - - /** - * Empty constructor. - */ - public Reference() { - this((Reference) null, (String) null); - } - - /** - * Constructor from an {@link java.net.URI} instance. - * - * @param uri The {@link java.net.URI} instance. - */ - public Reference(java.net.URI uri) { - this(uri.toString()); - } - - /** - * Constructor from an {@link java.net.URI} instance. - * - * @param baseUri The base {@link java.net.URI} instance. - * @param uri The {@link java.net.URI} instance. - */ - public Reference(java.net.URI baseUri, java.net.URI uri) { - this(baseUri.toString(), uri.toString()); - } - - /** - * Constructor from an {@link java.net.URL} instance. - * - * @param url The {@link java.net.URL} instance. - */ - public Reference(java.net.URL url) { - this(url.toString()); - } - - /** - * Constructor for a protocol and host name. Uses the default port for the given - * protocol. - * - * @param protocol Protocol/scheme to use - * @param hostName The host name or IP address. - */ - public Reference(Protocol protocol, String hostName) { - this(protocol, hostName, protocol.getDefaultPort()); - } - - /** - * Constructor for a protocol, host name and host port - * - * @param protocol Protocol/scheme to use - * @param hostName The host name or IP address. - * @param hostPort The host port (default ports are correctly ignored). - */ - public Reference(Protocol protocol, String hostName, int hostPort) { - this(protocol.getSchemeName(), hostName, hostPort, null, null, null); - } - - /** - * Clone constructor. - * - * @param ref The reference to clone. - */ - public Reference(Reference ref) { - this(ref.baseRef, ref.internalRef); - } - - /** - * Constructor from an URI reference (most likely relative). - * - * @param baseRef The base reference. - * @param uriReference The URI reference, either absolute or relative. - */ - public Reference(Reference baseRef, Reference uriReference) { - this(baseRef, uriReference.toString()); - } - - /** - * Constructor from a URI reference (most likely relative). - * - * @param baseRef The base reference. - * @param uriRef The URI reference, either absolute or relative. - */ - public Reference(Reference baseRef, String uriRef) { - uriRef = encodeInvalidCharacters(uriRef); - this.baseRef = baseRef; - this.internalRef = uriRef; - updateIndexes(); - } - - /** - * Constructor of relative reference from its parts. - * - * @param baseRef The base reference. - * @param relativePart The relative part component (most of the time it is the - * path component). - * @param query The optional query component for hierarchical - * identifiers. - * @param fragment The optional fragment identifier. - */ - public Reference(Reference baseRef, String relativePart, String query, String fragment) { - this(baseRef, toString(relativePart, query, fragment)); - } - - /** - * Constructor from a URI reference. - * - * @param uriReference The URI reference, either absolute or relative. - */ - public Reference(String uriReference) { - this((Reference) null, uriReference); - } - - /** - * Constructor from an identifier and a fragment. - * - * @param identifier The resource identifier. - * @param fragment The fragment identifier. - */ - public Reference(String identifier, String fragment) { - this((fragment == null) ? identifier : identifier + '#' + fragment); - } - - /** - * Constructor of absolute reference from its parts. - * - * @param scheme The scheme ("http", "https" or "ftp"). - * @param hostName The host name or IP address. - * @param hostPort The host port (default ports are correctly ignored). - * @param path The path component for hierarchical identifiers. - * @param query The optional query component for hierarchical identifiers. - * @param fragment The optional fragment identifier. - */ - public Reference(String scheme, String hostName, int hostPort, String path, String query, String fragment) { - this(toString(scheme, hostName, hostPort, path, query, fragment)); - } - - /** - * Adds a parameter to the query component. The name and value are automatically - * URL encoded if necessary. - * - * @param parameter The parameter to add. - * @return The updated reference. - */ - public Reference addQueryParameter(Parameter parameter) { - return addQueryParameter(parameter.getName(), parameter.getValue()); - } - - /** - * Adds a parameter to the query component. The name and value are automatically - * URL encoded if necessary. - * - * @param name The parameter name. - * @param value The optional parameter value. - * @return The updated reference. - */ - public Reference addQueryParameter(String name, String value) { - String query = getQuery(); - - if (query == null) { - if (value == null) { - setQuery(encode(name)); - } else { - setQuery(encode(name) + '=' + encode(value)); - } - } else { - if (value == null) { - setQuery(query + '&' + encode(name)); - } else { - setQuery(query + '&' + encode(name) + '=' + encode(value)); - } - } - - return this; - } - - /** - * Adds several parameters to the query component. The name and value are - * automatically URL encoded if necessary. - * - * @param parameters The parameters to add. - * @return The updated reference. - */ - public Reference addQueryParameters(Iterable parameters) { - for (Parameter param : parameters) { - addQueryParameter(param); - } - - return this; - } - - /** - * Adds a segment at the end of the path. If the current path doesn't end with a - * slash character, one is inserted before the new segment value. The value is - * automatically encoded if necessary. - * - * @param value The segment value to add. - * @return The updated reference. - */ - public Reference addSegment(String value) { - final String path = getPath(); - - if (value != null) { - if (path == null) { - setPath("/" + value); - } else if (path.endsWith("/")) { - setPath(path + encode(value)); - } else { - setPath(path + "/" + encode(value)); - } - } - - return this; - } - - public Reference copy() { - final Reference newRef = new Reference(); - - if (this.baseRef == null) { - newRef.baseRef = null; - } else if (equals(this.baseRef)) { - newRef.baseRef = newRef; - } else { - newRef.baseRef = this.baseRef.copy(); - } - - newRef.fragmentIndex = this.fragmentIndex; - newRef.internalRef = this.internalRef; - newRef.queryIndex = this.queryIndex; - newRef.schemeIndex = this.schemeIndex; - return newRef; - } - - /** - * Checks if all characters are valid and encodes invalid characters if - * necessary. - * - * @param uriRef The URI reference to check. - * @return The original reference, eventually with invalid URI characters - * encoded. - */ - private String encodeInvalidCharacters(String uriRef) throws IllegalArgumentException { - String result = uriRef; - - if (uriRef != null) { - boolean valid = true; - - // Ensure that all characters are valid, otherwise encode them - for (int i = 0; valid && (i < uriRef.length()); i++) { - if (!isValid(uriRef.charAt(i))) { - valid = false; - Context.getCurrentLogger().fine("Invalid character detected in URI reference at index '" + i - + "': \"" + uriRef.charAt(i) + "\". It will be automatically encoded."); - } else if ((uriRef.charAt(i) == '%') && (i > uriRef.length() - 2)) { - // A percent encoding character has been detected but - // without the necessary two hexadecimal digits following - valid = false; - Context.getCurrentLogger().fine("Invalid percent encoding detected in URI reference at index '" + i - + "': \"" + uriRef.charAt(i) + "\". It will be automatically encoded."); - } - } - - if (!valid) { - StringBuilder sb = new StringBuilder(); - - for (int i = 0; (i < uriRef.length()); i++) { - if (isValid(uriRef.charAt(i))) { - if ((uriRef.charAt(i) == '%') && (i > uriRef.length() - 2)) { - sb.append("%25"); - } else { - sb.append(uriRef.charAt(i)); - } - } else { - sb.append(encode(String.valueOf(uriRef.charAt(i)))); - } - } - - result = sb.toString(); - } - } - - return result; - } - - /** - * Indicates whether some other object is "equal to" this one. - * - * @param object The object to compare to. - * @return True if this object is the same as the obj argument. - */ - @Override - public boolean equals(Object object) { - if (object instanceof Reference) { - final Reference ref = (Reference) object; - if (this.internalRef == null) { - return ref.internalRef == null; - } - return this.internalRef.equals(ref.internalRef); - - } - - return false; - } - - /** - * Returns the authority component for hierarchical identifiers. Includes the - * user info, host name and the host port number.
- * Note that no URI decoding is done by this method. - * - * @return The authority component for hierarchical identifiers. - */ - public String getAuthority() { - final String part = isRelative() ? getRelativePart() : getSchemeSpecificPart(); - - if ((part != null) && part.startsWith("//")) { - int index = part.indexOf('/', 2); - - if (index != -1) { - return part.substring(2, index); - } - - index = part.indexOf('?'); - if (index != -1) { - return part.substring(2, index); - } - - return part.substring(2); - - } - - return null; - } - - /** - * Returns the optionally decoded authority component. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded authority component. - * @see #getAuthority() - */ - public String getAuthority(boolean decode) { - return decode ? decode(getAuthority()) : getAuthority(); - } - - /** - * Returns the base reference for relative references. - * - * @return The base reference for relative references. - */ - public Reference getBaseRef() { - return this.baseRef; - } - - /** - * Returns the optional extensions for hierarchical identifiers. An extensions - * part starts after the first '.' character of the last path segment and ends - * with either the end of the segment of with the first ';' character (matrix - * start). It is a token similar to file extensions separated by '.' characters. - * The value can be ommited.
- * Note that no URI decoding is done by this method. - * - * @return The extensions or null. - * @see #getExtensionsAsArray() - * @see #setExtensions(String) - */ - public String getExtensions() { - String result = null; - final String lastSegment = getLastSegment(); - - if (lastSegment != null) { - final int extensionIndex = lastSegment.indexOf('.'); - final int matrixIndex = lastSegment.indexOf(';'); - - if (extensionIndex != -1) { - // Extensions found - if (matrixIndex != -1) { - result = lastSegment.substring(extensionIndex + 1, matrixIndex); - } else { - // No matrix found - result = lastSegment.substring(extensionIndex + 1); - } - } - } - - return result; - } - - /** - * Returns the extensions as an array or null if no extension is found. - * - * @return The extensions as an array or null if no extension is found. - * @see #getExtensions() - */ - public String[] getExtensionsAsArray() { - String[] result = null; - final String extensions = getExtensions(); - - if (extensions != null) { - result = extensions.split("\\."); - } - - return result; - } - - /** - * Returns the fragment identifier.
- * Note that no URI decoding is done by this method. - * - * @return The fragment identifier. - */ - public String getFragment() { - if (hasFragment()) { - return this.internalRef.substring(this.fragmentIndex + 1); - } - - return null; - } - - /** - * Returns the optionally decoded fragment identifier. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded fragment identifier. - * @see #getFragment() - */ - public String getFragment(boolean decode) { - return decode ? decode(getFragment()) : getFragment(); - } - - /** - * Returns the hierarchical part which is equivalent to the scheme specific part - * less the query component.
- * Note that no URI decoding is done by this method. - * - * @return The hierarchical part . - */ - public String getHierarchicalPart() { - if (hasScheme()) { - // Scheme found - if (hasQuery()) { - // Query found - return this.internalRef.substring(this.schemeIndex + 1, this.queryIndex); - } - - // No query found - if (hasFragment()) { - // Fragment found - return this.internalRef.substring(this.schemeIndex + 1, this.fragmentIndex); - } - - // No fragment found - return this.internalRef.substring(this.schemeIndex + 1); - } - - // No scheme found - if (hasQuery()) { - // Query found - return this.internalRef.substring(0, this.queryIndex); - } - if (hasFragment()) { - // Fragment found - return this.internalRef.substring(0, this.fragmentIndex); - } - - // No fragment found - return this.internalRef; - } - - /** - * Returns the optionally decoded hierarchical part. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded hierarchical part. - * @see #getHierarchicalPart() - */ - public String getHierarchicalPart(boolean decode) { - return decode ? decode(getHierarchicalPart()) : getHierarchicalPart(); - } - - /** - * Returns the host domain name component for server based hierarchical - * identifiers. It can also be replaced by an IP address when no domain name was - * registered.
- * Note that no URI decoding is done by this method. - * - * @return The host domain name component for server based hierarchical - * identifiers. - */ - public String getHostDomain() { - String result = null; - final String authority = getAuthority(); - - if (authority != null) { - // We must prevent the case where the userinfo part contains ':' - // and the case of IPV6 addresses - int indexUI = authority.indexOf('@'); // user info - int indexIPV6 = authority.indexOf(']'); // IPV6 - int indexP = authority.indexOf(':', (indexIPV6 == -1) ? indexUI : indexIPV6); - - if (indexUI != -1) { - // User info found - if (indexP != -1) { - // Port found - result = authority.substring(indexUI + 1, indexP); - } else { - // No port found - result = authority.substring(indexUI + 1); - } - } else { - // No user info found - if (indexP != -1) { - // Port found - result = authority.substring(0, indexP); - } else { - // No port found - result = authority; - } - } - } - - return result; - } - - /** - * Returns the optionally decoded host domain name component. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded host domain name component. - * @see #getHostDomain() - */ - public String getHostDomain(boolean decode) { - return decode ? decode(getHostDomain()) : getHostDomain(); - } - - /** - * Returns the host identifier. Includes the scheme, the host name and the host - * port number.
- * Note that no URI decoding is done by this method. - * - * @return The host identifier. - */ - public String getHostIdentifier() { - final StringBuilder result = new StringBuilder(); - result.append(getScheme()).append("://").append(getAuthority()); - return result.toString(); - } - - /** - * Returns the optionally decoded host identifier. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded host identifier. - * @see #getHostIdentifier() - */ - public String getHostIdentifier(boolean decode) { - return decode ? decode(getHostIdentifier()) : getHostIdentifier(); - } - - /** - * Returns the optional port number for server based hierarchical identifiers. - * - * @return The optional port number for server based hierarchical identifiers or - * -1 if the port number does not exist. - */ - public int getHostPort() { - int result = -1; - final String authority = getAuthority(); - - if (authority != null) { - // We must prevent the case where the userinfo part contains ':' - // and the case of IPV6 addresses - int indexUI = authority.indexOf('@'); // user info - int indexIPV6 = authority.indexOf(']'); // IPV6 - int index = authority.indexOf(':', (indexIPV6 == -1) ? indexUI : indexIPV6); - - if (index != -1) { - try { - result = Integer.parseInt(authority.substring(index + 1)); - } catch (NumberFormatException nfe) { - Context.getCurrentLogger().log(Level.WARNING, "Can't parse hostPort : [hostRef,requestUri]=[" - + getBaseRef() + "," + this.internalRef + "]"); - } - } - } - - return result; - } - - /** - * Returns the absolute resource identifier, without the fragment.
- * Note that no URI decoding is done by this method. - * - * @return The absolute resource identifier, without the fragment. - */ - public String getIdentifier() { - if (hasFragment()) { - // Fragment found - return this.internalRef.substring(0, this.fragmentIndex); - } - - // No fragment found - return this.internalRef; - } - - /** - * Returns the optionally decoded absolute resource identifier. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded absolute resource identifier. - * @see #getIdentifier() - */ - public String getIdentifier(boolean decode) { - return decode ? decode(getIdentifier()) : getIdentifier(); - } - - /** - * Returns the last segment of a hierarchical path.
- * For example the "/a/b/c" and "/a/b/c/" paths have the same segments: "a", - * "b", "c.
- * Note that no URI decoding is done by this method. - * - * @return The last segment of a hierarchical path. - */ - public String getLastSegment() { - String result = null; - String path = getPath(); - - if (path != null) { - if (path.endsWith("/")) { - path = path.substring(0, path.length() - 1); - } - - final int lastSlash = path.lastIndexOf('/'); - - if (lastSlash != -1) { - result = path.substring(lastSlash + 1); - } - } - - return result; - } - - /** - * Returns the optionally decoded last segment. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded last segment. - * @see #getLastSegment() - */ - public String getLastSegment(boolean decode) { - return getLastSegment(decode, false); - } - - /** - * Returns the optionally decoded last segment. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @param excludeMatrix True if the matrix parameters are dropped from the - * segments. - * @return The optionally decoded last segment. - * @see #getLastSegment() - */ - public String getLastSegment(boolean decode, boolean excludeMatrix) { - String result = getLastSegment(); - - if (excludeMatrix && (result != null)) { - final int matrixIndex = result.indexOf(';'); - - if (matrixIndex != -1) { - result = result.substring(0, matrixIndex); - } - } - - return decode ? decode(result) : result; - } - - /** - * Returns the optional matrix for hierarchical identifiers. A matrix part - * starts after the first ';' character of the last path segment. It is a - * sequence of 'name=value' parameters separated by ';' characters. The value - * can be ommitted.
- * Note that no URI decoding is done by this method. - * - * @return The matrix or null. - */ - public String getMatrix() { - String lastSegment = getLastSegment(); - - if (lastSegment != null) { - final int matrixIndex = lastSegment.indexOf(';'); - - if (matrixIndex != -1) { - return lastSegment.substring(matrixIndex + 1); - } - } - - // No matrix found - return null; - } - - /** - * Returns the optionally decoded matrix. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded matrix. - * @see #getMatrix() - */ - public String getMatrix(boolean decode) { - return decode ? decode(getMatrix()) : getMatrix(); - } - - /** - * Returns the optional matrix as a form. - * - * @return The optional matrix component as a form. - */ - public Form getMatrixAsForm() { - return new Form(getMatrix(), ';'); - } - - /** - * Returns the optional matrix as a form submission. - * - * @param characterSet The supported character encoding. - * @return The optional matrix as a form. - */ - public Form getMatrixAsForm(CharacterSet characterSet) { - return new Form(getMatrix(), characterSet, ';'); - } - - /** - * Returns the parent reference of a hierarchical reference. The last slash of - * the path will be considered as the end of the parent path. - * - * @return The parent reference of a hierarchical reference. - */ - public Reference getParentRef() { - Reference result = null; - - if (isHierarchical()) { - String parentRef = null; - String path = getPath(); - - if (!path.equals("/") && !path.isEmpty()) { - if (path.endsWith("/")) { - path = path.substring(0, path.length() - 1); - } - - if (isAbsolute()) { - parentRef = getHostIdentifier() + path.substring(0, path.lastIndexOf('/') + 1); - } else { - parentRef = path.substring(0, path.lastIndexOf('/') + 1); - } - } else { - parentRef = this.internalRef; - } - - result = new Reference(parentRef); - } - - return result; - } - - /** - * Returns the path component for hierarchical identifiers. If not path is - * available it returns null.
- * Note that no URI decoding is done by this method. - * - * @return The path component for hierarchical identifiers. - */ - public String getPath() { - String result = null; - String part = isRelative() ? getRelativePart() : getSchemeSpecificPart(); - - if (part != null) { - if (part.startsWith("//")) { - // Authority found - int index1 = part.indexOf('/', 2); - - if (index1 != -1) { - // Path found - int index2 = part.indexOf('?'); - - if (index2 != -1) { - // Query found - result = part.substring(Math.min(index1, index2), index2); - } else { - // No query found - result = part.substring(index1); - } - } else { - // Path must be empty in this case - } - } else { - // No authority found - int index = part.indexOf('?'); - - if (index != -1) { - // Query found - result = part.substring(0, index); - } else { - // No query found - result = part; - } - } - } - - return result; - } - - /** - * Returns the optionally decoded path component. If not path is available it - * returns null. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded path component. - * @see #getPath() - */ - public String getPath(boolean decode) { - return decode ? decode(getPath()) : getPath(); - } - - /** - * Returns the optional query component for hierarchical identifiers.
- * Note that no URI decoding is done by this method. - * - * @return The query component or null. - */ - public String getQuery() { - if (hasQuery()) { - // Query found - if (hasFragment()) { - if (this.queryIndex < this.fragmentIndex) { - // Fragment found and query sign not inside fragment - return this.internalRef.substring(this.queryIndex + 1, this.fragmentIndex); - } - - return null; - } - - // No fragment found - return this.internalRef.substring(this.queryIndex + 1); - } - - // No query found - return null; - } - - /** - * Returns the optionally decoded query component. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded query component. - * @see #getQuery() - */ - public String getQuery(boolean decode) { - return decode ? decode(getQuery()) : getQuery(); - } - - /** - * Returns the optional query component as a form. - * - * @return The optional query component as a form. - */ - public Form getQueryAsForm() { - return new Form(getQuery()); - } - - /** - * Returns the optional query component as a form. - * - * @param decode Indicates if the names and values should be automatically - * decoded. - * @return The optional query component as a form. - */ - public Form getQueryAsForm(boolean decode) { - return new Form(getQuery(), decode); - } - - /** - * Returns the optional query component as a form submission. - * - * @param characterSet The supported character encoding. - * @return The optional query component as a form submission. - */ - public Form getQueryAsForm(CharacterSet characterSet) { - return new Form(getQuery(), characterSet); - } - - /** - * Returns the relative part of relative references, without the query and - * fragment. If the reference is absolute, then null is returned.
- * Note that no URI decoding is done by this method. - * - * @return The relative part. - */ - public String getRelativePart() { - return isRelative() ? toString(false, false) : null; - } - - /** - * Returns the optionally decoded relative part. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded relative part. - * @see #getRelativePart() - */ - public String getRelativePart(boolean decode) { - return decode ? decode(getRelativePart()) : getRelativePart(); - } - - /** - * Returns the current reference as a relative reference to the current base - * reference. This method should only be invoked for absolute references, - * otherwise an IllegalArgumentException will be raised. - * - * @return The current reference as a relative reference to the current base - * reference. - * @see #getRelativeRef(Reference) - */ - public Reference getRelativeRef() { - return getRelativeRef(getBaseRef()); - } - - /** - * Returns the current reference relatively to a base reference. This method - * should only be invoked for absolute references, otherwise an - * IllegalArgumentException will be raised. - * - * @param base The base reference to use. - * @throws IllegalArgumentException If the relative reference is computed - * although the reference or the base reference - * are not absolute or not hierarchical. - * @return The current reference relatively to a base reference. - */ - public Reference getRelativeRef(Reference base) { - Reference result = null; - - if (base == null) { - result = this; - } else if (!isAbsolute() || !isHierarchical()) { - throw new IllegalArgumentException("The reference must have an absolute hierarchical path component"); - } else if (!base.isAbsolute() || !base.isHierarchical()) { - throw new IllegalArgumentException("The base reference must have an absolute hierarchical path component"); - } else if (!getHostIdentifier().equals(base.getHostIdentifier())) { - result = this; - } else { - final String localPath = getPath(); - final String basePath = base.getPath(); - String relativePath = null; - - if ((basePath == null) || (localPath == null)) { - relativePath = localPath; - } else { - // Find the junction point - boolean diffFound = false; - int lastSlashIndex = -1; - int i = 0; - char current; - while (!diffFound && (i < localPath.length()) && (i < basePath.length())) { - current = localPath.charAt(i); - - if (current != basePath.charAt(i)) { - diffFound = true; - } else { - if (current == '/') { - lastSlashIndex = i; - } - i++; - } - } - - if (!diffFound) { - if (localPath.length() == basePath.length()) { - // Both paths are strictly equivalent - relativePath = "."; - } else if (i == localPath.length()) { - // End of local path reached - if (basePath.charAt(i) == '/') { - if ((i + 1) == basePath.length()) { - // Both paths are strictly equivalent - relativePath = "."; - } else { - // The local path is a direct parent of the base - // path - // We need to add enough ".." in the relative - // path - final StringBuilder sb = new StringBuilder(); - - // Count segments - int segments = 0; - for (int j = basePath.indexOf('/', i); j != -1; j = basePath.indexOf('/', j + 1)) - segments++; - - // Build relative path - for (int j = 0; j < segments; j++) - sb.append("../"); - - int lastLocalSlash = localPath.lastIndexOf('/'); - sb.append(localPath.substring(lastLocalSlash + 1)); - - relativePath = sb.toString(); - } - } else { - // The base path has a segment that starts like - // the last local path segment - // But that is longer. Situation similar to a - // junction - final StringBuilder sb = new StringBuilder(); - - // Count segments - int segments = 0; - for (int j = basePath.indexOf('/', i); j != -1; j = basePath.indexOf('/', j + 1)) - segments++; - - // Build relative path - for (int j = 0; j < segments; j++) - if (j > 0) - sb.append("/.."); - else - sb.append(".."); - - relativePath = sb.toString(); - - if (relativePath.isEmpty()) { - relativePath = "."; - } - } - } else if (i == basePath.length()) { - if (localPath.charAt(i) == '/') { - if ((i + 1) == localPath.length()) { - // Both paths are strictly equivalent - relativePath = "."; - } else { - // The local path is a direct child of the base - // path - relativePath = localPath.substring(i + 1); - } - } else { - if (lastSlashIndex == (i - 1)) { - // The local path is a direct subpath of the - // base path - relativePath = localPath.substring(i); - } else { - relativePath = ".." + localPath.substring(lastSlashIndex); - } - } - } - } else { - // We found a junction point, we need to add enough ".." in - // the relative path and append the rest of the local path - // the local path is a direct subpath of the base path - final StringBuilder sb = new StringBuilder(); - - // Count segments - int segments = 0; - for (int j = basePath.indexOf('/', i); j != -1; j = basePath.indexOf('/', j + 1)) - segments++; - - // Build relative path - for (int j = 0; j < segments; j++) - sb.append("../"); - - sb.append(localPath.substring(lastSlashIndex + 1)); - - relativePath = sb.toString(); - } - } - - // Build the result reference - result = new Reference(); - final String query = getQuery(); - final String fragment = getFragment(); - boolean modified = false; - - if ((query != null) && (!query.equals(base.getQuery()))) { - result.setQuery(query); - modified = true; - } - - if ((fragment != null) && (!fragment.equals(base.getFragment()))) { - result.setFragment(fragment); - modified = true; - } - - if (!modified || !".".equals(relativePath)) { - result.setPath(relativePath); - } - } - - return result; - } - - /** - * Returns the part of the resource identifier remaining after the base - * reference. Note that the optional fragment is not returned by this method. - * Must be used with the following prerequisites: - *

    - *
  • the reference is absolute
  • - *
  • the reference identifier starts with the resource baseRef
  • - *
- *
- * Note that no URI decoding is done by this method. - * - * @return The remaining resource part or null if the prerequisites are not - * satisfied. - * @see #getRemainingPart(boolean) - */ - public String getRemainingPart() { - return getRemainingPart(false, true); - } - - /** - * Returns the optionally decoded remaining part. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded remaining part. - * @see #getRemainingPart() - */ - public String getRemainingPart(boolean decode) { - return getRemainingPart(decode, true); - } - - /** - * Returns the optionally decoded remaining part with or without the query part - * of the reference. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @param query True if the query part should be returned, false otherwise. - * @return The optionally decoded remaining part. - * @see #getRemainingPart() - */ - public String getRemainingPart(boolean decode, boolean query) { - String result = null; - final String all = toString(query, false); - - if (getBaseRef() != null) { - final String base = getBaseRef().toString(query, false); - - if ((base != null) && all.startsWith(base)) { - result = all.substring(base.length()); - } - } else { - result = all; - } - - return decode ? decode(result) : result; - } - - /** - * Returns the scheme component.
- * Note that no URI decoding is done by this method. - * - * @return The scheme component. - */ - public String getScheme() { - if (hasScheme()) { - // Scheme found - return this.internalRef.substring(0, this.schemeIndex); - } - - // No scheme found - return null; - } - - /** - * Returns the optionally decoded scheme component. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded scheme component. - * @see #getScheme() - */ - public String getScheme(boolean decode) { - return decode ? decode(getScheme()) : getScheme(); - } - - /** - * Returns the protocol associated with the scheme component. - * - * @return The protocol associated with the scheme component. - */ - public Protocol getSchemeProtocol() { - return Protocol.valueOf(getScheme()); - } - - /** - * Returns the scheme specific part.
- * Note that no URI decoding is done by this method. - * - * @return The scheme specific part. - */ - public String getSchemeSpecificPart() { - String result = null; - - if (hasScheme()) { - // Scheme found - if (hasFragment()) { - // Fragment found - result = this.internalRef.substring(this.schemeIndex + 1, this.fragmentIndex); - } else { - // No fragment found - result = this.internalRef.substring(this.schemeIndex + 1); - } - } - - return result; - } - - /** - * Returns the optionally decoded scheme specific part. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded scheme specific part. - * @see #getSchemeSpecificPart() - */ - public String getSchemeSpecificPart(boolean decode) { - return decode ? decode(getSchemeSpecificPart()) : getSchemeSpecificPart(); - } - - /** - * Returns the list of segments in a hierarchical path.
- * A new list is created for each call.
- * Note that no URI decoding is done by this method. - * - * @return The segments of a hierarchical path. - */ - public List getSegments() { - final List result = new ArrayList(); - final String path = getPath(); - int start = -2; // The index of the slash starting the segment - char current; - - if (path != null) { - for (int i = 0; i < path.length(); i++) { - current = path.charAt(i); - - if (current == '/') { - if (start == -2) { - // Beginning of an absolute path or sequence of two - // separators - start = i; - } else { - // End of a segment - result.add(path.substring(start + 1, i)); - start = i; - } - } else { - if (start == -2) { - // Starting a new segment for a relative path - start = -1; - } else { - // Looking for the next character - } - } - } - - if (start != -2) { - // Add the last segment - result.add(path.substring(start + 1)); - } - } - - return result; - } - - /** - * Returns the optionally decoded list of segments. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded list of segments. - * @see #getSegments() - */ - public List getSegments(boolean decode) { - final List result = getSegments(); - - if (decode) { - for (int i = 0; i < result.size(); i++) { - result.set(i, decode(result.get(i))); - } - } - - return result; - } - - /** - * Returns the target reference. This method resolves relative references - * against the base reference then normalize them. - * - * @throws IllegalArgumentException If the base reference (after resolution) is - * not absolute. - * @throws IllegalArgumentException If the reference is relative and not base - * reference has been provided. - * - * @return The target reference. - */ - public Reference getTargetRef() { - Reference result = null; - - // Step 1 - Resolve relative reference against their base reference - if (isRelative() && (this.baseRef != null)) { - Reference baseReference = null; - - if (this.baseRef.isAbsolute()) { - baseReference = this.baseRef; - } else { - baseReference = this.baseRef.getTargetRef(); - } - - if (baseReference.isRelative()) { - throw new IllegalArgumentException( - "The base reference must have an absolute hierarchical path component"); - } - - // Relative URI detected - String authority = getAuthority(); - String path = getPath(); - String query = getQuery(); - String fragment = getFragment(); - - // Create an empty reference - result = new Reference(); - result.setScheme(baseReference.getScheme()); - - if (authority != null) { - result.setAuthority(authority); - result.setPath(path); - result.setQuery(query); - } else { - result.setAuthority(baseReference.getAuthority()); - - if ((path == null) || (path.isEmpty())) { - result.setPath(baseReference.getPath()); - - if (query != null) { - result.setQuery(query); - } else { - result.setQuery(baseReference.getQuery()); - } - } else { - if (path.startsWith("/")) { - result.setPath(path); - } else { - final String basePath = baseReference.getPath(); - String mergedPath = null; - - if ((baseReference.getAuthority() != null) && ((basePath == null) || (basePath.isEmpty()))) { - mergedPath = "/" + path; - } else { - // Remove the last segment which may be empty if - // the path is ending with a slash - final int lastSlash = basePath.lastIndexOf('/'); - if (lastSlash == -1) { - mergedPath = path; - } else { - mergedPath = basePath.substring(0, lastSlash + 1) + path; - } - } - - result.setPath(mergedPath); - } - - result.setQuery(query); - } - } - - result.setFragment(fragment); - } else if (isRelative()) { - // Relative reference with no baseRef detected - throw new IllegalArgumentException("Relative references are only usable when a base reference is set."); - } else { - // Absolute URI detected - result = new Reference(this.internalRef); - } - - // Step 2 - Normalize the target reference - result.normalize(); - - return result; - } - - /** - * Returns the user info component for server based hierarchical - * identifiers.
- * Note that no URI decoding is done by this method. - * - * @return The user info component for server based hierarchical identifiers. - */ - public String getUserInfo() { - String result = null; - final String authority = getAuthority(); - - if (authority != null) { - final int index = authority.indexOf('@'); - - if (index != -1) { - result = authority.substring(0, index); - } - } - - return result; - } - - /** - * Returns the optionally decoded user info component. - * - * @param decode Indicates if the result should be decoded using the - * {@link #decode(String)} method. - * @return The optionally decoded user info component. - * @see #getUserInfo() - */ - public String getUserInfo(boolean decode) { - return decode ? decode(getUserInfo()) : getUserInfo(); - } - - /** - * Indicates if this reference has file-like extensions on its last path - * segment. - * - * @return True if there is are extensions. - * @see #getExtensions() - */ - public boolean hasExtensions() { - boolean result = false; - - // If these reference ends with a "/", it cannot be a file. - final String path = getPath(); - if (!((path != null) && path.endsWith("/"))) { - final String lastSegment = getLastSegment(); - - if (lastSegment != null) { - final int extensionsIndex = lastSegment.indexOf('.'); - final int matrixIndex = lastSegment.indexOf(';'); - result = (extensionsIndex != -1) && ((matrixIndex == -1) || (extensionsIndex < matrixIndex)); - } - } - - return result; - } - - /** - * Indicates if this reference has a fragment identifier. - * - * @return True if there is a fragment identifier. - */ - public boolean hasFragment() { - return (this.fragmentIndex != -1); - } - - /** - * Returns a hash code value for the object. - * - * @return A hash code value for the object. - */ - @Override - public int hashCode() { - return (this.internalRef == null) ? 0 : this.internalRef.hashCode(); - } - - /** - * Indicates if this reference has a matrix. - * - * @return True if there is a matrix. - * @see #getMatrix() - */ - public boolean hasMatrix() { - return (getLastSegment().indexOf(';') != -1); - } - - /** - * Indicates if this reference has a query component. - * - * @return True if there is a query. - */ - public boolean hasQuery() { - return (this.queryIndex != -1); - } - - /** - * Indicates if this reference has a scheme component. - * - * @return True if there is a scheme component. - */ - public boolean hasScheme() { - return (this.schemeIndex != -1); - } - - /** - * Indicates if the reference is absolute. - * - * @return True if the reference is absolute. - */ - public boolean isAbsolute() { - return (getScheme() != null); - } - - /** - * Returns true if both reference are equivalent, meaning that they resolve to - * the same target reference. - * - * @param ref The reference to compare. - * @return True if both reference are equivalent. - */ - public boolean isEquivalentTo(Reference ref) { - return getTargetRef().equals(ref.getTargetRef()); - } - - /** - * Indicates if the identifier is hierarchical. - * - * @return True if the identifier is hierarchical, false if it is opaque. - */ - public boolean isHierarchical() { - return isRelative() || (getSchemeSpecificPart().charAt(0) == '/'); - } - - /** - * Indicates if the identifier is opaque. - * - * @return True if the identifier is opaque, false if it is hierarchical. - */ - public boolean isOpaque() { - return isAbsolute() && (getSchemeSpecificPart().charAt(0) != '/'); - } - - /** - * Indicates if the reference is a parent of the hierarchical child reference. - * - * @param childRef The hierarchical reference. - * @return True if the reference is a parent of the hierarchical child - * reference. - */ - public boolean isParent(Reference childRef) { - boolean result = false; - - if ((childRef != null) && (childRef.isHierarchical())) { - result = childRef.toString(false, false).startsWith(toString(false, false)); - } - - return result; - } - - /** - * Indicates if the reference is relative. - * - * @return True if the reference is relative. - */ - public boolean isRelative() { - return (getScheme() == null); - } - - /** - * Normalizes the reference. Useful before comparison between references or when - * building a target reference from a base and a relative references. - * - * @return The current reference. - */ - public Reference normalize() { - // 1. The input buffer is initialized with the now-appended path - // components and the output buffer is initialized to the empty string. - StringBuilder output = new StringBuilder(); - StringBuilder input = new StringBuilder(); - String path = getPath(); - - if (path != null) { - input.append(path); - } - - // 2. While the input buffer is not empty, loop as follows: - while (input.length() > 0) { - // A. If the input buffer begins with a prefix of "../" or "./", - // then remove that prefix from the input buffer; otherwise, - if ((input.length() >= 3) && input.substring(0, 3).equals("../")) { - input.delete(0, 3); - } else if ((input.length() >= 2) && input.substring(0, 2).equals("./")) { - input.delete(0, 2); - } - - // B. if the input buffer begins with a prefix of "/./" or "/.", - // where "." is a complete path segment, then replace that - // prefix with "/" in the input buffer; otherwise, - else if ((input.length() >= 3) && input.substring(0, 3).equals("/./")) { - input.delete(0, 2); - } else if ((input.length() == 2) && input.substring(0, 2).equals("/.")) { - input.delete(1, 2); - } - - // C. if the input buffer begins with a prefix of "/../" or "/..", - // where ".." is a complete path segment, then replace that prefix - // with "/" in the input buffer and remove the last segment and its - // preceding "/" (if any) from the output buffer; otherwise, - else if ((input.length() >= 4) && input.substring(0, 4).equals("/../")) { - input.delete(0, 3); - removeLastSegment(output); - } else if ((input.length() == 3) && input.substring(0, 3).equals("/..")) { - input.delete(1, 3); - removeLastSegment(output); - } - - // D. if the input buffer consists only of "." or "..", then remove - // that from the input buffer; otherwise, - else if ((input.length() == 1) && input.substring(0, 1).equals(".")) { - input.delete(0, 1); - } else if ((input.length() == 2) && input.substring(0, 2).equals("..")) { - input.delete(0, 2); - } - - // E. move the first path segment in the input buffer to the end of - // the output buffer, including the initial "/" character (if any) - // and any subsequent characters up to, but not including, the next - // "/" character or the end of the input buffer. - else { - int max = -1; - for (int i = 1; (max == -1) && (i < input.length()); i++) { - if (input.charAt(i) == '/') { - max = i; - } - } - - if (max != -1) { - // We found the next "/" character. - output.append(input.substring(0, max)); - input.delete(0, max); - } else { - // End of input buffer reached - output.append(input); - input.delete(0, input.length()); - } - } - } - - // Finally, the output buffer is returned as the result - setPath(output.toString()); - - // Ensure that the scheme and host names are reset in lower case - setScheme(getScheme()); - setHostDomain(getHostDomain()); - - // Remove the port if it is equal to the default port of the reference's - // Protocol. - final int hostPort = getHostPort(); - if (hostPort != -1) { - final int defaultPort = Protocol.valueOf(getScheme()).getDefaultPort(); - if (hostPort == defaultPort) { - setHostPort(null); - } - } - - return this; - } - - /** - * Removes the last segement from the output builder. - * - * @param output The output builder to update. - */ - private void removeLastSegment(StringBuilder output) { - int min = -1; - for (int i = output.length() - 1; (min == -1) && (i >= 0); i--) { - if (output.charAt(i) == '/') { - min = i; - } - } - - if (min != -1) { - // We found the previous "/" character. - output.delete(min, output.length()); - } else { - // End of output buffer reached - output.delete(0, output.length()); - } - - } - - /** - * Sets the authority component for hierarchical identifiers. - * - * @param authority The authority component for hierarchical identifiers. - */ - public void setAuthority(String authority) { - final String oldPart = isRelative() ? getRelativePart() : getSchemeSpecificPart(); - String newPart; - final String newAuthority = (authority == null) ? "" : "//" + authority; - - if (oldPart == null) { - newPart = newAuthority; - } else if (oldPart.startsWith("//")) { - int index = oldPart.indexOf('/', 2); - - if (index != -1) { - newPart = newAuthority + oldPart.substring(index); - } else { - index = oldPart.indexOf('?'); - if (index != -1) { - newPart = newAuthority + oldPart.substring(index); - } else { - newPart = newAuthority; - } - } - } else { - newPart = newAuthority + oldPart; - } - - if (isAbsolute()) { - setSchemeSpecificPart(newPart); - } else { - setRelativePart(newPart); - } - } - - /** - * Sets the base reference for relative references. - * - * @param baseRef The base reference for relative references. - */ - public void setBaseRef(Reference baseRef) { - this.baseRef = baseRef; - } - - /** - * Sets the base reference for relative references. - * - * @param baseUri The base URI for relative references. - */ - public void setBaseRef(String baseUri) { - setBaseRef(new Reference(baseUri)); - } - - /** - * Sets the extensions for hierarchical identifiers. An extensions part starts - * after the first '.' character of the last path segment and ends with either - * the end of the segment of with the first ';' character (matrix start). It is - * a token similar to file extensions separated by '.' characters. The value can - * be ommited.
- * Note that no URI decoding is done by this method. - * - * @param extensions The extensions to set or null (without leading or trailing - * dots). - * @see #getExtensions() - * @see #getExtensionsAsArray() - * @see #setExtensions(String[]) - */ - public void setExtensions(String extensions) { - final String lastSegment = getLastSegment(); - - if (lastSegment != null) { - final int extensionIndex = lastSegment.indexOf('.'); - final int matrixIndex = lastSegment.indexOf(';'); - final StringBuilder sb = new StringBuilder(); - - if (extensionIndex != -1) { - // Extensions found - sb.append(lastSegment, 0, extensionIndex); - - if ((extensions != null) && (!extensions.isEmpty())) { - sb.append('.').append(extensions); - } - - if (matrixIndex != -1) { - sb.append(lastSegment.substring(matrixIndex)); - } - } else { - // Extensions not found - if ((extensions != null) && (!extensions.isEmpty())) { - if (matrixIndex != -1) { - // Matrix found, make sure we append it - // after the extensions - sb.append(lastSegment, 0, matrixIndex).append('.').append(extensions) - .append(lastSegment.substring(matrixIndex)); - } else { - // No matrix found, just append the extensions - sb.append(lastSegment).append('.').append(extensions); - } - } else { - // No change necessary - sb.append(lastSegment); - } - } - - // Finally update the last segment - setLastSegment(sb.toString()); - } else { - setLastSegment('.' + extensions); - } - } - - /** - * Sets the extensions based on an array of extension tokens (without dots). - * - * @param extensions The array of extensions. - * @see #getExtensions() - * @see #getExtensionsAsArray() - * @see #setExtensions(String) - */ - public void setExtensions(String[] extensions) { - String exts = null; - - if (extensions != null) { - final StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < extensions.length; i++) { - if (i > 0) { - sb.append('.'); - } - - sb.append(extensions[i]); - } - - exts = sb.toString(); - } - - setExtensions(exts); - } - - /** - * Sets the fragment identifier. - * - * @param fragment The fragment identifier. - * @throws IllegalArgumentException if the fragment parameter contains the - * fragment delimiter ('#'). - */ - public void setFragment(String fragment) { - fragment = encodeInvalidCharacters(fragment); - - if ((fragment != null) && (fragment.indexOf('#') != -1)) { - throw new IllegalArgumentException("Illegal '#' character detected in parameter"); - } - - if (hasFragment()) { - // Existing fragment - if (fragment != null) { - this.internalRef = this.internalRef.substring(0, this.fragmentIndex + 1) + fragment; - } else { - this.internalRef = this.internalRef.substring(0, this.fragmentIndex); - } - } else { - // No existing fragment - if (fragment != null) { - if (this.internalRef != null) { - this.internalRef = this.internalRef + '#' + fragment; - } else { - this.internalRef = '#' + fragment; - } - } else { - // Do nothing - } - } - - updateIndexes(); - } - - /** - * Sets the host domain component for server based hierarchical identifiers. - * - * @param domain The host component for server based hierarchical identifiers. - */ - public void setHostDomain(String domain) { - final String authority = getAuthority(); - - if (authority == null) { - setAuthority(domain); - } else { - if (domain == null) { - domain = ""; - } else { - // URI specification indicates that host names should be - // produced in lower case - domain = domain.toLowerCase(); - } - - // We must prevent the case where the userinfo part contains ':' - // and the case of IPV6 addresses - int indexUI = authority.indexOf('@'); // user info - int indexIPV6 = authority.indexOf(']'); // IPV6 - int indexP = authority.indexOf(':', (indexIPV6 == -1) ? indexUI : indexIPV6); - - if (indexUI != -1) { - // User info found - if (indexP != -1) { - // Port found - setAuthority(authority.substring(0, indexUI + 1) + domain + authority.substring(indexP)); - } else { - // No port found - setAuthority(authority.substring(0, indexUI + 1) + domain); - } - } else { - // No user info found - if (indexP != -1) { - // Port found - setAuthority(domain + authority.substring(indexP)); - } else { - // No port found - setAuthority(domain); - } - } - } - } - - /** - * Sets the optional port number for server based hierarchical identifiers. - * - * @param port The optional port number for server based hierarchical - * identifiers. - * @throws IllegalArgumentException If the autority has not been defined. - */ - public void setHostPort(Integer port) { - final String authority = getAuthority(); - - if (authority != null) { - // We must prevent the case where the userinfo part contains ':' - // and the case of IPV6 addresses - int indexUI = authority.indexOf('@'); // user info - int indexIPV6 = authority.indexOf(']'); // IPV6 - int index = authority.indexOf(':', (indexIPV6 == -1) ? indexUI : indexIPV6); - String newPort = (port == null) ? "" : ":" + port; - - if (index != -1) { - setAuthority(authority.substring(0, index) + newPort); - } else { - setAuthority(authority + newPort); - } - } else { - throw new IllegalArgumentException("No authority defined, please define a host name first"); - } - } - - /** - * Sets the absolute resource identifier. - * - * @param identifier The absolute resource identifier. - * @throws IllegalArgumentException If the identifier parameter contains the - * fragment delimiter ('#'). - */ - public void setIdentifier(String identifier) { - identifier = encodeInvalidCharacters(identifier); - - if (identifier == null) { - identifier = ""; - } - - if (identifier.indexOf('#') != -1) { - throw new IllegalArgumentException("Illegal '#' character detected in parameter"); - } - - if (hasFragment()) { - // Fragment found - this.internalRef = identifier + this.internalRef.substring(this.fragmentIndex); - } else { - // No fragment found - this.internalRef = identifier; - } - - updateIndexes(); - } - - /** - * Sets the last segment of the path. If no path is available, then it creates - * one and adds a slash in front of the given last segmetn.
- * Note that no URI decoding is done by this method. - * - * @param lastSegment The last segment of a hierarchical path. - */ - public void setLastSegment(String lastSegment) { - String path = getPath(); - - if (path != null) { - int lastSlashIndex = path.lastIndexOf('/'); - - if (lastSlashIndex != -1) { - setPath(path.substring(0, lastSlashIndex + 1) + lastSegment); - return; - } - } - - setPath('/' + lastSegment); - } - - /** - * Sets the path component for hierarchical identifiers. - * - * @param path The path component for hierarchical identifiers. - */ - public void setPath(String path) { - final String oldPart = isRelative() ? getRelativePart() : getSchemeSpecificPart(); - String newPart = null; - - if (oldPart != null) { - if (path == null) { - path = ""; - } - - if (oldPart.startsWith("//")) { - // Authority found - final int index1 = oldPart.indexOf('/', 2); - - if (index1 != -1) { - // Path found - final int index2 = oldPart.indexOf('?'); - - if (index2 != -1) { - // Query found - newPart = oldPart.substring(0, index1) + path + oldPart.substring(index2); - } else { - // No query found - newPart = oldPart.substring(0, index1) + path; - } - } else { - // No path found - final int index2 = oldPart.indexOf('?'); - - if (index2 != -1) { - // Query found - newPart = oldPart.substring(0, index2) + path + oldPart.substring(index2); - } else { - // No query found - newPart = oldPart + path; - } - } - } else { - // No authority found - final int index = oldPart.indexOf('?'); - - if (index != -1) { - // Query found - newPart = path + oldPart.substring(index); - } else { - // No query found - newPart = path; - } - } - } else { - newPart = path; - } - - if (isAbsolute()) { - setSchemeSpecificPart(newPart); - } else { - setRelativePart(newPart); - } - } - - /** - * Sets the scheme component based on this protocol. - * - * @param protocol The protocol of the scheme component. - */ - public void setProtocol(Protocol protocol) { - setScheme(protocol.getSchemeName()); - } - - /** - * Sets the query component for hierarchical identifiers. - * - * @param query The query component for hierarchical identifiers. - */ - public void setQuery(String query) { - query = encodeInvalidCharacters(query); - final boolean emptyQueryString = ((query == null) || query.isEmpty()); - - if (hasQuery()) { - // Query found - if (hasFragment()) { - // Fragment found - if (!emptyQueryString) { - this.internalRef = this.internalRef.substring(0, this.queryIndex + 1) + query - + this.internalRef.substring(this.fragmentIndex); - } else { - this.internalRef = this.internalRef.substring(0, this.queryIndex) - + this.internalRef.substring(this.fragmentIndex); - } - } else { - // No fragment found - if (!emptyQueryString) { - this.internalRef = this.internalRef.substring(0, this.queryIndex + 1) + query; - } else { - this.internalRef = this.internalRef.substring(0, this.queryIndex); - } - } - } else { - // No query found - if (hasFragment()) { - // Fragment found - if (!emptyQueryString) { - this.internalRef = this.internalRef.substring(0, this.fragmentIndex) + '?' + query - + this.internalRef.substring(this.fragmentIndex); - } else { - // Do nothing; - } - } else { - // No fragment found - if (!emptyQueryString) { - if (this.internalRef != null) { - this.internalRef = this.internalRef + '?' + query; - } else { - this.internalRef = '?' + query; - } - } else { - // Do nothing; - } - } - } - - updateIndexes(); - } - - /** - * Sets the relative part for relative references only. - * - * @param relativePart The relative part to set. - */ - public void setRelativePart(String relativePart) { - relativePart = encodeInvalidCharacters(relativePart); - - if (relativePart == null) { - relativePart = ""; - } - - if (!hasScheme()) { - // This is a relative reference, no scheme found - if (hasQuery()) { - // Query found - this.internalRef = relativePart + this.internalRef.substring(this.queryIndex); - } else if (hasFragment()) { - // Fragment found - this.internalRef = relativePart + this.internalRef.substring(this.fragmentIndex); - } else { - // No fragment found - this.internalRef = relativePart; - } - } - - updateIndexes(); - } - - /** - * Sets the scheme component. - * - * @param scheme The scheme component. - */ - public void setScheme(String scheme) { - scheme = encodeInvalidCharacters(scheme); - - if (scheme != null) { - // URI specification indicates that scheme names should be - // produced in lower case - scheme = scheme.toLowerCase(); - } - - if (hasScheme()) { - // Scheme found - if (scheme != null) { - this.internalRef = scheme + this.internalRef.substring(this.schemeIndex); - } else { - this.internalRef = this.internalRef.substring(this.schemeIndex + 1); - } - } else { - // No scheme found - if (scheme != null) { - if (this.internalRef == null) { - this.internalRef = scheme + ':'; - } else { - this.internalRef = scheme + ':' + this.internalRef; - } - } - } - - updateIndexes(); - } - - /** - * Sets the scheme specific part. - * - * @param schemeSpecificPart The scheme specific part. - */ - public void setSchemeSpecificPart(String schemeSpecificPart) { - schemeSpecificPart = encodeInvalidCharacters(schemeSpecificPart); - - if (schemeSpecificPart == null) { - schemeSpecificPart = ""; - } - - if (hasScheme()) { - // Scheme found - if (hasFragment()) { - // Fragment found - this.internalRef = this.internalRef.substring(0, this.schemeIndex + 1) + schemeSpecificPart - + this.internalRef.substring(this.fragmentIndex); - } else { - // No fragment found - this.internalRef = this.internalRef.substring(0, this.schemeIndex + 1) + schemeSpecificPart; - } - } else { - // No scheme found - if (hasFragment()) { - // Fragment found - this.internalRef = schemeSpecificPart + this.internalRef.substring(this.fragmentIndex); - } else { - // No fragment found - this.internalRef = schemeSpecificPart; - } - } - - updateIndexes(); - } - - /** - * Sets the segments of a hierarchical path.
- * A new absolute path will replace any existing one. - * - * @param segments The segments of the hierarchical path. - */ - public void setSegments(List segments) { - final StringBuilder sb = new StringBuilder(); - - for (final String segment : segments) { - sb.append('/').append(segment); - } - - setPath(sb.toString()); - } - - /** - * Sets the user info component for server based hierarchical identifiers. - * - * @param userInfo The user info component for server based hierarchical - * identifiers. - * @throws IllegalArgumentException If the autority part has not been defined. - */ - public void setUserInfo(String userInfo) { - final String authority = getAuthority(); - - if (authority != null) { - final int index = authority.indexOf('@'); - final String newUserInfo = (userInfo == null) ? "" : userInfo + '@'; - - if (index != -1) { - setAuthority(newUserInfo + authority.substring(index + 1)); - } else { - setAuthority(newUserInfo + authority); - } - } else { - throw new IllegalArgumentException("No authority defined, please define a host name first"); - } - } - - /** - * Returns the reference as an URI string. - * - * @return The reference as an URI string. - */ - @Override - public String toString() { - return this.internalRef; - } - - /** - * Returns the URI reference string. - * - * @param query Indicates if the query should be included; - * @param fragment Indicates if the fragment should be included; - * @return The URI reference string. - */ - public String toString(boolean query, boolean fragment) { - if (query) { - if (fragment) { - return this.internalRef; - } - - if (hasFragment()) { - return this.internalRef.substring(0, this.fragmentIndex); - } - return this.internalRef; - } - - if (fragment) { - // Fragment should be included - if (hasQuery()) { - // Query found - if (hasFragment()) { - // Fragment found - return this.internalRef.substring(0, this.queryIndex) + "#" + getFragment(); - } - - // No fragment found - return this.internalRef.substring(0, this.queryIndex); - } - - // No query found - return this.internalRef; - } - - // Fragment should not be included - if (hasQuery()) { - // Query found - return this.internalRef.substring(0, this.queryIndex); - } - if (hasFragment()) { - // Fragment found - return this.internalRef.substring(0, this.fragmentIndex); - } - - return this.internalRef; - } - - /** - * Converts to a {@link java.net.URI} instance. Note that relative references - * are resolved before conversion using the {@link #getTargetRef()} method. - * - * @return A {@link java.net.URI} instance. - */ - public java.net.URI toUri() { - return java.net.URI.create(getTargetRef().toString()); - } - - /** - * Converts to a {@link java.net.URL} instance. Note that relative references - * are resolved before conversion using the {@link #getTargetRef()} method. - * - * @return A {@link java.net.URL} instance. - */ - public java.net.URL toUrl() { - java.net.URL result = null; - - try { - result = new java.net.URL(getTargetRef().toString()); - } catch (java.net.MalformedURLException e) { - throw new IllegalArgumentException("Malformed URL exception", e); - } - - return result; - } - - /** - * Updates internal indexes. - */ - private void updateIndexes() { - if (this.internalRef != null) { - // Compute the indexes - final int firstSlashIndex = this.internalRef.indexOf('/'); - this.schemeIndex = this.internalRef.indexOf(':'); - - if ((firstSlashIndex != -1) && (this.schemeIndex > firstSlashIndex)) { - // We are in the rare case of a relative reference where one of - // the path segments contains a colon character. In this case, - // we ignore the colon as a valid scheme index. - // Note that this colon can't be in the first segment as it is - // forbidden by the URI RFC. - this.schemeIndex = -1; - } - - this.queryIndex = this.internalRef.indexOf('?'); - this.fragmentIndex = this.internalRef.indexOf('#'); - - if (hasQuery() && hasFragment() && (this.queryIndex > this.fragmentIndex)) { - // Query sign inside fragment - this.queryIndex = -1; - } - - if (hasQuery() && this.schemeIndex > this.queryIndex) { - // Colon sign inside query - this.schemeIndex = -1; - } - - if (hasFragment() && this.schemeIndex > this.fragmentIndex) { - // Colon sign inside fragment - this.schemeIndex = -1; - } - } else { - this.schemeIndex = -1; - this.queryIndex = -1; - this.fragmentIndex = -1; - } - } + /** Helps to map characters and their validity as URI characters. */ + private static final boolean[] charValidityMap = new boolean[127]; + + static { + // Initialize the map of valid characters. + for (int character = 0; character < 127; character++) { + charValidityMap[character] = + isReserved(character) + || isUnreserved(character) + || (character == '%') + || (character == '{') + || (character == '}'); + } + } + + /** + * Decodes a given string using the standard URI encoding mechanism and the UTF-8 character set. + * + * @param toDecode The string to decode. + * @return The decoded string. + */ + public static String decode(String toDecode) { + return decode(toDecode, CharacterSet.UTF_8); + } + + /** + * Decodes a given string using the standard URI encoding mechanism. If the provided character + * set is null, the string is returned but not decoded. Note: The World Wide Web + * Consortium Recommendation states that UTF-8 should be used. Not doing so may introduce + * incompatibilities. + * + * @param toDecode The string to decode. + * @param characterSet The name of a supported character encoding. + * @return The decoded string or null if the named character encoding is not supported. + */ + public static String decode(String toDecode, CharacterSet characterSet) { + String result = null; + try { + result = + (characterSet == null) + ? toDecode + : java.net.URLDecoder.decode(toDecode, characterSet.getName()); + } catch (UnsupportedEncodingException uee) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Unable to decode the string with the UTF-8 character set.", + uee); + } + + return result; + } + + /** + * Encodes a given string using the standard URI encoding mechanism and the UTF-8 character set. + * + * @param toEncode The string to encode. + * @return The encoded string. + */ + public static String encode(String toEncode) { + return encode(toEncode, true, CharacterSet.UTF_8); + } + + /** + * Encodes a given string using the standard URI encoding mechanism and the UTF-8 character set. + * Useful to prevent the usage of '+' to encode spaces (%20 instead). The '*' characters are + * encoded as %2A and %7E are replaced by '~'. + * + * @param toEncode The string to encode. + * @param queryString True if the string to encode is part of a query string instead of a HTML + * form post. + * @return The encoded string. + */ + public static String encode(String toEncode, boolean queryString) { + return encode(toEncode, queryString, CharacterSet.UTF_8); + } + + /** + * Encodes a given string using the standard URI encoding mechanism and the UTF-8 character set. + * Useful to prevent the usage of '+' to encode spaces (%20 instead). The '*' characters are + * encoded as %2A and %7E are replaced by '~'. + * + * @param toEncode The string to encode. + * @param queryString True if the string to encode is part of a query string instead of a HTML + * form post. + * @param characterSet The supported character encoding. + * @return The encoded string. + */ + public static String encode(String toEncode, boolean queryString, CharacterSet characterSet) { + + String result = null; + + try { + result = + (characterSet == null) + ? toEncode + : java.net.URLEncoder.encode(toEncode, characterSet.getName()); + } catch (UnsupportedEncodingException uee) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Unable to encode the string with the UTF-8 character set.", + uee); + } + + if (result != null && queryString) { + result = result.replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); + } + + return result; + } + + /** + * Encodes a given string using the standard URI encoding mechanism. If the provided character + * set is null, the string is returned but not encoded. + * + *

Note: The World Wide Web + * Consortium Recommendation states that UTF-8 should be used. Not doing so may introduce + * incompatibilities. + * + * @param toEncode The string to encode. + * @param characterSet The supported character encoding. + * @return The encoded string or null if the named character encoding is not supported. + */ + public static String encode(String toEncode, CharacterSet characterSet) { + return encode(toEncode, true, characterSet); + } + + /** + * Indicates if the given character is alphabetical (a-z or A-Z). + * + * @param character The character to test. + * @return True if the given character is alphabetical (a-z or A-Z). + */ + private static boolean isAlpha(int character) { + return isUpperCase(character) || isLowerCase(character); + } + + /** + * Indicates if the given character is a digit (0-9). + * + * @param character The character to test. + * @return True if the given character is a digit (0-9). + */ + private static boolean isDigit(int character) { + return (character >= '0') && (character <= '9'); + } + + /** + * Indicates if the given character is a generic URI component delimiter character. + * + * @param character The character to test. + * @return True if the given character is a generic URI component delimiter character. + */ + public static boolean isGenericDelimiter(int character) { + return (character == ':') + || (character == '/') + || (character == '?') + || (character == '#') + || (character == '[') + || (character == ']') + || (character == '@'); + } + + /** + * Indicates if the given character is lower case (a-z). + * + * @param character The character to test. + * @return True if the given character is lower case (a-z). + */ + private static boolean isLowerCase(int character) { + return (character >= 'a') && (character <= 'z'); + } + + /** + * Indicates if the given character is a reserved URI character. + * + * @param character The character to test. + * @return True if the given character is a reserved URI character. + */ + public static boolean isReserved(int character) { + return isGenericDelimiter(character) || isSubDelimiter(character); + } + + /** + * Indicates if the given character is a URI subcomponent delimiter character. + * + * @param character The character to test. + * @return True if the given character is a URI subcomponent delimiter character. + */ + public static boolean isSubDelimiter(int character) { + return (character == '!') + || (character == '$') + || (character == '&') + || (character == '\'') + || (character == '(') + || (character == ')') + || (character == '*') + || (character == '+') + || (character == ',') + || (character == ';') + || (character == '='); + } + + /** + * Indicates if the given character is an unreserved URI character. + * + * @param character The character to test. + * @return True if the given character is an unreserved URI character. + */ + public static boolean isUnreserved(int character) { + return isAlpha(character) + || isDigit(character) + || (character == '-') + || (character == '.') + || (character == '_') + || (character == '~'); + } + + /** + * Indicates if the given character is upper case (A-Z). + * + * @param character The character to test. + * @return True if the given character is upper case (A-Z). + */ + private static boolean isUpperCase(int character) { + return (character >= 'A') && (character <= 'Z'); + } + + /** + * Indicates if the given character is a valid URI character. + * + * @param character The character to test. + * @return True if the given character is a valid URI character. + */ + public static boolean isValid(int character) { + return character >= 0 && character < 127 && charValidityMap[character]; + } + + /** + * Creates a reference string from its parts. + * + * @param scheme The scheme ("http", "https" or "ftp"). + * @param hostName The host name or IP address. + * @param hostPort The host port (default ports are correctly ignored). + * @param path The path component for hierarchical identifiers. + * @param query The optional query component for hierarchical identifiers. + * @param fragment The optional fragment identifier. + * @return The reference as String. + */ + public static String toString( + String scheme, + String hostName, + Integer hostPort, + String path, + String query, + String fragment) { + String host = hostName; + + // Appends the host port number + if (hostPort != null) { + final int defaultPort = Protocol.valueOf(scheme).getDefaultPort(); + if (hostPort != defaultPort) { + host = hostName + ':' + hostPort; + } + } + + return toString(scheme, host, path, query, fragment); + } + + /** + * Creates a relative reference string from its parts. + * + * @param relativePart The relative part component. + * @param query The optional query component for hierarchical identifiers. + * @param fragment The optional fragment identifier. + * @return The relative reference as a String. + */ + public static String toString(String relativePart, String query, String fragment) { + final StringBuilder sb = new StringBuilder(); + + // Append the path + if (relativePart != null) { + sb.append(relativePart); + } + + // Append the query string + if (query != null) { + sb.append('?').append(query); + } + + // Append the fragment identifier + if (fragment != null) { + sb.append('#').append(fragment); + } + + // Actually construct the reference + return sb.toString(); + } + + /** + * Creates a reference string from its parts. + * + * @param scheme The scheme ("http", "https" or "ftp"). + * @param host The host name or IP address plus the optional port number. + * @param path The path component for hierarchical identifiers. + * @param query The optional query component for hierarchical identifiers. + * @param fragment The optional fragment identifier. + * @return The reference a String. + */ + public static String toString( + String scheme, String host, String path, String query, String fragment) { + final StringBuilder sb = new StringBuilder(); + + if (scheme != null) { + // Append the scheme and host name + sb.append(scheme.toLowerCase()).append("://").append(host); + } + + // Append the path + if (path != null) { + sb.append(path); + } + + // Append the query string + if (query != null) { + sb.append('?').append(query); + } + + // Append the fragment identifier + if (fragment != null) { + sb.append('#').append(fragment); + } + + // Actually construct the reference + return sb.toString(); + } + + /** The base reference for relative references. */ + private volatile Reference baseRef; + + /** The fragment separator index. */ + private volatile int fragmentIndex; + + /** The internal reference. */ + private volatile String internalRef; + + /** The query separator index. */ + private volatile int queryIndex; + + /** The scheme separator index. */ + private volatile int schemeIndex; + + /** Empty constructor. */ + public Reference() { + this((Reference) null, (String) null); + } + + /** + * Constructor from an {@link java.net.URI} instance. + * + * @param uri The {@link java.net.URI} instance. + */ + public Reference(java.net.URI uri) { + this(uri.toString()); + } + + /** + * Constructor from an {@link java.net.URI} instance. + * + * @param baseUri The base {@link java.net.URI} instance. + * @param uri The {@link java.net.URI} instance. + */ + public Reference(java.net.URI baseUri, java.net.URI uri) { + this(baseUri.toString(), uri.toString()); + } + + /** + * Constructor from an {@link java.net.URL} instance. + * + * @param url The {@link java.net.URL} instance. + */ + public Reference(java.net.URL url) { + this(url.toString()); + } + + /** + * Constructor for a protocol and host name. Uses the default port for the given protocol. + * + * @param protocol Protocol/scheme to use + * @param hostName The host name or IP address. + */ + public Reference(Protocol protocol, String hostName) { + this(protocol, hostName, protocol.getDefaultPort()); + } + + /** + * Constructor for a protocol, host name and host port + * + * @param protocol Protocol/scheme to use + * @param hostName The host name or IP address. + * @param hostPort The host port (default ports are correctly ignored). + */ + public Reference(Protocol protocol, String hostName, int hostPort) { + this(protocol.getSchemeName(), hostName, hostPort, null, null, null); + } + + /** + * Clone constructor. + * + * @param ref The reference to clone. + */ + public Reference(Reference ref) { + this(ref.baseRef, ref.internalRef); + } + + /** + * Constructor from a URI reference (most likely relative). + * + * @param baseRef The base reference. + * @param uriReference The URI reference, either absolute or relative. + */ + public Reference(Reference baseRef, Reference uriReference) { + this(baseRef, uriReference.toString()); + } + + /** + * Constructor from a URI reference (most likely relative). + * + * @param baseRef The base reference. + * @param uriRef The URI reference, either absolute or relative. + */ + public Reference(Reference baseRef, String uriRef) { + uriRef = encodeInvalidCharacters(uriRef); + this.baseRef = baseRef; + this.internalRef = uriRef; + updateIndexes(); + } + + /** + * Constructor of relative reference from its parts. + * + * @param baseRef The base reference. + * @param relativePart The relative part component (most of the time it is the path component). + * @param query The optional query component for hierarchical identifiers. + * @param fragment The optional fragment identifier. + */ + public Reference(Reference baseRef, String relativePart, String query, String fragment) { + this(baseRef, toString(relativePart, query, fragment)); + } + + /** + * Constructor from a URI reference. + * + * @param uriReference The URI reference, either absolute or relative. + */ + public Reference(String uriReference) { + this((Reference) null, uriReference); + } + + /** + * Constructor from an identifier and a fragment. + * + * @param identifier The resource identifier. + * @param fragment The fragment identifier. + */ + public Reference(String identifier, String fragment) { + this((fragment == null) ? identifier : identifier + '#' + fragment); + } + + /** + * Constructor of absolute reference from its parts. + * + * @param scheme The scheme ("http", "https" or "ftp"). + * @param hostName The host name or IP address. + * @param hostPort The host port (default ports are correctly ignored). + * @param path The path component for hierarchical identifiers. + * @param query The optional query component for hierarchical identifiers. + * @param fragment The optional fragment identifier. + */ + public Reference( + String scheme, + String hostName, + int hostPort, + String path, + String query, + String fragment) { + this(toString(scheme, hostName, hostPort, path, query, fragment)); + } + + /** + * Adds a parameter to the query component. The name and value are automatically URL encoded if + * necessary. + * + * @param parameter The parameter to add. + * @return The updated reference. + */ + public Reference addQueryParameter(Parameter parameter) { + return addQueryParameter(parameter.getName(), parameter.getValue()); + } + + /** + * Adds a parameter to the query component. The name and value are automatically URL encoded if + * necessary. + * + * @param name The parameter name. + * @param value The optional parameter value. + * @return The updated reference. + */ + public Reference addQueryParameter(String name, String value) { + String query = getQuery(); + + if (query == null) { + if (value == null) { + setQuery(encode(name)); + } else { + setQuery(encode(name) + '=' + encode(value)); + } + } else { + if (value == null) { + setQuery(query + '&' + encode(name)); + } else { + setQuery(query + '&' + encode(name) + '=' + encode(value)); + } + } + + return this; + } + + /** + * Adds several parameters to the query component. The name and value are automatically URL + * encoded if necessary. + * + * @param parameters The parameters to add. + * @return The updated reference. + */ + public Reference addQueryParameters(Iterable parameters) { + for (Parameter param : parameters) { + addQueryParameter(param); + } + + return this; + } + + /** + * Adds a segment at the end of the path. If the current path doesn't end with a slash + * character, one is inserted before the new segment value. The value is automatically encoded + * if necessary. + * + * @param value The segment value to add. + * @return The updated reference. + */ + public Reference addSegment(String value) { + final String path = getPath(); + + if (value != null) { + if (path == null) { + setPath("/" + value); + } else if (path.endsWith("/")) { + setPath(path + encode(value)); + } else { + setPath(path + "/" + encode(value)); + } + } + + return this; + } + + public Reference copy() { + final Reference newRef = new Reference(); + + if (this.baseRef == null) { + newRef.baseRef = null; + } else if (equals(this.baseRef)) { + newRef.baseRef = newRef; + } else { + newRef.baseRef = this.baseRef.copy(); + } + + newRef.fragmentIndex = this.fragmentIndex; + newRef.internalRef = this.internalRef; + newRef.queryIndex = this.queryIndex; + newRef.schemeIndex = this.schemeIndex; + return newRef; + } + + /** + * Checks if all characters are valid and encodes invalid characters if necessary. + * + * @param uriRef The URI reference to check. + * @return The original reference, eventually with invalid URI characters encoded. + */ + private String encodeInvalidCharacters(String uriRef) throws IllegalArgumentException { + if (uriRef == null) { + return null; + } + + boolean valid = true; + + // Ensure that all characters are valid, otherwise encode them + for (int i = 0; valid && (i < uriRef.length()); i++) { + final char character = uriRef.charAt(i); + + if (!isValid(character) + || ((character == '%') + && (i + > uriRef.length() + - 2))) { // missing the 2 necessary trailing characters + valid = false; + Context.getCurrentLogger() + .log( + Level.FINE, + "Invalid character \"{0}\" detected in URI at index {1} will be encoded.", + new Object[] {character, i}); + } + } + + if (valid) { + return uriRef; + } + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < uriRef.length(); i++) { + final char character = uriRef.charAt(i); + if (isValid(character)) { + if ((character == '%') && (i > uriRef.length() - 2)) { + sb.append("%25"); + } else { + sb.append(character); + } + } else { + sb.append(encode(String.valueOf(character))); + } + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Reference that)) { + return false; + } + return Objects.equals(this.internalRef, that.internalRef); + } + + /** + * Returns the authority component for hierarchical identifiers. Includes the user info, host + * name, and the host port number.
+ * Note that this method does no URI decoding. + * + * @return The authority component for hierarchical identifiers. + */ + public String getAuthority() { + final String part = isRelative() ? getRelativePart() : getSchemeSpecificPart(); + + if ((part != null) && part.startsWith("//")) { + int index = part.indexOf('/', 2); + + if (index != -1) { + return part.substring(2, index); + } + + index = part.indexOf('?'); + if (index != -1) { + return part.substring(2, index); + } + + return part.substring(2); + } + + return null; + } + + /** + * Returns the optionally decoded authority component. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded authority component. + * @see #getAuthority() + */ + public String getAuthority(boolean decode) { + return decode ? decode(getAuthority()) : getAuthority(); + } + + /** + * Returns the base reference for relative references. + * + * @return The base reference for relative references. + */ + public Reference getBaseRef() { + return this.baseRef; + } + + /** + * Returns the optional extensions for hierarchical identifiers. An extensions part starts after + * the first '.' character of the last path segment and ends with either the end of the segment + * of with the first ';' character (matrix start). It is a token similar to file extensions + * separated by '.' characters. The value can be omitted.
+ * Note that this method does no URI decoding. + * + * @return The extensions or null. + * @see #getExtensionsAsArray() + * @see #setExtensions(String) + */ + public String getExtensions() { + String result = null; + final String lastSegment = getLastSegment(); + + if (lastSegment != null) { + final int extensionIndex = lastSegment.indexOf('.'); + final int matrixIndex = lastSegment.indexOf(';'); + + if (extensionIndex != -1) { + // Extensions found + if (matrixIndex != -1) { + result = lastSegment.substring(extensionIndex + 1, matrixIndex); + } else { + // No matrix found + result = lastSegment.substring(extensionIndex + 1); + } + } + } + + return result; + } + + /** + * Returns the extensions as an array or null if no extension is found. + * + * @return The extensions as an array or null if no extension is found. + * @see #getExtensions() + */ + public String[] getExtensionsAsArray() { + String[] result = null; + final String extensions = getExtensions(); + + if (extensions != null) { + result = extensions.split("\\."); + } + + return result; + } + + /** + * Returns the fragment identifier.
+ * Note that this method does no URI decoding. + * + * @return The fragment identifier. + */ + public String getFragment() { + if (hasFragment()) { + return this.internalRef.substring(this.fragmentIndex + 1); + } + + return null; + } + + /** + * Returns the optionally decoded fragment identifier. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded fragment identifier. + * @see #getFragment() + */ + public String getFragment(boolean decode) { + return decode ? decode(getFragment()) : getFragment(); + } + + /** + * Returns the hierarchical part which is equivalent to the scheme-specific part less the query + * component.
+ * Note that this method does no URI decoding. + * + * @return The hierarchical part. + */ + public String getHierarchicalPart() { + if (hasScheme()) { + // Scheme found + if (hasQuery()) { + // Query found + return this.internalRef.substring(this.schemeIndex + 1, this.queryIndex); + } + + // No query found + if (hasFragment()) { + // Fragment found + return this.internalRef.substring(this.schemeIndex + 1, this.fragmentIndex); + } + + // No fragment found + return this.internalRef.substring(this.schemeIndex + 1); + } + + // No scheme found + if (hasQuery()) { + // Query found + return this.internalRef.substring(0, this.queryIndex); + } + if (hasFragment()) { + // Fragment found + return this.internalRef.substring(0, this.fragmentIndex); + } + + // No fragment found + return this.internalRef; + } + + /** + * Returns the optionally decoded hierarchical part. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded hierarchical part. + * @see #getHierarchicalPart() + */ + public String getHierarchicalPart(boolean decode) { + return decode ? decode(getHierarchicalPart()) : getHierarchicalPart(); + } + + /** + * Returns the host domain name component for server-based hierarchical identifiers. It can also + * be replaced by an IP address when no domain name was registered.
+ * Note that this method does no URI decoding. + * + * @return The host domain name component for server-based hierarchical identifiers. + */ + public String getHostDomain() { + String result = null; + final String authority = getAuthority(); + + if (authority != null) { + // We must prevent the case where the userinfo part contains ':' + // and the case of IPV6 addresses + int indexUI = authority.indexOf('@'); // user info + int indexIPV6 = authority.indexOf(']'); // IPV6 + int indexP = authority.indexOf(':', (indexIPV6 == -1) ? indexUI : indexIPV6); + + if (indexUI != -1) { + // User info found + if (indexP != -1) { + // Port found + result = authority.substring(indexUI + 1, indexP); + } else { + // No port found + result = authority.substring(indexUI + 1); + } + } else { + // No user info found + if (indexP != -1) { + // Port found + result = authority.substring(0, indexP); + } else { + // No port found + result = authority; + } + } + } + + return result; + } + + /** + * Returns the optionally decoded host domain name component. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded host domain name component. + * @see #getHostDomain() + */ + public String getHostDomain(boolean decode) { + return decode ? decode(getHostDomain()) : getHostDomain(); + } + + /** + * Returns the host identifier. Includes the scheme, the host name, and the host port number. + *
+ * Note that this method does no URI decoding. + * + * @return The host identifier. + */ + public String getHostIdentifier() { + return getScheme() + "://" + getAuthority(); + } + + /** + * Returns the optionally decoded host identifier. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded host identifier. + * @see #getHostIdentifier() + */ + public String getHostIdentifier(boolean decode) { + return decode ? decode(getHostIdentifier()) : getHostIdentifier(); + } + + /** + * Returns the optional port number for server-based hierarchical identifiers. + * + * @return The optional port number for server-based hierarchical identifiers or -1 if the port + * number does not exist. + */ + public int getHostPort() { + int result = -1; + final String authority = getAuthority(); + + if (authority != null) { + // We must prevent the case where the userinfo part contains ':' + // and the case of IPV6 addresses + int indexUI = authority.indexOf('@'); // user info + int indexIPV6 = authority.indexOf(']'); // IPV6 + int index = authority.indexOf(':', (indexIPV6 == -1) ? indexUI : indexIPV6); + + if (index != -1) { + try { + result = Integer.parseInt(authority.substring(index + 1)); + } catch (NumberFormatException nfe) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Can''t parse hostPort : [hostRef,requestUri]=[{0},{1}]", + new Object[] {getBaseRef(), this.internalRef}); + } + } + } + + return result; + } + + /** + * Returns the absolute resource identifier, without the fragment.
+ * Note that this method does no URI decoding. + * + * @return The absolute resource identifier, without the fragment. + */ + public String getIdentifier() { + if (hasFragment()) { + // Fragment found + return this.internalRef.substring(0, this.fragmentIndex); + } + + // No fragment found + return this.internalRef; + } + + /** + * Returns the optionally decoded absolute resource identifier. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded absolute resource identifier. + * @see #getIdentifier() + */ + public String getIdentifier(boolean decode) { + return decode ? decode(getIdentifier()) : getIdentifier(); + } + + /** + * Returns the last segment of a hierarchical path.
+ * For example, the "/a/b/c" and "/a/b/c/" paths have the same segments: "a", "b", "c.
+ * Note that this method does no URI decoding. + * + * @return The last segment of a hierarchical path. + */ + public String getLastSegment() { + String result = null; + String path = getPath(); + + if (path != null) { + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + + final int lastSlash = path.lastIndexOf('/'); + + if (lastSlash != -1) { + result = path.substring(lastSlash + 1); + } + } + + return result; + } + + /** + * Returns the optionally decoded last segment. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded last segment. + * @see #getLastSegment() + */ + public String getLastSegment(boolean decode) { + return getLastSegment(decode, false); + } + + /** + * Returns the optionally decoded last segment. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @param excludeMatrix True if the matrix parameters are dropped from the segments. + * @return The optionally decoded last segment. + * @see #getLastSegment() + */ + public String getLastSegment(boolean decode, boolean excludeMatrix) { + String result = getLastSegment(); + + if (excludeMatrix && (result != null)) { + final int matrixIndex = result.indexOf(';'); + + if (matrixIndex != -1) { + result = result.substring(0, matrixIndex); + } + } + + return decode ? decode(result) : result; + } + + /** + * Returns the optional matrix for hierarchical identifiers. A matrix part starts after the + * first ';' character of the last path segment. It is a sequence of 'name=value' parameters + * separated by ';' characters. The value can be omitted.
+ * Note that this method does no URI decoding. + * + * @return The matrix or null. + */ + public String getMatrix() { + String lastSegment = getLastSegment(); + + if (lastSegment != null) { + final int matrixIndex = lastSegment.indexOf(';'); + + if (matrixIndex != -1) { + return lastSegment.substring(matrixIndex + 1); + } + } + + // No matrix found + return null; + } + + /** + * Returns the optionally decoded matrix. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded matrix. + * @see #getMatrix() + */ + public String getMatrix(boolean decode) { + return decode ? decode(getMatrix()) : getMatrix(); + } + + /** + * Returns the optional matrix as a form. + * + * @return The optional matrix component as a form. + */ + public Form getMatrixAsForm() { + return new Form(getMatrix(), ';'); + } + + /** + * Returns the optional matrix as a form submission. + * + * @param characterSet The supported character encoding. + * @return The optional matrix as a form. + */ + public Form getMatrixAsForm(CharacterSet characterSet) { + return new Form(getMatrix(), characterSet, ';'); + } + + /** + * Returns the parent reference of a hierarchical reference. The last slash of the path will be + * considered as the end of the parent path. + * + * @return The parent reference of a hierarchical reference. + */ + public Reference getParentRef() { + Reference result = null; + + if (isHierarchical()) { + String parentRef = null; + String path = getPath(); + + if (!path.equals("/") && !path.isEmpty()) { + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + + if (isAbsolute()) { + parentRef = getHostIdentifier() + path.substring(0, path.lastIndexOf('/') + 1); + } else { + parentRef = path.substring(0, path.lastIndexOf('/') + 1); + } + } else { + parentRef = this.internalRef; + } + + result = new Reference(parentRef); + } + + return result; + } + + /** + * Returns the path component for hierarchical identifiers. If no path is available, it returns + * null.
+ * Note that this method does no URI decoding. + * + * @return The path component for hierarchical identifiers. + */ + public String getPath() { + String result = null; + String part = isRelative() ? getRelativePart() : getSchemeSpecificPart(); + + if (part != null) { + if (part.startsWith("//")) { + // Authority found + int index1 = part.indexOf('/', 2); + + if (index1 != -1) { + // Path found + int index2 = part.indexOf('?'); + + if (index2 != -1) { + // Query found + result = part.substring(Math.min(index1, index2), index2); + } else { + // No query found + result = part.substring(index1); + } + } else { + // Path must be empty in this case + } + } else { + // No authority found + int index = part.indexOf('?'); + + if (index != -1) { + // Query found + result = part.substring(0, index); + } else { + // No query found + result = part; + } + } + } + + return result; + } + + /** + * Returns the optionally decoded path component. If no path is available, it returns null. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded path component. + * @see #getPath() + */ + public String getPath(boolean decode) { + return decode ? decode(getPath()) : getPath(); + } + + /** + * Returns the optional query component for hierarchical identifiers.
+ * Note that this method does no URI decoding. + * + * @return The query component or null. + */ + public String getQuery() { + if (hasQuery()) { + // Query found + if (hasFragment()) { + if (this.queryIndex < this.fragmentIndex) { + // Fragment found and query sign not inside fragment + return this.internalRef.substring(this.queryIndex + 1, this.fragmentIndex); + } + + return null; + } + + // No fragment found + return this.internalRef.substring(this.queryIndex + 1); + } + + // No query found + return null; + } + + /** + * Returns the optionally decoded query component. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded query component. + * @see #getQuery() + */ + public String getQuery(boolean decode) { + return decode ? decode(getQuery()) : getQuery(); + } + + /** + * Returns the optional query component as a form. + * + * @return The optional query component as a form. + */ + public Form getQueryAsForm() { + return new Form(getQuery()); + } + + /** + * Returns the optional query component as a form. + * + * @param decode Indicates if the names and values should be automatically decoded. + * @return The optional query component as a form. + */ + public Form getQueryAsForm(boolean decode) { + return new Form(getQuery(), decode); + } + + /** + * Returns the optional query component as a form submission. + * + * @param characterSet The supported character encoding. + * @return The optional query component as a form submission. + */ + public Form getQueryAsForm(CharacterSet characterSet) { + return new Form(getQuery(), characterSet); + } + + /** + * Returns the relative part of relative references, without the query and fragment. If the + * reference is absolute, then null is returned.
+ * Note that this method does no URI decoding. + * + * @return The relative part. + */ + public String getRelativePart() { + return isRelative() ? toString(false, false) : null; + } + + /** + * Returns the optionally decoded relative part. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded relative part. + * @see #getRelativePart() + */ + public String getRelativePart(boolean decode) { + return decode ? decode(getRelativePart()) : getRelativePart(); + } + + /** + * Returns the current reference as a relative reference to the current base reference. This + * method should only be invoked for absolute references, otherwise an IllegalArgumentException + * will be raised. + * + * @return The current reference as a relative reference to the current base reference. + * @see #getRelativeRef(Reference) + */ + public Reference getRelativeRef() { + return getRelativeRef(getBaseRef()); + } + + /** + * Returns the current reference relatively to a base reference. This method should only be + * invoked for absolute references, otherwise an IllegalArgumentException will be raised. + * + * @param base The base reference to use. + * @return The current reference relatively to a base reference. + * @throws IllegalArgumentException If the relative reference is computed, although the + * reference or the base reference are not absolute or not hierarchical. + */ + public Reference getRelativeRef(Reference base) { + Reference result = null; + + if (base == null) { + result = this; + } else if (!isAbsolute() || !isHierarchical()) { + throw new IllegalArgumentException( + "The reference must have an absolute hierarchical path component"); + } else if (!base.isAbsolute() || !base.isHierarchical()) { + throw new IllegalArgumentException( + "The base reference must have an absolute hierarchical path component"); + } else if (!getHostIdentifier().equals(base.getHostIdentifier())) { + result = this; + } else { + final String localPath = getPath(); + final String basePath = base.getPath(); + String relativePath = null; + + if ((basePath == null) || (localPath == null)) { + relativePath = localPath; + } else { + // Find the junction point + boolean diffFound = false; + int lastSlashIndex = -1; + int i = 0; + char current; + while (!diffFound && (i < localPath.length()) && (i < basePath.length())) { + current = localPath.charAt(i); + + if (current != basePath.charAt(i)) { + diffFound = true; + } else { + if (current == '/') { + lastSlashIndex = i; + } + i++; + } + } + + if (!diffFound) { + if (localPath.length() == basePath.length()) { + // Both paths are strictly equivalent + relativePath = "."; + } else if (i == localPath.length()) { + // End of local path reached + if (basePath.charAt(i) == '/') { + if ((i + 1) == basePath.length()) { + // Both paths are strictly equivalent + relativePath = "."; + } else { + // The local path is a direct parent of the base + // path + // We need to add enough ".." in the relative + // path + final StringBuilder sb = new StringBuilder(); + + // Count segments + int segments = 0; + for (int j = basePath.indexOf('/', i); + j != -1; + j = basePath.indexOf('/', j + 1)) segments++; + + // Build relative path + sb.append("../".repeat(Math.max(0, segments))); + + int lastLocalSlash = localPath.lastIndexOf('/'); + sb.append(localPath.substring(lastLocalSlash + 1)); + + relativePath = sb.toString(); + } + } else { + // The base path has a segment that starts like the last local path + // segment, but that is longer. Situation similar to a junction + final StringBuilder sb = new StringBuilder(); + + // Count segments + int segments = 0; + for (int j = basePath.indexOf('/', i); + j != -1; + j = basePath.indexOf('/', j + 1)) segments++; + + // Build relative path + for (int j = 0; j < segments; j++) + if (j > 0) sb.append("/.."); + else sb.append(".."); + + relativePath = sb.toString(); + + if (relativePath.isEmpty()) { + relativePath = "."; + } + } + } else if (i == basePath.length()) { + if (localPath.charAt(i) == '/') { + if ((i + 1) == localPath.length()) { + // Both paths are strictly equivalent + relativePath = "."; + } else { + // The local path is a direct child of the base + // path + relativePath = localPath.substring(i + 1); + } + } else { + if (lastSlashIndex == (i - 1)) { + // The local path is a direct subpath of the + // base path + relativePath = localPath.substring(i); + } else { + relativePath = ".." + localPath.substring(lastSlashIndex); + } + } + } + } else { + // We found a junction point, we need to add enough ".." in + // the relative path and append the rest of the local path + // the local path is a direct subpath of the base path + final StringBuilder sb = new StringBuilder(); + + // Count segments + int segments = 0; + for (int j = basePath.indexOf('/', i); + j != -1; + j = basePath.indexOf('/', j + 1)) segments++; + + // Build relative path + sb.append("../".repeat(Math.max(0, segments))); + + sb.append(localPath.substring(lastSlashIndex + 1)); + + relativePath = sb.toString(); + } + } + + // Build the result reference + result = new Reference(); + final String query = getQuery(); + final String fragment = getFragment(); + boolean modified = false; + + if ((query != null) && (!query.equals(base.getQuery()))) { + result.setQuery(query); + modified = true; + } + + if ((fragment != null) && (!fragment.equals(base.getFragment()))) { + result.setFragment(fragment); + modified = true; + } + + if (!modified || !".".equals(relativePath)) { + result.setPath(relativePath); + } + } + + return result; + } + + /** + * Returns the part of the resource identifier remaining after the base reference. Note that + * this method does not return the optional fragment. Must be used with the following + * prerequisites: + * + *

    + *
  • the reference is absolute + *
  • the reference identifier starts with the resource baseRef + *
+ * + *
+ * Note that this method does no URI decoding. + * + * @return The remaining resource parts or null if the prerequisites are not satisfied. + * @see #getRemainingPart(boolean) + */ + public String getRemainingPart() { + return getRemainingPart(false, true); + } + + /** + * Returns the optionally decoded remaining part. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded remaining part. + * @see #getRemainingPart() + */ + public String getRemainingPart(boolean decode) { + return getRemainingPart(decode, true); + } + + /** + * Returns the optionally decoded remaining part with or without the query part of the + * reference. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @param query True if the query part should be returned, false otherwise. + * @return The optionally decoded remaining part. + * @see #getRemainingPart() + */ + public String getRemainingPart(boolean decode, boolean query) { + String result = null; + final String all = toString(query, false); + + if (getBaseRef() != null) { + final String base = getBaseRef().toString(query, false); + + if ((base != null) && all.startsWith(base)) { + result = all.substring(base.length()); + } + } else { + result = all; + } + + return decode ? decode(result) : result; + } + + /** + * Returns the scheme component.
+ * Note that this method does no URI decoding. + * + * @return The scheme component. + */ + public String getScheme() { + if (hasScheme()) { + // Scheme found + return this.internalRef.substring(0, this.schemeIndex); + } + + // No scheme found + return null; + } + + /** + * Returns the optionally decoded scheme component. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded scheme component. + * @see #getScheme() + */ + public String getScheme(boolean decode) { + return decode ? decode(getScheme()) : getScheme(); + } + + /** + * Returns the protocol associated with the scheme component. + * + * @return The protocol associated with the scheme component. + */ + public Protocol getSchemeProtocol() { + return Protocol.valueOf(getScheme()); + } + + /** + * Returns the scheme-specific part.
+ * Note that no URI decoding is done by this method. + * + * @return The scheme-specific part. + */ + public String getSchemeSpecificPart() { + String result = null; + + if (hasScheme()) { + // Scheme found + if (hasFragment()) { + // Fragment found + result = this.internalRef.substring(this.schemeIndex + 1, this.fragmentIndex); + } else { + // No fragment found + result = this.internalRef.substring(this.schemeIndex + 1); + } + } + + return result; + } + + /** + * Returns the optionally decoded scheme-specific part. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded scheme-specific part. + * @see #getSchemeSpecificPart() + */ + public String getSchemeSpecificPart(boolean decode) { + return decode ? decode(getSchemeSpecificPart()) : getSchemeSpecificPart(); + } + + /** + * Returns the list of segments in a hierarchical path.
+ * A new list is created for each call.
+ * Note that this method does no URI decoding. + * + * @return The segments of a hierarchical path. + */ + public List getSegments() { + final List result = new ArrayList<>(); + final String path = getPath(); + int start = -2; // The index of the slash starting the segment + char current; + + if (path != null) { + for (int i = 0; i < path.length(); i++) { + current = path.charAt(i); + + if (current == '/') { + if (start == -2) { + // Beginning of an absolute path or sequence of two + // separators + start = i; + } else { + // End of a segment + result.add(path.substring(start + 1, i)); + start = i; + } + } else { + if (start == -2) { + // Starting a new segment for a relative path + start = -1; + } else { + // Looking for the next character + } + } + } + + if (start != -2) { + // Add the last segment + result.add(path.substring(start + 1)); + } + } + + return result; + } + + /** + * Returns the optionally decoded list of segments. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded list of segments. + * @see #getSegments() + */ + public List getSegments(boolean decode) { + final List result = getSegments(); + + if (decode) { + for (int i = 0; i < result.size(); i++) { + result.set(i, decode(result.get(i))); + } + } + + return result; + } + + /** + * Returns the target reference. This method resolves relative references against the base + * reference, then normalizes them. + * + * @return The target reference. + * @throws IllegalArgumentException If the base reference (after resolution) is not absolute. + * @throws IllegalArgumentException If the reference is relative and not base reference has been + * provided. + */ + public Reference getTargetRef() { + Reference result = null; + + // Step 1 - Resolve relative reference against their base reference + if (isRelative() && (this.baseRef != null)) { + final Reference baseReference; + + if (this.baseRef.isAbsolute()) { + baseReference = this.baseRef; + } else { + baseReference = this.baseRef.getTargetRef(); + } + + if (baseReference.isRelative()) { + throw new IllegalArgumentException( + "The base reference must have an absolute hierarchical path component"); + } + + // Relative URI detected + String authority = getAuthority(); + String path = getPath(); + String query = getQuery(); + String fragment = getFragment(); + + // Create an empty reference + result = new Reference(); + result.setScheme(baseReference.getScheme()); + + if (authority != null) { + result.setAuthority(authority); + result.setPath(path); + result.setQuery(query); + } else { + result.setAuthority(baseReference.getAuthority()); + + if ((path == null) || (path.isEmpty())) { + result.setPath(baseReference.getPath()); + + if (query != null) { + result.setQuery(query); + } else { + result.setQuery(baseReference.getQuery()); + } + } else { + if (path.startsWith("/")) { + result.setPath(path); + } else { + final String basePath = baseReference.getPath(); + final String mergedPath; + + if ((baseReference.getAuthority() != null) + && ((basePath == null) || (basePath.isEmpty()))) { + mergedPath = "/" + path; + } else { + // Remove the last segment which may be empty if + // the path is ending with a slash + final int lastSlash = basePath.lastIndexOf('/'); + if (lastSlash == -1) { + mergedPath = path; + } else { + mergedPath = basePath.substring(0, lastSlash + 1) + path; + } + } + + result.setPath(mergedPath); + } + + result.setQuery(query); + } + } + + result.setFragment(fragment); + } else if (isRelative()) { + // Relative reference with no baseRef detected + throw new IllegalArgumentException( + "Relative references are only usable when a base reference is set."); + } else { + // Absolute URI detected + result = new Reference(this.internalRef); + } + + // Step 2 - Normalize the target reference + result.normalize(); + + return result; + } + + /** + * Returns the user info component for server-based hierarchical identifiers.
+ * Note that this method does no URI decoding. + * + * @return The user info component for server-based hierarchical identifiers. + */ + public String getUserInfo() { + String result = null; + final String authority = getAuthority(); + + if (authority != null) { + final int index = authority.indexOf('@'); + + if (index != -1) { + result = authority.substring(0, index); + } + } + + return result; + } + + /** + * Returns the optionally decoded user info component. + * + * @param decode Indicates if the result should be decoded using the {@link #decode(String)} + * method. + * @return The optionally decoded user info component. + * @see #getUserInfo() + */ + public String getUserInfo(boolean decode) { + return decode ? decode(getUserInfo()) : getUserInfo(); + } + + /** + * Indicates if this reference has file-like extensions on its last path segment. + * + * @return True if there is are extensions. + * @see #getExtensions() + */ + public boolean hasExtensions() { + boolean result = false; + + // If this reference ends with a "/", it cannot be a file. + final String path = getPath(); + if (!((path != null) && path.endsWith("/"))) { + final String lastSegment = getLastSegment(); + + if (lastSegment != null) { + final int extensionsIndex = lastSegment.indexOf('.'); + final int matrixIndex = lastSegment.indexOf(';'); + result = + (extensionsIndex != -1) + && ((matrixIndex == -1) || (extensionsIndex < matrixIndex)); + } + } + + return result; + } + + /** + * Indicates if this reference has a fragment identifier. + * + * @return True if there is a fragment identifier. + */ + public boolean hasFragment() { + return (this.fragmentIndex != -1); + } + + /** + * Returns a hash code value for the object. + * + * @return A hash code value for the object. + */ + @Override + public int hashCode() { + return (this.internalRef == null) ? 0 : this.internalRef.hashCode(); + } + + /** + * Indicates if this reference has a matrix. + * + * @return True if there is a matrix. + * @see #getMatrix() + */ + public boolean hasMatrix() { + return (getLastSegment().indexOf(';') != -1); + } + + /** + * Indicates if this reference has a query component. + * + * @return True if there is a query. + */ + public boolean hasQuery() { + return (this.queryIndex != -1); + } + + /** + * Indicates if this reference has a scheme component. + * + * @return True if there is a scheme component. + */ + public boolean hasScheme() { + return (this.schemeIndex != -1); + } + + /** + * Indicates if the reference is absolute. + * + * @return True if the reference is absolute. + */ + public boolean isAbsolute() { + return (getScheme() != null); + } + + /** + * Returns true if both references are equivalent, meaning that they resolve to the same target + * reference. + * + * @param ref The reference to compare. + * @return True if both references are equivalent. + */ + public boolean isEquivalentTo(Reference ref) { + return getTargetRef().equals(ref.getTargetRef()); + } + + /** + * Indicates if the identifier is hierarchical. + * + * @return True if the identifier is hierarchical, false if it is opaque. + */ + public boolean isHierarchical() { + return isRelative() || (getSchemeSpecificPart().charAt(0) == '/'); + } + + /** + * Indicates if the identifier is opaque. + * + * @return True if the identifier is opaque, false if it is hierarchical. + */ + public boolean isOpaque() { + return isAbsolute() && (getSchemeSpecificPart().charAt(0) != '/'); + } + + /** + * Indicates if the reference is a parent of the hierarchical child reference. + * + * @param childRef The hierarchical reference. + * @return True if the reference is a parent of the hierarchical child reference. + */ + public boolean isParent(Reference childRef) { + boolean result = false; + + if ((childRef != null) && (childRef.isHierarchical())) { + result = childRef.toString(false, false).startsWith(toString(false, false)); + } + + return result; + } + + /** + * Indicates if the reference is relative. + * + * @return True if the reference is relative. + */ + public boolean isRelative() { + return (getScheme() == null); + } + + /** + * Normalizes the reference. Useful before comparison between references or when building a + * target reference from a base and a relative reference. + * + * @return The current reference. + */ + public Reference normalize() { + // 1. The input buffer is initialized with the now-appended path + // components, and the output buffer is initialized to the empty string. + StringBuilder output = new StringBuilder(); + StringBuilder input = new StringBuilder(); + String path = getPath(); + + if (path != null) { + input.append(path); + } + + // 2. While the input buffer is not empty, the loop is as follows: + while (!input.isEmpty()) { + // A. If the input buffer begins with a prefix of "../" or "./", + // then remove that prefix from the input buffer. + if ((input.length() >= 3) && input.substring(0, 3).equals("../")) { + input.delete(0, 3); + } else if ((input.length() >= 2) && input.substring(0, 2).equals("./")) { + input.delete(0, 2); + } + + // B. if the input buffer begins with a prefix of "/./" or "/.", + // where "." is a complete path segment, then replace that + // prefix with "/" in the input buffer; otherwise, + else if ((input.length() >= 3) && input.substring(0, 3).equals("/./")) { + input.delete(0, 2); + } else if ((input.length() == 2) && input.substring(0, 2).equals("/.")) { + input.delete(1, 2); + } + + // C. if the input buffer begins with a prefix of "/../" or "/..", + // where ".." is a complete path segment, then replace that prefix + // with "/" in the input buffer and remove the last segment and its + // preceding "/" (if any) from the output buffer; otherwise, + else if ((input.length() >= 4) && input.substring(0, 4).equals("/../")) { + input.delete(0, 3); + removeLastSegment(output); + } else if ((input.length() == 3) && input.substring(0, 3).equals("/..")) { + input.delete(1, 3); + removeLastSegment(output); + } + + // D. if the input buffer consists only of "." or "..", then remove + // that from the input buffer; otherwise, + else if ((input.length() == 1) && input.substring(0, 1).equals(".")) { + input.delete(0, 1); + } else if ((input.length() == 2) && input.substring(0, 2).equals("..")) { + input.delete(0, 2); + } + + // E. move the first path segment in the input buffer to the end of + // the output buffer, including the initial "/" character (if any) + // and any later characters up to, but not including, the next + // "/" character or the end of the input buffer. + else { + int max = -1; + for (int i = 1; (max == -1) && (i < input.length()); i++) { + if (input.charAt(i) == '/') { + max = i; + } + } + + if (max != -1) { + // We found the next "/" character. + output.append(input, 0, max); + input.delete(0, max); + } else { + // End of input buffer reached + output.append(input); + input.delete(0, input.length()); + } + } + } + + // Finally, the output buffer is returned as the result + setPath(output.toString()); + + // Ensure that the scheme and host names are reset in lower case + setScheme(getScheme()); + setHostDomain(getHostDomain()); + + // Remove the port if it is equal to the default port of the reference's + // Protocol. + final int hostPort = getHostPort(); + if (hostPort != -1) { + final int defaultPort = Protocol.valueOf(getScheme()).getDefaultPort(); + if (hostPort == defaultPort) { + setHostPort(null); + } + } + + return this; + } + + /** + * Removes the last segment from the output builder. + * + * @param output The output builder to update. + */ + private void removeLastSegment(StringBuilder output) { + int min = -1; + for (int i = output.length() - 1; (min == -1) && (i >= 0); i--) { + if (output.charAt(i) == '/') { + min = i; + } + } + + if (min != -1) { + // We found the previous "/" character. + output.delete(min, output.length()); + } else { + // End of output buffer reached + output.delete(0, output.length()); + } + } + + /** + * Sets the authority component for hierarchical identifiers. + * + * @param authority The authority component for hierarchical identifiers. + */ + public void setAuthority(String authority) { + final String oldPart = isRelative() ? getRelativePart() : getSchemeSpecificPart(); + String newPart; + final String newAuthority = (authority == null) ? "" : "//" + authority; + + if (oldPart == null) { + newPart = newAuthority; + } else if (oldPart.startsWith("//")) { + int index = oldPart.indexOf('/', 2); + + if (index != -1) { + newPart = newAuthority + oldPart.substring(index); + } else { + index = oldPart.indexOf('?'); + if (index != -1) { + newPart = newAuthority + oldPart.substring(index); + } else { + newPart = newAuthority; + } + } + } else { + newPart = newAuthority + oldPart; + } + + if (isAbsolute()) { + setSchemeSpecificPart(newPart); + } else { + setRelativePart(newPart); + } + } + + /** + * Sets the base reference for relative references. + * + * @param baseRef The base reference for relative references. + */ + public void setBaseRef(Reference baseRef) { + this.baseRef = baseRef; + } + + /** + * Sets the base reference for relative references. + * + * @param baseUri The base URI for relative references. + */ + public void setBaseRef(String baseUri) { + setBaseRef(new Reference(baseUri)); + } + + /** + * Sets the extensions for hierarchical identifiers. An extensions part starts after the first + * '.' character of the last path segment and ends with either the end of the segment of with + * the first ';' character (matrix start). It is a token similar to file extensions separated by + * '.' characters. The value can be omitted.
+ * Note that this method does no URI decoding. + * + * @param extensions The extensions to set or null (without leading or trailing dots). + * @see #getExtensions() + * @see #getExtensionsAsArray() + * @see #setExtensions(String[]) + */ + public void setExtensions(String extensions) { + final String lastSegment = getLastSegment(); + + if (lastSegment != null) { + final int extensionIndex = lastSegment.indexOf('.'); + final int matrixIndex = lastSegment.indexOf(';'); + final StringBuilder sb = new StringBuilder(); + + if (extensionIndex != -1) { + // Extensions found + sb.append(lastSegment, 0, extensionIndex); + + if ((extensions != null) && (!extensions.isEmpty())) { + sb.append('.').append(extensions); + } + + if (matrixIndex != -1) { + sb.append(lastSegment.substring(matrixIndex)); + } + } else { + // Extensions not found + if ((extensions != null) && (!extensions.isEmpty())) { + if (matrixIndex != -1) { + // Matrix found, make sure we append it + // after the extensions + sb.append(lastSegment, 0, matrixIndex) + .append('.') + .append(extensions) + .append(lastSegment.substring(matrixIndex)); + } else { + // No matrix found, just append the extensions + sb.append(lastSegment).append('.').append(extensions); + } + } else { + // No change necessary + sb.append(lastSegment); + } + } + + // Finally update the last segment + setLastSegment(sb.toString()); + } else { + setLastSegment('.' + extensions); + } + } + + /** + * Sets the extensions based on an array of extension tokens (without dots). + * + * @param extensions The array of extensions. + * @see #getExtensions() + * @see #getExtensionsAsArray() + * @see #setExtensions(String) + */ + public void setExtensions(String[] extensions) { + String exts = null; + + if (extensions != null) { + final StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < extensions.length; i++) { + if (i > 0) { + sb.append('.'); + } + + sb.append(extensions[i]); + } + + exts = sb.toString(); + } + + setExtensions(exts); + } + + /** + * Sets the fragment identifier. + * + * @param fragment The fragment identifier. + * @throws IllegalArgumentException if the fragment parameter contains the fragment delimiter + * ('#'). + */ + public void setFragment(String fragment) { + fragment = encodeInvalidCharacters(fragment); + + if ((fragment != null) && (fragment.indexOf('#') != -1)) { + throw new IllegalArgumentException("Illegal '#' character detected in parameter"); + } + + if (hasFragment()) { + // Existing fragment + if (fragment != null) { + this.internalRef = this.internalRef.substring(0, this.fragmentIndex + 1) + fragment; + } else { + this.internalRef = this.internalRef.substring(0, this.fragmentIndex); + } + } else { + // No existing fragment + if (fragment != null) { + if (this.internalRef != null) { + this.internalRef = this.internalRef + '#' + fragment; + } else { + this.internalRef = '#' + fragment; + } + } else { + // Do nothing + } + } + + updateIndexes(); + } + + /** + * Sets the host domain component for server-based hierarchical identifiers. + * + * @param domain The host component for server-based hierarchical identifiers. + */ + public void setHostDomain(String domain) { + final String authority = getAuthority(); + + if (authority == null) { + setAuthority(domain); + } else { + if (domain == null) { + domain = ""; + } else { + // URI specification indicates that host names should be + // produced in lower case + domain = domain.toLowerCase(); + } + + // We must prevent the case where the userinfo part contains ':' + // and the case of IPV6 addresses + int indexUI = authority.indexOf('@'); // user info + int indexIPV6 = authority.indexOf(']'); // IPV6 + int indexP = authority.indexOf(':', (indexIPV6 == -1) ? indexUI : indexIPV6); + + if (indexUI != -1) { + // User info found + if (indexP != -1) { + // Port found + setAuthority( + authority.substring(0, indexUI + 1) + + domain + + authority.substring(indexP)); + } else { + // No port found + setAuthority(authority.substring(0, indexUI + 1) + domain); + } + } else { + // No user info found + if (indexP != -1) { + // Port found + setAuthority(domain + authority.substring(indexP)); + } else { + // No port found + setAuthority(domain); + } + } + } + } + + /** + * Sets the optional port number for server-based hierarchical identifiers. + * + * @param port The optional port number for server-based hierarchical identifiers. + * @throws IllegalArgumentException If the authority has not been defined. + */ + public void setHostPort(Integer port) { + final String authority = getAuthority(); + + if (authority != null) { + // We must prevent the case where the userinfo part contains ':' + // and the case of IPV6 addresses + int indexUI = authority.indexOf('@'); // user info + int indexIPV6 = authority.indexOf(']'); // IPV6 + int index = authority.indexOf(':', (indexIPV6 == -1) ? indexUI : indexIPV6); + String newPort = (port == null) ? "" : ":" + port; + + if (index != -1) { + setAuthority(authority.substring(0, index) + newPort); + } else { + setAuthority(authority + newPort); + } + } else { + throw new IllegalArgumentException( + "No authority defined, please define a host name first"); + } + } + + /** + * Sets the absolute resource identifier. + * + * @param identifier The absolute resource identifier. + * @throws IllegalArgumentException If the identifier parameter contains the fragment delimiter + * ('#'). + */ + public void setIdentifier(String identifier) { + identifier = encodeInvalidCharacters(identifier); + + if (identifier == null) { + identifier = ""; + } + + if (identifier.indexOf('#') != -1) { + throw new IllegalArgumentException("Illegal '#' character detected in parameter"); + } + + if (hasFragment()) { + // Fragment found + this.internalRef = identifier + this.internalRef.substring(this.fragmentIndex); + } else { + // No fragment found + this.internalRef = identifier; + } + + updateIndexes(); + } + + /** + * Sets the last segment of the path. If no path is available, then it creates one and adds a + * slash in front of the given last segment.
+ * Note that this method does no URI decoding. + * + * @param lastSegment The last segment of a hierarchical path. + */ + public void setLastSegment(String lastSegment) { + String path = getPath(); + + if (path != null) { + int lastSlashIndex = path.lastIndexOf('/'); + + if (lastSlashIndex != -1) { + setPath(path.substring(0, lastSlashIndex + 1) + lastSegment); + return; + } + } + + setPath('/' + lastSegment); + } + + /** + * Sets the path component for hierarchical identifiers. + * + * @param path The path component for hierarchical identifiers. + */ + public void setPath(String path) { + final String oldPart = isRelative() ? getRelativePart() : getSchemeSpecificPart(); + String newPart = null; + + if (oldPart != null) { + if (path == null) { + path = ""; + } + + if (oldPart.startsWith("//")) { + // Authority found + final int index1 = oldPart.indexOf('/', 2); + + if (index1 != -1) { + // Path found + final int index2 = oldPart.indexOf('?'); + + if (index2 != -1) { + // Query found + newPart = oldPart.substring(0, index1) + path + oldPart.substring(index2); + } else { + // No query found + newPart = oldPart.substring(0, index1) + path; + } + } else { + // No path found + final int index2 = oldPart.indexOf('?'); + + if (index2 != -1) { + // Query found + newPart = oldPart.substring(0, index2) + path + oldPart.substring(index2); + } else { + // No query found + newPart = oldPart + path; + } + } + } else { + // No authority found + final int index = oldPart.indexOf('?'); + + if (index != -1) { + // Query found + newPart = path + oldPart.substring(index); + } else { + // No query found + newPart = path; + } + } + } else { + newPart = path; + } + + if (isAbsolute()) { + setSchemeSpecificPart(newPart); + } else { + setRelativePart(newPart); + } + } + + /** + * Sets the scheme component based on this protocol. + * + * @param protocol The protocol of the scheme component. + */ + public void setProtocol(Protocol protocol) { + setScheme(protocol.getSchemeName()); + } + + /** + * Sets the query component for hierarchical identifiers. + * + * @param query The query component for hierarchical identifiers. + */ + public void setQuery(String query) { + query = encodeInvalidCharacters(query); + final boolean emptyQueryString = ((query == null) || query.isEmpty()); + + if (hasQuery()) { + // Query found + if (hasFragment()) { + // Fragment found + if (!emptyQueryString) { + this.internalRef = + this.internalRef.substring(0, this.queryIndex + 1) + + query + + this.internalRef.substring(this.fragmentIndex); + } else { + this.internalRef = + this.internalRef.substring(0, this.queryIndex) + + this.internalRef.substring(this.fragmentIndex); + } + } else { + // No fragment found + if (!emptyQueryString) { + this.internalRef = this.internalRef.substring(0, this.queryIndex + 1) + query; + } else { + this.internalRef = this.internalRef.substring(0, this.queryIndex); + } + } + } else { + // No query found + if (hasFragment()) { + // Fragment found + if (!emptyQueryString) { + this.internalRef = + this.internalRef.substring(0, this.fragmentIndex) + + '?' + + query + + this.internalRef.substring(this.fragmentIndex); + } else { + // Do nothing; + } + } else { + // No fragment found + if (!emptyQueryString) { + if (this.internalRef != null) { + this.internalRef = this.internalRef + '?' + query; + } else { + this.internalRef = '?' + query; + } + } else { + // Do nothing; + } + } + } + + updateIndexes(); + } + + /** + * Sets the relative part for relative references only. + * + * @param relativePart The relative part to set. + */ + public void setRelativePart(String relativePart) { + relativePart = encodeInvalidCharacters(relativePart); + + if (relativePart == null) { + relativePart = ""; + } + + if (!hasScheme()) { + // This is a relative reference, no scheme found + if (hasQuery()) { + // Query found + this.internalRef = relativePart + this.internalRef.substring(this.queryIndex); + } else if (hasFragment()) { + // Fragment found + this.internalRef = relativePart + this.internalRef.substring(this.fragmentIndex); + } else { + // No fragment found + this.internalRef = relativePart; + } + } + + updateIndexes(); + } + + /** + * Sets the scheme component. + * + * @param scheme The scheme component. + */ + public void setScheme(String scheme) { + scheme = encodeInvalidCharacters(scheme); + + if (scheme != null) { + // URI specification indicates that scheme names should be + // produced in the lower case + scheme = scheme.toLowerCase(); + } + + if (hasScheme()) { + // Scheme found + if (scheme != null) { + this.internalRef = scheme + this.internalRef.substring(this.schemeIndex); + } else { + this.internalRef = this.internalRef.substring(this.schemeIndex + 1); + } + } else { + // No scheme found + if (scheme != null) { + if (this.internalRef == null) { + this.internalRef = scheme + ':'; + } else { + this.internalRef = scheme + ':' + this.internalRef; + } + } + } + + updateIndexes(); + } + + /** + * Sets the scheme-specific part. + * + * @param schemeSpecificPart The scheme-specific part. + */ + public void setSchemeSpecificPart(String schemeSpecificPart) { + schemeSpecificPart = encodeInvalidCharacters(schemeSpecificPart); + + if (schemeSpecificPart == null) { + schemeSpecificPart = ""; + } + + if (hasScheme()) { + // Scheme found + if (hasFragment()) { + // Fragment found + this.internalRef = + this.internalRef.substring(0, this.schemeIndex + 1) + + schemeSpecificPart + + this.internalRef.substring(this.fragmentIndex); + } else { + // No fragment found + this.internalRef = + this.internalRef.substring(0, this.schemeIndex + 1) + schemeSpecificPart; + } + } else { + // No scheme found + if (hasFragment()) { + // Fragment found + this.internalRef = + schemeSpecificPart + this.internalRef.substring(this.fragmentIndex); + } else { + // No fragment found + this.internalRef = schemeSpecificPart; + } + } + + updateIndexes(); + } + + /** + * Sets the segments of a hierarchical path.
+ * A new absolute path will replace any existing one. + * + * @param segments The segments of the hierarchical path. + */ + public void setSegments(List segments) { + final StringBuilder sb = new StringBuilder(); + + for (final String segment : segments) { + sb.append('/').append(segment); + } + + setPath(sb.toString()); + } + + /** + * Sets the user info component for server-based hierarchical identifiers. + * + * @param userInfo The user info component for server-based hierarchical identifiers. + * @throws IllegalArgumentException If the authority part has not been defined. + */ + public void setUserInfo(String userInfo) { + final String authority = getAuthority(); + + if (authority != null) { + final int index = authority.indexOf('@'); + final String newUserInfo = (userInfo == null) ? "" : userInfo + '@'; + + if (index != -1) { + setAuthority(newUserInfo + authority.substring(index + 1)); + } else { + setAuthority(newUserInfo + authority); + } + } else { + throw new IllegalArgumentException( + "No authority defined, please define a host name first"); + } + } + + /** + * Returns the reference as a URI string. + * + * @return The reference as a URI string. + */ + @Override + public String toString() { + return this.internalRef; + } + + /** + * Returns the URI reference string. + * + * @param query Indicates if the query should be included; + * @param fragment Indicates if the fragment should be included; + * @return The URI reference string. + */ + public String toString(boolean query, boolean fragment) { + if (query) { + if (fragment) { + return this.internalRef; + } + + if (hasFragment()) { + return this.internalRef.substring(0, this.fragmentIndex); + } + return this.internalRef; + } + + if (fragment) { + // Fragment should be included + if (hasQuery()) { + // Query found + if (hasFragment()) { + // Fragment found + return this.internalRef.substring(0, this.queryIndex) + "#" + getFragment(); + } + + // No fragment found + return this.internalRef.substring(0, this.queryIndex); + } + + // No query found + return this.internalRef; + } + + // Fragment should not be included + if (hasQuery()) { + // Query found + return this.internalRef.substring(0, this.queryIndex); + } + if (hasFragment()) { + // Fragment found + return this.internalRef.substring(0, this.fragmentIndex); + } + + return this.internalRef; + } + + /** + * Converts to a {@link java.net.URI} instance. Note that relative references are resolved + * before conversion using the {@link #getTargetRef()} method. + * + * @return A {@link java.net.URI} instance. + */ + public java.net.URI toUri() { + return java.net.URI.create(getTargetRef().toString()); + } + + /** + * Converts to a {@link java.net.URL} instance. Note that relative references are resolved + * before conversion using the {@link #getTargetRef()} method. + * + * @return A {@link java.net.URL} instance. + */ + public java.net.URL toUrl() { + java.net.URL result = null; + + try { + result = toUri().toURL(); + } catch (java.net.MalformedURLException e) { + throw new IllegalArgumentException("Malformed URL exception", e); + } + + return result; + } + + /** Updates internal indexes. */ + private void updateIndexes() { + if (this.internalRef != null) { + // Compute the indexes + final int firstSlashIndex = this.internalRef.indexOf('/'); + this.schemeIndex = this.internalRef.indexOf(':'); + + if ((firstSlashIndex != -1) && (this.schemeIndex > firstSlashIndex)) { + // We are in the rare case of a relative reference where one of + // the path segments contains a colon character. In this case, + // we ignore the colon as a valid scheme index. + // Note that this colon can't be in the first segment as it is + // forbidden by the URI RFC. + this.schemeIndex = -1; + } + + this.queryIndex = this.internalRef.indexOf('?'); + this.fragmentIndex = this.internalRef.indexOf('#'); + + if (hasQuery() && hasFragment() && (this.queryIndex > this.fragmentIndex)) { + // Query sign inside the fragment + this.queryIndex = -1; + } + + if (hasQuery() && this.schemeIndex > this.queryIndex) { + // Colon sign inside the query + this.schemeIndex = -1; + } + + if (hasFragment() && this.schemeIndex > this.fragmentIndex) { + // Colon sign inside fragment + this.schemeIndex = -1; + } + } else { + this.schemeIndex = -1; + this.queryIndex = -1; + this.fragmentIndex = -1; + } + } } diff --git a/org.restlet/src/main/java/org/restlet/data/ReferenceList.java b/org.restlet/src/main/java/org/restlet/data/ReferenceList.java index 4aae2679ce..55896cacf7 100644 --- a/org.restlet/src/main/java/org/restlet/data/ReferenceList.java +++ b/org.restlet/src/main/java/org/restlet/data/ReferenceList.java @@ -1,65 +1,62 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.restlet.engine.io.IoUtils; -import org.restlet.representation.Representation; -import org.restlet.representation.StringRepresentation; -import org.restlet.util.WrapperList; - import java.io.BufferedReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import org.restlet.engine.io.IoUtils; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.util.WrapperList; /** * List of URI references. - * + * * @author Jerome Louvel */ public class ReferenceList extends WrapperList { - /** The list's identifier. */ - private volatile Reference identifier; - - /** - * Constructor. - */ - public ReferenceList() { - super(); - } - - /** - * Constructor. - * - * @param initialCapacity The initial list capacity. - */ - public ReferenceList(int initialCapacity) { - super(new ArrayList(initialCapacity)); - } - - /** - * Constructor. - * - * @param delegate The delegate list. - */ - public ReferenceList(List delegate) { - super(delegate); - } - - /** - * Constructor from a "text/uri-list" representation. - * - * @param uriList The "text/uri-list" representation to parse. - * @throws IOException - */ - public ReferenceList(Representation uriList) throws IOException { + /** The list's identifier. */ + private volatile Reference identifier; + + /** Constructor. */ + public ReferenceList() { + super(); + } + + /** + * Constructor. + * + * @param initialCapacity The initial list capacity. + */ + public ReferenceList(int initialCapacity) { + super(new ArrayList<>(initialCapacity)); + } + + /** + * Constructor. + * + * @param delegate The delegate list. + */ + public ReferenceList(List delegate) { + super(delegate); + } + + /** + * Constructor from a "text/uri-list" representation. + * + * @param uriList The "text/uri-list" representation to parse. + * @throws IOException + */ + public ReferenceList(Representation uriList) throws IOException { try (BufferedReader br = new BufferedReader(uriList.getReader(), IoUtils.BUFFER_SIZE)) { String line = br.readLine(); @@ -78,104 +75,120 @@ public ReferenceList(Representation uriList) throws IOException { line = br.readLine(); } } - } - - /** - * Creates then adds a reference at the end of the list. - * - * @param uri The uri of the reference to add. - * @return True (as per the general contract of the Collection.add method). - */ - public boolean add(String uri) { - return add(new Reference(uri)); - } - - /** - * Returns the list identifier. - * - * @return The list identifier. - */ - public Reference getIdentifier() { - return this.identifier; - } - - /** - * Returns a representation of the list in the "text/uri-list" format. - * - * @return A representation of the list in the "text/uri-list" format. - */ - public Representation getTextRepresentation() { - final StringBuilder sb = new StringBuilder(); - - if (getIdentifier() != null) { - sb.append("# ").append(getIdentifier().toString()).append("\r\n"); - } - - for (final Reference ref : this) { - sb.append(ref.toString()).append("\r\n"); - } - - return new StringRepresentation(sb.toString(), MediaType.TEXT_URI_LIST); - } - - /** - * Returns a representation of the list in "text/html" format. - * - * @return A representation of the list in "text/html" format. - */ - public Representation getWebRepresentation() { - // Create a simple HTML list - final StringBuilder sb = new StringBuilder(); - sb.append("\n"); - - if (getIdentifier() != null) { - sb.append("

Listing of \"").append(getIdentifier().getPath()).append("\"

\n"); - final Reference parentRef = getIdentifier().getParentRef(); - - if (!parentRef.equals(getIdentifier())) { - sb.append("..
\n"); - } - } else { - sb.append("

List of references

\n"); - } - - for (final Reference ref : this) { - sb.append("").append(ref.getRelativeRef(getIdentifier())).append("
\n"); - } - sb.append("\n"); - - return new StringRepresentation(sb.toString(), MediaType.TEXT_HTML); - } - - /** - * Sets the list reference. - * - * @param identifier The list identifier. - */ - public void setIdentifier(Reference identifier) { - this.identifier = identifier; - } - - /** - * Sets the list reference. - * - * @param identifier The list identifier as a URI. - */ - public void setIdentifier(String identifier) { - setIdentifier(new Reference(identifier)); - } - - /** - * Returns a view of the portion of this list between the specified fromIndex, - * inclusive, and toIndex, exclusive. - * - * @param fromIndex The start position. - * @param toIndex The end position (exclusive). - * @return The sub-list. - */ - @Override - public ReferenceList subList(int fromIndex, int toIndex) { - return new ReferenceList(getDelegate().subList(fromIndex, toIndex)); - } + } + + /** + * Creates then adds a reference at the end of the list. + * + * @param uri The uri of the reference to add. + * @return True (as per the general contract of the Collection.add method). + */ + public boolean add(String uri) { + return add(new Reference(uri)); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object obj) { + return super.equals(obj) + && Objects.equals(getIdentifier(), ((ReferenceList) obj).getIdentifier()); + } + + /** + * Returns the list identifier. + * + * @return The list identifier. + */ + public Reference getIdentifier() { + return this.identifier; + } + + /** + * Returns a representation of the list in the "text/uri-list" format. + * + * @return A representation of the list in the "text/uri-list" format. + */ + public Representation getTextRepresentation() { + final StringBuilder sb = new StringBuilder(); + + if (getIdentifier() != null) { + sb.append("# ").append(getIdentifier().toString()).append("\r\n"); + } + + for (final Reference ref : this) { + sb.append(ref.toString()).append("\r\n"); + } + + return new StringRepresentation(sb.toString(), MediaType.TEXT_URI_LIST); + } + + /** + * Returns a representation of the list in "text/html" format. + * + * @return A representation of the list in "text/html" format. + */ + public Representation getWebRepresentation() { + // Create a simple HTML list + final StringBuilder sb = new StringBuilder(); + sb.append("\n"); + + if (getIdentifier() != null) { + sb.append("

Listing of \"").append(getIdentifier().getPath()).append("\"

\n"); + final Reference parentRef = getIdentifier().getParentRef(); + + if (!parentRef.equals(getIdentifier())) { + sb.append("..
\n"); + } + } else { + sb.append("

List of references

\n"); + } + for (final Reference ref : this) { + sb.append("") + .append(ref.getRelativeRef(getIdentifier())) + .append("
\n"); + } + sb.append("\n"); + + return new StringRepresentation(sb.toString(), MediaType.TEXT_HTML); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return super.hashCode(); + } + + /** + * Sets the list reference. + * + * @param identifier The list identifier. + */ + public void setIdentifier(Reference identifier) { + this.identifier = identifier; + } + + /** + * Sets the list reference. + * + * @param identifier The list identifier as a URI. + */ + public void setIdentifier(String identifier) { + setIdentifier(new Reference(identifier)); + } + + /** + * Returns a view of the portion of this list between the specified fromIndex, inclusive, and + * toIndex, exclusive. + * + * @param fromIndex The start position. + * @param toIndex The end position (exclusive). + * @return The sub-list. + */ + @Override + public ReferenceList subList(int fromIndex, int toIndex) { + return new ReferenceList(getDelegate().subList(fromIndex, toIndex)); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/ServerInfo.java b/org.restlet/src/main/java/org/restlet/data/ServerInfo.java index cb08823bcd..75264de959 100644 --- a/org.restlet/src/main/java/org/restlet/data/ServerInfo.java +++ b/org.restlet/src/main/java/org/restlet/data/ServerInfo.java @@ -1,120 +1,112 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; /** * Server specific data related to a call. - * + * * @author Jerome Louvel */ public final class ServerInfo { - /** Indicates if the server accepts range requests for a resource. */ - private volatile boolean acceptingRanges; - - /** The IP address. */ - private volatile String address; + /** Indicates if the server accepts range requests for a resource. */ + private volatile boolean acceptingRanges; - /** The agent name. */ - private volatile String agent; + /** The IP address. */ + private volatile String address; - /** The port number. */ - private volatile int port; + /** The agent name. */ + private volatile String agent; - /** - * Constructor. - */ - public ServerInfo() { - this.address = null; - this.agent = null; - this.port = -1; - this.acceptingRanges = false; - } + /** The port number. */ + private volatile int port; - /** - * Returns the IP address. - * - * @return The IP address. - */ - public String getAddress() { - return this.address; - } + /** Constructor. */ + public ServerInfo() { + this.address = null; + this.agent = null; + this.port = -1; + this.acceptingRanges = false; + } - /** - * Returns the agent name (ex: "Restlet-Framework/2.0"). Note that when used - * with HTTP connectors, this property maps to the "Server" header. - * - * @return The agent name. - */ - public String getAgent() { - return this.agent; - } + /** + * Returns the IP address. + * + * @return The IP address. + */ + public String getAddress() { + return this.address; + } - /** - * Returns the port number which received the call. If no port is specified, -1 - * is returned. - * - * @return The port number which received the call. - */ - public int getPort() { - return this.port; - } + /** + * Returns the agent name (ex: "Restlet-Framework/2.0"). Note that when used with HTTP + * connectors, this property maps to the "Server" header. + * + * @return The agent name. + */ + public String getAgent() { + return this.agent; + } - /** - * Return true if the server accepts range requests for a resource, with the - * "byte" range unit. Note that when used with HTTP connectors, this property - * maps to the "Accept-Ranges" header. - * - * @return True if the server accepts range requests for a resource. - */ - public boolean isAcceptingRanges() { - return acceptingRanges; - } + /** + * Returns the port number which received the call. If no port is specified, -1 is returned. + * + * @return The port number which received the call. + */ + public int getPort() { + return this.port; + } - /** - * Indicates if the server accepts range requests for a resource, with the - * "byte" range unit. Note that when used with HTTP connectors, this property - * maps to the "Accept-Ranges" header. - * - * @param acceptingRanges True if the server accepts range requests for a - * resource. - */ - public void setAcceptingRanges(boolean acceptingRanges) { - this.acceptingRanges = acceptingRanges; - } + /** + * Return true if the server accepts range requests for a resource, with the "byte" range unit. + * Note that when used with HTTP connectors, this property maps to the "Accept-Ranges" header. + * + * @return True if the server accepts range requests for a resource. + */ + public boolean isAcceptingRanges() { + return acceptingRanges; + } - /** - * Sets the IP address which received the call. - * - * @param address The IP address which received the call. - */ - public void setAddress(String address) { - this.address = address; - } + /** + * Indicates if the server accepts range requests for a resource, with the "byte" range unit. + * Note that when used with HTTP connectors, this property maps to the "Accept-Ranges" header. + * + * @param acceptingRanges True if the server accepts range requests for a resource. + */ + public void setAcceptingRanges(boolean acceptingRanges) { + this.acceptingRanges = acceptingRanges; + } - /** - * Sets the agent name (ex: "Restlet-Framework/2.0"). Note that when used with - * HTTP connectors, this property maps to the "Server" header. - * - * @param agent The agent name. - */ - public void setAgent(String agent) { - this.agent = agent; - } + /** + * Sets the IP address which received the call. + * + * @param address The IP address which received the call. + */ + public void setAddress(String address) { + this.address = address; + } - /** - * Sets the port number which received the call. - * - * @param port The port number which received the call. - */ - public void setPort(int port) { - this.port = port; - } + /** + * Sets the agent name (ex: "Restlet-Framework/2.0"). Note that when used with HTTP connectors, + * this property maps to the "Server" header. + * + * @param agent The agent name. + */ + public void setAgent(String agent) { + this.agent = agent; + } + /** + * Sets the port number which received the call. + * + * @param port The port number which received the call. + */ + public void setPort(int port) { + this.port = port; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Status.java b/org.restlet/src/main/java/org/restlet/data/Status.java index afdb361208..3347dd8198 100644 --- a/org.restlet/src/main/java/org/restlet/data/Status.java +++ b/org.restlet/src/main/java/org/restlet/data/Status.java @@ -1,647 +1,585 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; import org.restlet.engine.Engine; /** * Status to return after handling a call. - * + * * @author Jerome Louvel */ public final class Status { - private static final String BASE_ADDED_HTTP = "http://tools.ietf.org/html/rfc6585"; - - private static final String BASE_HTTP = "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html"; - - private static final String BASE_RESTLET = "https://javadoc.io/static/org.restlet/org.restlet/" + Engine.VERSION + "/"; - - /** - * The server could not understand the request due to malformed syntax. - * - * @see HTTP - * RFC - 10.4.1 400 Bad Request - */ - public static final Status CLIENT_ERROR_BAD_REQUEST = new Status(400); - - /** - * The request could not be completed due to a conflict with the current state - * of the resource (as experienced in a version control system). - * - * @see HTTP - * RFC - 10.4.10 409 Conflict - */ - public static final Status CLIENT_ERROR_CONFLICT = new Status(409); - - /** - * The user agent expects some behavior of the server (given in an Expect - * request-header field), but this expectation could not be met by this server. - * - * @see HTTP - * RFC - 10.4.18 417 Expectation Failed - */ - public static final Status CLIENT_ERROR_EXPECTATION_FAILED = new Status(417); - - /** - * The server understood the request, but is refusing to fulfill it as it could - * be explained in the entity. - * - * @see HTTP - * RFC - 10.4.4 403 Forbidden - */ - public static final Status CLIENT_ERROR_FORBIDDEN = new Status(403); - - /** - * The requested resource is no longer available at the server and no forwarding - * address is known. - * - * @see HTTP - * RFC - 10.4.11 410 Gone - */ - public static final Status CLIENT_ERROR_GONE = new Status(410); - - /** - * The server refuses to accept the request without a defined Content-Length. - * - * @see HTTP - * RFC - 10.4.12 411 Length Required - */ - public static final Status CLIENT_ERROR_LENGTH_REQUIRED = new Status(411); - - /** - * The method specified in the Request-Line is not allowed for the resource - * identified by the Request-URI. - * - * @see HTTP - * RFC - 10.4.6 405 Method Not Allowed - */ - public static final Status CLIENT_ERROR_METHOD_NOT_ALLOWED = new Status(405); - - /** - * The resource identified by the request is only capable of generating response - * entities whose content characteristics do not match the user's requirements - * (in Accept* headers). - * - * @see HTTP - * RFC - 10.4.7 406 Not Acceptable - */ - public static final Status CLIENT_ERROR_NOT_ACCEPTABLE = new Status(406); - - /** - * The server has not found anything matching the Request-URI or the server does - * not wish to reveal exactly why the request has been refused, or no other - * response is applicable. - * - * @see HTTP - * RFC - 10.4.5 404 Not Found - */ - public static final Status CLIENT_ERROR_NOT_FOUND = new Status(404); - - /** - * This code is reserved for future use. - * - * @see HTTP - * RFC - 10.4.3 402 Payment Required - */ - public static final Status CLIENT_ERROR_PAYMENT_REQUIRED = new Status(402); - - /** - * Sent by the server when the user agent asks the server to carry out a request - * under certain conditions that are not met. - * - * @see HTTP - * RFC - 10.4.13 412 Precondition Failed - */ - public static final Status CLIENT_ERROR_PRECONDITION_FAILED = new Status(412); - - /** - * This code is similar to 401 (Unauthorized), but indicates that the client - * must first authenticate itself with the proxy. - * - * @see HTTP - * RFC - 10.4.8 407 Proxy Authentication Required - */ - public static final Status CLIENT_ERROR_PROXY_AUTHENTIFICATION_REQUIRED = new Status(407); - - /** - * The server is refusing to process a request because the request entity is - * larger than the server is willing or able to process. - * - * @see HTTP - * RFC - 10.4.14 413 Request Entity Too Large - */ - public static final Status CLIENT_ERROR_REQUEST_ENTITY_TOO_LARGE = new Status(413); - - /** - * Sent by the server when an HTTP client opens a connection, but has never sent - * a request (or never sent the blank line that signals the end of the request). - * - * @see HTTP - * RFC - 10.4.9 408 Request Timeout - */ - public static final Status CLIENT_ERROR_REQUEST_TIMEOUT = new Status(408); - - /** - * The server is refusing to service the request because the Request-URI is - * longer than the server is willing to interpret. - * - * @see HTTP - * RFC - 10.4.15 414 Request-URI Too Long - */ - public static final Status CLIENT_ERROR_REQUEST_URI_TOO_LONG = new Status(414); - - /** - * The request includes a Range request-header field and the selected resource - * is too small for any of the byte-ranges to apply. - * - * @see HTTP - * RFC - 10.4.17 416 Requested Range Not Satisfiable - */ - public static final Status CLIENT_ERROR_REQUESTED_RANGE_NOT_SATISFIABLE = new Status(416); - - /** - * The server refuses to accept the request because the user has sent too many - * requests in a given amount of time. - * - * @see HTTP RFC - - * 10.4.12 429 Too Many Requests - */ - public static final Status CLIENT_ERROR_TOO_MANY_REQUESTS = new Status(429); - - /** - * The request requires user authentication. - * - * @see HTTP - * RFC - 10.4.2 401 Unauthorized - */ - public static final Status CLIENT_ERROR_UNAUTHORIZED = new Status(401); - - /** - * The server is refusing to service the request because the entity of the - * request is in a format not supported by the requested resource for the - * requested method. - * - * @see HTTP - * RFC - 10.4.16 415 Unsupported Media Type - */ - public static final Status CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE = new Status(415); - - /** - * The client connector faced an error during the communication with the remote - * server (interruption, timeout, etc.). The status code is 1001. - */ - public static final Status CONNECTOR_ERROR_COMMUNICATION = new Status(1001); - - /** - * The client connector could not connect to the remote server. The status code - * is 1000. - */ - public static final Status CONNECTOR_ERROR_CONNECTION = new Status(1000); - - /** - * The client connector faced an internal error during the process of a request - * to its server or the process of a response to its client. The status code is - * 1002. - */ - public static final Status CONNECTOR_ERROR_INTERNAL = new Status(1002); - - /** - * This interim response (the client has to wait for the final response) is used - * to inform the client that the initial part of the request has been received - * and has not yet been rejected or completed by the server. - * - * @see HTTP - * RFC - 10.1.1 100 Continue - */ - public static final Status INFO_CONTINUE = new Status(100); - - /** - * Warning status code, typically returned by a cache, indicating that it is - * intentionally disconnected from the rest of the network for a period of time. - * - * @see HTTP - * RFC - 14.46 Warning - */ - public static final Status INFO_DISCONNECTED_OPERATION = new Status(112); - - /** - * Warning status code, typically returned by a cache, indicating that it - * heuristically chose a freshness lifetime greater than 24 hours and the - * response's age is greater than 24 hours. - * - * @see HTTP - * RFC - 14.46 Warning - */ - public static final Status INFO_HEURISTIC_EXPIRATION = new Status(113); - - /** - * Warning status code, optionally including arbitrary information to be - * presented to a human user, typically returned by a cache. - * - * @see HTTP - * RFC - 14.46 Warning - */ - public static final Status INFO_MISC_WARNING = new Status(199); - - /** - * Warning status code, typically returned by a cache, indicating that the - * response is stale because an attempt to revalidate the response failed, due - * to an inability to reach the server. - * - * @see HTTP - * RFC - 14.46 Warning - */ - public static final Status INFO_REVALIDATION_FAILED = new Status(111); - - /** - * Warning status code, typically returned by a cache, indicating that the - * response is stale. - * - * @see HTTP - * RFC - 14.46 Warning - */ - public static final Status INFO_STALE_RESPONSE = new Status(110); - - /** - * The server understands and is willing to comply with the client's request, - * via the Upgrade message header field, for a change in the application - * protocol being used on this connection. - * - * @see HTTP - * RFC - 10.1.1 101 Switching Protocols - */ - public static final Status INFO_SWITCHING_PROTOCOL = new Status(101); - - /** - * The requested resource resides temporarily under a different URI which should - * not be used for future requests by the client (use status codes 303 or 307 - * instead since this status has been manifestly misused). - * - * @see HTTP - * RFC - 10.3.3 302 Found - */ - public static final Status REDIRECTION_FOUND = new Status(302); - - /** - * The server lets the user agent choosing one of the multiple representations - * of the requested resource, each representation having its own specific - * location provided in the response entity. - * - * @see HTTP - * RFC - 10.3.1 300 Multiple Choices - */ - public static final Status REDIRECTION_MULTIPLE_CHOICES = new Status(300); - - /** - * Status code sent by the server in response to a conditional GET request in - * case the document has not been modified. - * - * @see HTTP - * RFC - 10.3.5 304 Not Modified - */ - public static final Status REDIRECTION_NOT_MODIFIED = new Status(304); - - /** - * The requested resource has been assigned a new permanent URI and any future - * references to this resource SHOULD use one of the returned URIs. - * - * @see HTTP - * RFC - 10.3.2 301 Moved Permanently - */ - public static final Status REDIRECTION_PERMANENT = new Status(301); - - /** - * The response to the request can be found under a different URI and SHOULD be - * retrieved using a GET method on that resource. - * - * @see HTTP - * RFC - 10.3.4 303 See Other - */ - public static final Status REDIRECTION_SEE_OTHER = new Status(303); - - /** - * The requested resource resides temporarily under a different URI which should - * not be used for future requests by the client. - * - * @see HTTP - * RFC - 10.3.8 307 Temporary Redirect - */ - public static final Status REDIRECTION_TEMPORARY = new Status(307); - - /** - * The requested resource MUST be accessed through the proxy given by the - * Location field. - * - * @see HTTP - * RFC - 10.3.6 305 Use Proxy - */ - public static final Status REDIRECTION_USE_PROXY = new Status(305); - - /** - * The server, while acting as a gateway or proxy, received an invalid response - * from the upstream server it accessed in attempting to fulfill the request. - * - * @see HTTP - * RFC - 10.5.3 502 Bad Gateway - */ - public static final Status SERVER_ERROR_BAD_GATEWAY = new Status(502); - - /** - * The server, while acting as a gateway or proxy, could not connect to the - * upstream server. - * - * @see HTTP - * RFC - 10.5.5 504 Gateway Timeout - */ - public static final Status SERVER_ERROR_GATEWAY_TIMEOUT = new Status(504); - - /** - * The server encountered an unexpected condition which prevented it from - * fulfilling the request. - * - * @see HTTP - * RFC - 10.5.1 500 Internal Server Error - */ - public static final Status SERVER_ERROR_INTERNAL = new Status(500); - - /** - * The server does not support the functionality required to fulfill the - * request. - * - * @see HTTP - * RFC - 10.5.2 501 Not Implemented - */ - public static final Status SERVER_ERROR_NOT_IMPLEMENTED = new Status(501); - - /** - * The server is currently unable to handle the request due to a temporary - * overloading or maintenance of the server. - * - * @see HTTP - * RFC - 10.5.4 503 Service Unavailable - */ - public static final Status SERVER_ERROR_SERVICE_UNAVAILABLE = new Status(503); - - /** - * The server does not support, or refuses to support, the HTTP protocol version - * that was used in the request message. - * - * @see HTTP - * RFC - 10.5.6 505 HTTP Version Not Supported - */ - public static final Status SERVER_ERROR_VERSION_NOT_SUPPORTED = new Status(505); - - /** - * The request has been accepted for processing, but the processing has not been - * completed. - * - * @see HTTP - * RFC - 10.2.3 202 Accepted - */ - public static final Status SUCCESS_ACCEPTED = new Status(202); - - /** - * The request has been fulfilled and resulted in a new resource being created. - * - * @see HTTP - * RFC - 10.2.2 201 Created - */ - public static final Status SUCCESS_CREATED = new Status(201); - - /** - * Warning status code, optionally including arbitrary information to be - * presented to a human user, typically returned by a cache. - * - * @see HTTP - * RFC - 14.46 Warning - */ - public static final Status SUCCESS_MISC_PERSISTENT_WARNING = new Status(299); - - /** - * The server has fulfilled the request but does not need to return an - * entity-body (for example after a DELETE), and might want to return updated - * meta-information. - * - * @see HTTP - * RFC - 10.2.5 204 No Content - */ - public static final Status SUCCESS_NO_CONTENT = new Status(204); - - /** - * The request has succeeded but the returned meta-information in the - * entity-header does not come from the origin server, but is gathered from a - * local or a third-party copy. - * - * @see HTTP - * RFC - 10.2.4 203 Non-Authoritative Information - */ - public static final Status SUCCESS_NON_AUTHORITATIVE = new Status(203); - - /** - * The request has succeeded. - * - * @see HTTP - * RFC - 10.2.1 200 OK - */ - public static final Status SUCCESS_OK = new Status(200); - - /** - * The server has fulfilled the partial GET request for the resource assuming - * the request has included a Range header field indicating the desired range. - * - * @see HTTP - * RFC - 10.2.7 206 Partial Content - */ - public static final Status SUCCESS_PARTIAL_CONTENT = new Status(206); - - /** - * The server has fulfilled the request, and the user agent SHOULD reset the - * document view which caused the request to be sent. - * - * @see HTTP - * RFC - 10.2.6 205 Reset Content - */ - public static final Status SUCCESS_RESET_CONTENT = new Status(205); - - /** - * Warning status code, typically returned by a cache or a proxy, indicating - * that the response has been transformed. - * - * @see HTTP - * RFC - 14.46 Warning - */ - public static final Status SUCCESS_TRANSFORMATION_APPLIED = new Status(214); - - /** - * Check if the provided reason phrase of the status contains forbidden - * characters such as CR and LF. An IllegalArgumentException is thrown in this - * case. - * - * @see Status - * Code and Reason Phrase - * @param reasonPhrase The reason phrase to check. - * @return The name if it is correct. - */ - private static String checkReasonPhrase(String reasonPhrase) { - if (reasonPhrase != null) { - if (reasonPhrase.contains("\n") || reasonPhrase.contains("\r")) { - throw new IllegalArgumentException("Reason phrase of the status must not contain CR or LF characters."); - } - } - - return reasonPhrase; - } - - /** - * Indicates if the status is a client error status, meaning "The request - * contains bad syntax or cannot be fulfilled". - * - * @param code The code of the status. - * @return True if the status is a client error status. - */ - public static boolean isClientError(int code) { - return (code >= 400) && (code <= 499); - } - - /** - * Indicates if the status is a connector error status, meaning "The connector - * failed to send or receive an apparently valid message". - * - * @param code The code of the status. - * @return True if the status is a server error status. - */ - public static boolean isConnectorError(int code) { - return (code >= 1000) && (code <= 1099); - } - - /** - * Indicates if the status is an error (client or server) status. - * - * @param code The code of the status. - * @return True if the status is an error (client or server) status. - */ - public static boolean isError(int code) { - return isClientError(code) || isServerError(code) || isConnectorError(code) || isGlobalError(code); - } - - /** - * Indicates if the status is a client error status, meaning "The request - * contains bad syntax or cannot be fulfilled". - * - * @param code The code of the status. - * @return True if the status is a client error status. - */ - public static boolean isGlobalError(int code) { - return (code >= 600) && (code <= 699); - } - - /** - * Indicates if the status is an information status, meaning "request received, - * continuing process". - * - * @param code The code of the status. - * @return True if the status is an information status. - */ - public static boolean isInformational(int code) { - return (code >= 100) && (code <= 199); - } - - /** - * Indicates if the status is a redirection status, meaning "Further action must - * be taken in order to complete the request". - * - * @param code The code of the status. - * @return True if the status is a redirection status. - */ - public static boolean isRedirection(int code) { - return (code >= 300) && (code <= 399); - } - - /** - * Indicates if the status is a server error status, meaning "The server failed - * to fulfill an apparently valid request". - * - * @param code The code of the status. - * @return True if the status is a server error status. - */ - public static boolean isServerError(int code) { - return (code >= 500) && (code <= 599); - } - - /** - * Indicates if the status is a success status, meaning "The action was - * successfully received, understood, and accepted". - * - * @param code The code of the status. - * @return True if the status is a success status. - */ - public static boolean isSuccess(int code) { - return (code >= 200) && (code <= 299); - } - - /** - * Returns the status associated to a code. If an existing constant exists then - * it is returned, otherwise a new instance is created. - * - * @param code The code. - * @return The associated status. - */ - public static Status valueOf(int code) { + private static final String BASE_ADDED_HTTP = "http://tools.ietf.org/html/rfc6585"; + + private static final String BASE_HTTP = + "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html"; + + private static final String BASE_RESTLET = + "https://javadoc.io/static/org.restlet/org.restlet/" + Engine.VERSION + "/"; + + /** + * The server could not understand the request due to malformed syntax. + * + * @see HTTP RFC - + * 10.4.1 400 Bad Request + */ + public static final Status CLIENT_ERROR_BAD_REQUEST = new Status(400); + + /** + * The request could not be completed due to a conflict with the current state of the resource + * (as experienced in a version control system). + * + * @see HTTP RFC - + * 10.4.10 409 Conflict + */ + public static final Status CLIENT_ERROR_CONFLICT = new Status(409); + + /** + * The user agent expects some behavior of the server (given in an Expect request-header field), + * but this expectation could not be met by this server. + * + * @see HTTP RFC - + * 10.4.18 417 Expectation Failed + */ + public static final Status CLIENT_ERROR_EXPECTATION_FAILED = new Status(417); + + /** + * The server understood the request, but is refusing to fulfill it as it could be explained in + * the entity. + * + * @see HTTP RFC - + * 10.4.4 403 Forbidden + */ + public static final Status CLIENT_ERROR_FORBIDDEN = new Status(403); + + /** + * The requested resource is no longer available at the server and no forwarding address is + * known. + * + * @see HTTP RFC - + * 10.4.11 410 Gone + */ + public static final Status CLIENT_ERROR_GONE = new Status(410); + + /** + * The server refuses to accept the request without a defined Content-Length. + * + * @see HTTP RFC - + * 10.4.12 411 Length Required + */ + public static final Status CLIENT_ERROR_LENGTH_REQUIRED = new Status(411); + + /** + * The method specified in the Request-Line is not allowed for the resource identified by the + * Request-URI. + * + * @see HTTP RFC - + * 10.4.6 405 Method Not Allowed + */ + public static final Status CLIENT_ERROR_METHOD_NOT_ALLOWED = new Status(405); + + /** + * The resource identified by the request is only capable of generating response entities whose + * content characteristics do not match the user's requirements (in Accept* headers). + * + * @see HTTP RFC - + * 10.4.7 406 Not Acceptable + */ + public static final Status CLIENT_ERROR_NOT_ACCEPTABLE = new Status(406); + + /** + * The server has not found anything matching the Request-URI or the server does not wish to + * reveal exactly why the request has been refused, or no other response is applicable. + * + * @see HTTP RFC - + * 10.4.5 404 Not Found + */ + public static final Status CLIENT_ERROR_NOT_FOUND = new Status(404); + + /** + * This code is reserved for future use. + * + * @see HTTP RFC - + * 10.4.3 402 Payment Required + */ + public static final Status CLIENT_ERROR_PAYMENT_REQUIRED = new Status(402); + + /** + * Sent by the server when the user agent asks the server to carry out a request under certain + * conditions that are not met. + * + * @see HTTP RFC - + * 10.4.13 412 Precondition Failed + */ + public static final Status CLIENT_ERROR_PRECONDITION_FAILED = new Status(412); + + /** + * This code is similar to 401 (Unauthorized), but indicates that the client must first + * authenticate itself with the proxy. + * + * @see HTTP RFC - + * 10.4.8 407 Proxy Authentication Required + */ + public static final Status CLIENT_ERROR_PROXY_AUTHENTIFICATION_REQUIRED = new Status(407); + + /** + * The server is refusing to process a request because the request entity is larger than the + * server is willing or able to process. + * + * @see HTTP RFC - + * 10.4.14 413 Request Entity Too Large + */ + public static final Status CLIENT_ERROR_REQUEST_ENTITY_TOO_LARGE = new Status(413); + + /** + * Sent by the server when an HTTP client opens a connection, but has never sent a request (or + * never sent the blank line that signals the end of the request). + * + * @see HTTP RFC - + * 10.4.9 408 Request Timeout + */ + public static final Status CLIENT_ERROR_REQUEST_TIMEOUT = new Status(408); + + /** + * The server is refusing to service the request because the Request-URI is longer than the + * server is willing to interpret. + * + * @see HTTP RFC - + * 10.4.15 414 Request-URI Too Long + */ + public static final Status CLIENT_ERROR_REQUEST_URI_TOO_LONG = new Status(414); + + /** + * The request includes a Range request-header field and the selected resource is too small for + * any of the byte-ranges to apply. + * + * @see HTTP RFC - + * 10.4.17 416 Requested Range Not Satisfiable + */ + public static final Status CLIENT_ERROR_REQUESTED_RANGE_NOT_SATISFIABLE = new Status(416); + + /** + * The server refuses to accept the request because the user has sent too many requests in a + * given amount of time. + * + * @see HTTP RFC - 10.4.12 429 Too Many + * Requests + */ + public static final Status CLIENT_ERROR_TOO_MANY_REQUESTS = new Status(429); + + /** + * The request requires user authentication. + * + * @see HTTP RFC - + * 10.4.2 401 Unauthorized + */ + public static final Status CLIENT_ERROR_UNAUTHORIZED = new Status(401); + + /** + * The server is refusing to service the request because the entity of the request is in a + * format not supported by the requested resource for the requested method. + * + * @see HTTP RFC - + * 10.4.16 415 Unsupported Media Type + */ + public static final Status CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE = new Status(415); + + /** + * The client connector faced an error during the communication with the remote server + * (interruption, timeout, etc.). The status code is 1001. + */ + public static final Status CONNECTOR_ERROR_COMMUNICATION = new Status(1001); + + /** The client connector could not connect to the remote server. The status code is 1000. */ + public static final Status CONNECTOR_ERROR_CONNECTION = new Status(1000); + + /** + * The client connector faced an internal error during the process of a request to its server or + * the process of a response to its client. The status code is 1002. + */ + public static final Status CONNECTOR_ERROR_INTERNAL = new Status(1002); + + /** + * This interim response (the client has to wait for the final response) is used to inform the + * client that the initial part of the request has been received and has not yet been rejected + * or completed by the server. + * + * @see HTTP RFC - + * 10.1.1 100 Continue + */ + public static final Status INFO_CONTINUE = new Status(100); + + /** + * Warning status code, typically returned by a cache, indicating that it is intentionally + * disconnected from the rest of the network for a period of time. + * + * @see HTTP RFC - + * 14.46 Warning + */ + public static final Status INFO_DISCONNECTED_OPERATION = new Status(112); + + /** + * Warning status code, typically returned by a cache, indicating that it heuristically chose a + * freshness lifetime greater than 24 hours and the response's age is greater than 24 hours. + * + * @see HTTP RFC - + * 14.46 Warning + */ + public static final Status INFO_HEURISTIC_EXPIRATION = new Status(113); + + /** + * Warning status code, optionally including arbitrary information to be presented to a human + * user, typically returned by a cache. + * + * @see HTTP RFC - + * 14.46 Warning + */ + public static final Status INFO_MISC_WARNING = new Status(199); + + /** + * Warning status code, typically returned by a cache, indicating that the response is stale + * because an attempt to revalidate the response failed, due to an inability to reach the + * server. + * + * @see HTTP RFC - + * 14.46 Warning + */ + public static final Status INFO_REVALIDATION_FAILED = new Status(111); + + /** + * Warning status code, typically returned by a cache, indicating that the response is stale. + * + * @see HTTP RFC - + * 14.46 Warning + */ + public static final Status INFO_STALE_RESPONSE = new Status(110); + + /** + * The server understands and is willing to comply with the client's request, via the Upgrade + * message header field, for a change in the application protocol being used on this connection. + * + * @see HTTP RFC - + * 10.1.1 101 Switching Protocols + */ + public static final Status INFO_SWITCHING_PROTOCOL = new Status(101); + + /** + * The requested resource resides temporarily under a different URI which should not be used for + * future requests by the client (use status codes 303 or 307 instead since this status has been + * manifestly misused). + * + * @see HTTP RFC - + * 10.3.3 302 Found + */ + public static final Status REDIRECTION_FOUND = new Status(302); + + /** + * The server lets the user agent choosing one of the multiple representations of the requested + * resource, each representation having its own specific location provided in the response + * entity. + * + * @see HTTP RFC - + * 10.3.1 300 Multiple Choices + */ + public static final Status REDIRECTION_MULTIPLE_CHOICES = new Status(300); + + /** + * Status code sent by the server in response to a conditional GET request in case the document + * has not been modified. + * + * @see HTTP RFC - + * 10.3.5 304 Not Modified + */ + public static final Status REDIRECTION_NOT_MODIFIED = new Status(304); + + /** + * The requested resource has been assigned a new permanent URI and any future references to + * this resource SHOULD use one of the returned URIs. + * + * @see HTTP RFC - + * 10.3.2 301 Moved Permanently + */ + public static final Status REDIRECTION_PERMANENT = new Status(301); + + /** + * The response to the request can be found under a different URI and SHOULD be retrieved using + * a GET method on that resource. + * + * @see HTTP RFC - + * 10.3.4 303 See Other + */ + public static final Status REDIRECTION_SEE_OTHER = new Status(303); + + /** + * The requested resource resides temporarily under a different URI which should not be used for + * future requests by the client. + * + * @see HTTP RFC - + * 10.3.8 307 Temporary Redirect + */ + public static final Status REDIRECTION_TEMPORARY = new Status(307); + + /** + * The requested resource MUST be accessed through the proxy given by the Location field. + * + * @see HTTP RFC - + * 10.3.6 305 Use Proxy + */ + public static final Status REDIRECTION_USE_PROXY = new Status(305); + + /** + * The server, while acting as a gateway or proxy, received an invalid response from the + * upstream server it accessed in attempting to fulfill the request. + * + * @see HTTP RFC - + * 10.5.3 502 Bad Gateway + */ + public static final Status SERVER_ERROR_BAD_GATEWAY = new Status(502); + + /** + * The server, while acting as a gateway or proxy, could not connect to the upstream server. + * + * @see HTTP RFC - + * 10.5.5 504 Gateway Timeout + */ + public static final Status SERVER_ERROR_GATEWAY_TIMEOUT = new Status(504); + + /** + * The server encountered an unexpected condition which prevented it from fulfilling the + * request. + * + * @see HTTP RFC - + * 10.5.1 500 Internal Server Error + */ + public static final Status SERVER_ERROR_INTERNAL = new Status(500); + + /** + * The server does not support the functionality required to fulfill the request. + * + * @see HTTP RFC - + * 10.5.2 501 Not Implemented + */ + public static final Status SERVER_ERROR_NOT_IMPLEMENTED = new Status(501); + + /** + * The server is currently unable to handle the request due to a temporary overloading or + * maintenance of the server. + * + * @see HTTP RFC - + * 10.5.4 503 Service Unavailable + */ + public static final Status SERVER_ERROR_SERVICE_UNAVAILABLE = new Status(503); + + /** + * The server does not support, or refuses to support, the HTTP protocol version that was used + * in the request message. + * + * @see HTTP RFC - + * 10.5.6 505 HTTP Version Not Supported + */ + public static final Status SERVER_ERROR_VERSION_NOT_SUPPORTED = new Status(505); + + /** + * The request has been accepted for processing, but the processing has not been completed. + * + * @see HTTP RFC - + * 10.2.3 202 Accepted + */ + public static final Status SUCCESS_ACCEPTED = new Status(202); + + /** + * The request has been fulfilled and resulted in a new resource being created. + * + * @see HTTP RFC - + * 10.2.2 201 Created + */ + public static final Status SUCCESS_CREATED = new Status(201); + + /** + * Warning status code, optionally including arbitrary information to be presented to a human + * user, typically returned by a cache. + * + * @see HTTP RFC - + * 14.46 Warning + */ + public static final Status SUCCESS_MISC_PERSISTENT_WARNING = new Status(299); + + /** + * The server has fulfilled the request but does not need to return an entity-body (for example + * after a DELETE), and might want to return updated meta-information. + * + * @see HTTP RFC - + * 10.2.5 204 No Content + */ + public static final Status SUCCESS_NO_CONTENT = new Status(204); + + /** + * The request has succeeded but the returned meta-information in the entity-header does not + * come from the origin server, but is gathered from a local or a third-party copy. + * + * @see HTTP RFC - + * 10.2.4 203 Non-Authoritative Information + */ + public static final Status SUCCESS_NON_AUTHORITATIVE = new Status(203); + + /** + * The request has succeeded. + * + * @see HTTP RFC - + * 10.2.1 200 OK + */ + public static final Status SUCCESS_OK = new Status(200); + + /** + * The server has fulfilled the partial GET request for the resource assuming the request has + * included a Range header field indicating the desired range. + * + * @see HTTP RFC - + * 10.2.7 206 Partial Content + */ + public static final Status SUCCESS_PARTIAL_CONTENT = new Status(206); + + /** + * The server has fulfilled the request, and the user agent SHOULD reset the document view which + * caused the request to be sent. + * + * @see HTTP RFC - + * 10.2.6 205 Reset Content + */ + public static final Status SUCCESS_RESET_CONTENT = new Status(205); + + /** + * Warning status code, typically returned by a cache or a proxy, indicating that the response + * has been transformed. + * + * @see HTTP RFC - + * 14.46 Warning + */ + public static final Status SUCCESS_TRANSFORMATION_APPLIED = new Status(214); + + /** + * Check if the provided reason phrase of the status contains forbidden characters such as CR + * and LF. An IllegalArgumentException is thrown in this case. + * + * @see Status Code + * and Reason Phrase + * @param reasonPhrase The reason phrase to check. + * @return The name if it is correct. + */ + private static String checkReasonPhrase(String reasonPhrase) { + if (reasonPhrase != null && (reasonPhrase.contains("\n") || reasonPhrase.contains("\r"))) { + throw new IllegalArgumentException( + "Reason phrase of the status must not contain CR or LF characters."); + } + + return reasonPhrase; + } + + /** + * Indicates if the status is a client error status, meaning "The request contains bad syntax or + * cannot be fulfilled". + * + * @param code The code of the status. + * @return True if the status is a client error status. + */ + public static boolean isClientError(int code) { + return (code >= 400) && (code <= 499); + } + + /** + * Indicates if the status is a connector error status, meaning "The connector failed to send or + * receive an apparently valid message". + * + * @param code The code of the status. + * @return True if the status is a server error status. + */ + public static boolean isConnectorError(int code) { + return (code >= 1000) && (code <= 1099); + } + + /** + * Indicates if the status is an error (client or server) status. + * + * @param code The code of the status. + * @return True if the status is an error (client or server) status. + */ + public static boolean isError(int code) { + return isClientError(code) + || isServerError(code) + || isConnectorError(code) + || isGlobalError(code); + } + + /** + * Indicates if the status is a client error status, meaning "The request contains bad syntax or + * cannot be fulfilled". + * + * @param code The code of the status. + * @return True if the status is a client error status. + */ + public static boolean isGlobalError(int code) { + return (code >= 600) && (code <= 699); + } + + /** + * Indicates if the status is an information status, meaning "request received, continuing + * process". + * + * @param code The code of the status. + * @return True if the status is an information status. + */ + public static boolean isInformational(int code) { + return (code >= 100) && (code <= 199); + } + + /** + * Indicates if the status is a redirection status, meaning "Further action must be taken in + * order to complete the request". + * + * @param code The code of the status. + * @return True if the status is a redirection status. + */ + public static boolean isRedirection(int code) { + return (code >= 300) && (code <= 399); + } + + /** + * Indicates if the status is a server error status, meaning "The server failed to fulfill an + * apparently valid request". + * + * @param code The code of the status. + * @return True if the status is a server error status. + */ + public static boolean isServerError(int code) { + return (code >= 500) && (code <= 599); + } + + /** + * Indicates if the status is a success status, meaning "The action was successfully received, + * understood, and accepted". + * + * @param code The code of the status. + * @return True if the status is a success status. + */ + public static boolean isSuccess(int code) { + return (code >= 200) && (code <= 299); + } + + /** + * Returns the status associated with a code. If an existing constant exists, then it is + * returned, otherwise a new instance is created. + * + * @param code The code. + * @return The associated status. + */ + public static Status valueOf(int code) { return switch (code) { case 100 -> INFO_CONTINUE; case 101 -> INFO_SWITCHING_PROTOCOL; @@ -696,217 +634,221 @@ public static Status valueOf(int code) { case 1002 -> CONNECTOR_ERROR_INTERNAL; default -> new Status(code); }; - } - - /** The specification code. */ - private final int code; - - /** The longer description. */ - private final String description; - - /** - * The short reason phrase displayed next to the status code in a HTTP response. - */ - private volatile String reasonPhrase; - - /** The related error or exception. */ - private final Throwable throwable; - - /** The URI of the specification describing the method. */ - private final String uri; - - /** - * Constructor. - * - * @param code The specification code. - */ - public Status(int code) { - this(code, null, null, null, null); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - */ - public Status(int code, String reasonPhrase) { - this(code, null, reasonPhrase, null, null); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - * @param description The longer description. - */ - public Status(int code, String reasonPhrase, String description) { - this(code, null, reasonPhrase, description, null); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - * @param description The longer description. - * @param uri The URI of the specification describing the method. - */ - public Status(int code, String reasonPhrase, String description, String uri) { - this(code, null, reasonPhrase, description, uri); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param throwable The related error or exception. - */ - public Status(int code, Throwable throwable) { - this(code, throwable, null, null, null); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param throwable The related error or exception. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - */ - public Status(int code, Throwable throwable, String reasonPhrase) { - this(code, throwable, reasonPhrase, null, null); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param throwable The related error or exception. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - * @param description The longer description. - */ - public Status(int code, Throwable throwable, String reasonPhrase, String description) { - this(code, throwable, reasonPhrase, description, null); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param throwable The related error or exception. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - * @param description The longer description. - * @param uri The URI of the specification describing the method. - */ - public Status(int code, Throwable throwable, String reasonPhrase, String description, String uri) { - this.code = code; - this.throwable = throwable; - this.reasonPhrase = checkReasonPhrase(reasonPhrase); - this.description = description; - this.uri = uri; - } - - /** - * Constructor. - * - * @param status The status to copy. - * @param description The description to associate. - */ - public Status(Status status, String description) { - this(status, null, null, description); - } - - /** - * Constructor. - * - * @param status The status to copy. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - * @param description The description to associate. - */ - public Status(Status status, String reasonPhrase, String description) { - this(status, null, reasonPhrase, description); - } - - /** - * Constructor. - * - * @param status The status to copy. - * @param throwable The related error or exception. - */ - public Status(Status status, Throwable throwable) { - this(status, throwable, null, null); - } - - /** - * Constructor. - * - * @param status The status to copy. - * @param throwable The related error or exception. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - */ - public Status(Status status, Throwable throwable, String reasonPhrase) { - this(status, throwable, reasonPhrase, null); - } - - /** - * Constructor. - * - * @param status The status to copy. - * @param throwable The related error or exception. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - * @param description The description to associate. - */ - public Status(Status status, Throwable throwable, String reasonPhrase, String description) { - this(status.getCode(), (throwable == null) ? status.getThrowable() : throwable, - (reasonPhrase == null) ? status.getReasonPhrase() : reasonPhrase, - (description == null) ? status.getDescription() : description, status.getUri()); - } - - /** - * Indicates if the status is equal to a given one. - * - * @param object The object to compare to. - * @return True if the status is equal to a given one. - */ - @Override - public boolean equals(final Object object) { - return (object instanceof Status) && (this.code == ((Status) object).getCode()); - } - - /** - * Returns the corresponding code (HTTP or custom code). - * - * @return The corresponding code. - */ - public int getCode() { - return this.code; - } - - /** - * Returns the description. This value is typically used by the - * {@link org.restlet.service.StatusService} to build a meaningful description - * of an error via a response entity. - * - * @return The description. - */ - public String getDescription() { - if (this.description != null) { - return this.description; - } + } + + /** The specification code. */ + private final int code; + + /** The longer description. */ + private final String description; + + /** The short reason phrase displayed next to the status code in an HTTP response. */ + private volatile String reasonPhrase; + + /** The related error or exception. */ + private final Throwable throwable; + + /** The URI of the specification describing the method. */ + private final String uri; + + /** + * Constructor. + * + * @param code The specification code. + */ + public Status(int code) { + this(code, null, null, null, null); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + */ + public Status(int code, String reasonPhrase) { + this(code, null, reasonPhrase, null, null); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + * @param description The longer description. + */ + public Status(int code, String reasonPhrase, String description) { + this(code, null, reasonPhrase, description, null); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + * @param description The longer description. + * @param uri The URI of the specification describing the method. + */ + public Status(int code, String reasonPhrase, String description, String uri) { + this(code, null, reasonPhrase, description, uri); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param throwable The related error or exception. + */ + public Status(int code, Throwable throwable) { + this(code, throwable, null, null, null); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param throwable The related error or exception. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + */ + public Status(int code, Throwable throwable, String reasonPhrase) { + this(code, throwable, reasonPhrase, null, null); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param throwable The related error or exception. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + * @param description The longer description. + */ + public Status(int code, Throwable throwable, String reasonPhrase, String description) { + this(code, throwable, reasonPhrase, description, null); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param throwable The related error or exception. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + * @param description The longer description. + * @param uri The URI of the specification describing the method. + */ + public Status( + int code, Throwable throwable, String reasonPhrase, String description, String uri) { + this.code = code; + this.throwable = throwable; + this.reasonPhrase = checkReasonPhrase(reasonPhrase); + this.description = description; + this.uri = uri; + } + + /** + * Constructor. + * + * @param status The status to copy. + * @param description The description to associate. + */ + public Status(Status status, String description) { + this(status, null, null, description); + } + + /** + * Constructor. + * + * @param status The status to copy. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + * @param description The description to associate. + */ + public Status(Status status, String reasonPhrase, String description) { + this(status, null, reasonPhrase, description); + } + + /** + * Constructor. + * + * @param status The status to copy. + * @param throwable The related error or exception. + */ + public Status(Status status, Throwable throwable) { + this(status, throwable, null, null); + } + + /** + * Constructor. + * + * @param status The status to copy. + * @param throwable The related error or exception. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + */ + public Status(Status status, Throwable throwable, String reasonPhrase) { + this(status, throwable, reasonPhrase, null); + } + + /** + * Constructor. + * + * @param status The status to copy. + * @param throwable The related error or exception. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + * @param description The description to associate. + */ + public Status(Status status, Throwable throwable, String reasonPhrase, String description) { + this( + status.getCode(), + (throwable == null) ? status.getThrowable() : throwable, + (reasonPhrase == null) ? status.getReasonPhrase() : reasonPhrase, + (description == null) ? status.getDescription() : description, + status.getUri()); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Status that)) { + return false; + } + return this.code == that.getCode(); + } + + /** + * Returns the corresponding code (HTTP or custom code). + * + * @return The corresponding code. + */ + public int getCode() { + return this.code; + } + + /** + * Returns the description. This value is typically used by the {@link + * org.restlet.service.StatusService} to build a meaningful description of an error via a + * response entity. + * + * @return The description. + */ + public String getDescription() { + if (this.description != null) { + return this.description; + } return switch (this.code) { case 100 -> "The client should continue with its request"; - case 101 -> "The server is willing to change the application protocol being used on this connection"; + case 101 -> + "The server is willing to change the application protocol being used on this connection"; case 110 -> "MUST be included whenever the returned response is stale"; case 111 -> "MUST be included if a cache returns a stale response because an attempt to revalidate the response failed, due to an inability to reach the server"; @@ -917,9 +859,12 @@ public String getDescription() { case 199 -> "The warning text MAY include arbitrary information to be presented to a human user, or logged. A system receiving this warning MUST NOT take any automated action, besides presenting the warning to the user"; case 200 -> "The request has succeeded"; - case 201 -> "The request has been fulfilled and resulted in a new resource being created"; - case 202 -> "The request has been accepted for processing, but the processing has not been completed"; - case 203 -> "The returned meta-information is not the definitive set as available from the origin server"; + case 201 -> + "The request has been fulfilled and resulted in a new resource being created"; + case 202 -> + "The request has been accepted for processing, but the processing has not been completed"; + case 203 -> + "The returned meta-information is not the definitive set as available from the origin server"; case 204 -> "The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta-information"; case 205 -> @@ -933,8 +878,10 @@ public String getDescription() { case 301 -> "The requested resource has been assigned a new permanent URI"; case 302 -> "The requested resource can be found under a different URI"; case 303 -> "The response to the request can be found under a different URI"; - case 304 -> "The client has performed a conditional GET request and the document has not been modified"; - case 305 -> "The requested resource must be accessed through the proxy given by the location field"; + case 304 -> + "The client has performed a conditional GET request and the document has not been modified"; + case 305 -> + "The requested resource must be accessed through the proxy given by the location field"; case 307 -> "The requested resource resides temporarily under a different URI"; case 400 -> "The request could not be understood by the server due to malformed syntax"; case 401 -> "The request requires user authentication"; @@ -947,8 +894,10 @@ public String getDescription() { "The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request"; case 407 -> "This code is similar to Unauthorized, but indicates that the client must first authenticate itself with the proxy"; - case 408 -> "The client did not produce a request within the time that the server was prepared to wait"; - case 409 -> "The request could not be completed due to a conflict with the current state of the resource"; + case 408 -> + "The client did not produce a request within the time that the server was prepared to wait"; + case 409 -> + "The request could not be completed due to a conflict with the current state of the resource"; case 410 -> "The requested resource is no longer available at the server and no forwarding address is known"; case 411 -> "The server refuses to accept the request without a defined content length"; @@ -962,17 +911,20 @@ public String getDescription() { "The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method"; case 416 -> "For byte ranges, this means that the first byte position were greater than the current length of the selected resource"; - case 417 -> "The expectation given in the request header could not be met by this server"; + case 417 -> + "The expectation given in the request header could not be met by this server"; case 429 -> "The server is refusing to service the request because the user has sent too many requests in a given amount of time (\"rate limiting\")"; - case 500 -> "The server encountered an unexpected condition which prevented it from fulfilling the request"; - case 501 -> "The server does not support the functionality required to fulfill the request"; + case 500 -> + "The server encountered an unexpected condition which prevented it from fulfilling the request"; + case 501 -> + "The server does not support the functionality required to fulfill the request"; case 502 -> "The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request"; case 503 -> "The server is currently unable to handle the request due to a temporary overloading or maintenance of the server"; case 504 -> - "The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server (e.g. DNS) it needed to access in attempting to complete the request"; + "The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI (e.g., HTTP, FTP, LDAP) or some other auxiliary server (e.g., DNS) it needed to access in attempting to complete the request"; case 505 -> "The server does not support, or refuses to support, the protocol version that was used in the request message"; case 1000 -> "The connector failed to connect to the server"; @@ -981,20 +933,18 @@ public String getDescription() { "The connector encountered an unexpected condition which prevented it from fulfilling the request"; default -> null; }; - } - /** - * Returns the reason phrase of this status. When supported by the HTTP server - * connector, this is returned in the first line of the HTTP response, next to - * to the status code. - * - * @return The reason phrase of this status. - */ - public String getReasonPhrase() { - if (this.reasonPhrase != null) { - return this.reasonPhrase; - } + /** + * Returns the reason phrase of this status. When supported by the HTTP server connector, this + * is returned in the first line of the HTTP response, next to the status code. + * + * @return The reason phrase of this status. + */ + public String getReasonPhrase() { + if (this.reasonPhrase != null) { + return this.reasonPhrase; + } return switch (this.code) { case 100 -> "Continue"; @@ -1050,32 +1000,32 @@ public String getReasonPhrase() { case 1002 -> "Internal Connector Error"; default -> null; }; + } + /** + * Returns the related error or exception. + * + * @return The related error or exception. + */ + public Throwable getThrowable() { + return this.throwable; } - /** - * Returns the related error or exception. - * - * @return The related error or exception. - */ - public Throwable getThrowable() { - return this.throwable; - } - - /** - * Returns the URI of the specification describing the status. - * - * @return The URI of the specification describing the status. - */ - public String getUri() { - if (this.uri != null) { - return this.uri; - } + /** + * Returns the URI of the specification describing the status. + * + * @return The URI of the specification describing the status. + */ + public String getUri() { + if (this.uri != null) { + return this.uri; + } return switch (this.code) { case 100 -> BASE_HTTP + "#sec10.1.1"; case 101 -> BASE_HTTP + "#sec10.1.2"; - case 110, 111, 112, 113, 199, 214, 299 -> "http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.46"; + case 110, 111, 112, 113, 199, 214, 299 -> + "http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.46"; case 200 -> BASE_HTTP + "#sec10.2.1"; case 201 -> BASE_HTTP + "#sec10.2.2"; case 202 -> BASE_HTTP + "#sec10.2.3"; @@ -1116,121 +1066,124 @@ public String getUri() { case 504 -> BASE_HTTP + "#sec10.5.5"; case 505 -> BASE_HTTP + "#sec10.5.6"; case 1000 -> BASE_RESTLET + "org/restlet/data/Status.html#CONNECTOR_ERROR_CONNECTION"; - case 1001 -> BASE_RESTLET + "org/restlet/data/Status.html#CONNECTOR_ERROR_COMMUNICATION"; + case 1001 -> + BASE_RESTLET + "org/restlet/data/Status.html#CONNECTOR_ERROR_COMMUNICATION"; case 1002 -> BASE_RESTLET + "org/restlet/data/Status.html#CONNECTOR_ERROR_INTERNAL"; default -> null; }; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return getCode(); + } + + /** + * Indicates if the status is a client error status, meaning "The request contains bad syntax or + * cannot be fulfilled". + * + * @return True if the status is a client error status. + */ + public boolean isClientError() { + return isClientError(getCode()); + } + /** + * Indicates if the status is a connector error status, meaning "The connector failed to send or + * receive an apparently valid message". + * + * @return True if the status is a connector error status. + */ + public boolean isConnectorError() { + return isConnectorError(getCode()); } - /** {@inheritDoc} */ - @Override - public int hashCode() { - return getCode(); - } - - /** - * Indicates if the status is a client error status, meaning "The request - * contains bad syntax or cannot be fulfilled". - * - * @return True if the status is a client error status. - */ - public boolean isClientError() { - return isClientError(getCode()); - } - - /** - * Indicates if the status is a connector error status, meaning "The connector - * failed to send or receive an apparently valid message". - * - * @return True if the status is a connector error status. - */ - public boolean isConnectorError() { - return isConnectorError(getCode()); - } - - /** - * Indicates if the status is an error (client or server) status. - * - * @return True if the status is an error (client or server) status. - */ - public boolean isError() { - return isError(getCode()); - } - - /** - * Indicates if the status is a global error status, meaning "The server has - * definitive information about a particular user". - * - * @return True if the status is a global error status. - */ - public boolean isGlobalError() { - return isGlobalError(getCode()); - } - - /** - * Indicates if the status is an information status, meaning "request received, - * continuing process". - * - * @return True if the status is an information status. - */ - public boolean isInformational() { - return isInformational(getCode()); - } - - /** - * Indicates if an error is recoverable, meaning that simply retrying after a - * delay could result in a success. Tests {@link #isConnectorError()} and if the - * status is {@link #CLIENT_ERROR_REQUEST_TIMEOUT} or - * {@link #SERVER_ERROR_GATEWAY_TIMEOUT} or - * {@link #SERVER_ERROR_SERVICE_UNAVAILABLE}. - * - * @return True if the error is recoverable. - */ - public boolean isRecoverableError() { - return isConnectorError() || equals(Status.CLIENT_ERROR_REQUEST_TIMEOUT) - || equals(Status.SERVER_ERROR_GATEWAY_TIMEOUT) || equals(Status.SERVER_ERROR_SERVICE_UNAVAILABLE); - } - - /** - * Indicates if the status is a redirection status, meaning "Further action must - * be taken in order to complete the request". - * - * @return True if the status is a redirection status. - */ - public boolean isRedirection() { - return isRedirection(getCode()); - } - - /** - * Indicates if the status is a server error status, meaning "The server failed - * to fulfill an apparently valid request". - * - * @return True if the status is a server error status. - */ - public boolean isServerError() { - return isServerError(getCode()); - } - - /** - * Indicates if the status is a success status, meaning "The action was - * successfully received, understood, and accepted". - * - * @return True if the status is a success status. - */ - public boolean isSuccess() { - return isSuccess(getCode()); - } - - /** - * Returns the reason phrase of the status followed by its HTTP code. - * - * @return The reason phrase of the status followed by its HTTP code. - */ - @Override - public String toString() { - return getReasonPhrase() + " (" + this.code + ")" - + ((getDescription() == null) ? "" : " - " + getDescription()); - } + /** + * Indicates if the status is an error (client or server) status. + * + * @return True if the status is an error (client or server) status. + */ + public boolean isError() { + return isError(getCode()); + } + + /** + * Indicates if the status is a global error status, meaning "The server has definitive + * information about a particular user". + * + * @return True if the status is a global error status. + */ + public boolean isGlobalError() { + return isGlobalError(getCode()); + } + + /** + * Indicates if the status is an information status, meaning "request received, continuing + * process". + * + * @return True if the status is an information status. + */ + public boolean isInformational() { + return isInformational(getCode()); + } + /** + * Indicates if an error is recoverable, meaning that simply retrying after a delay could result + * in a success. Tests {@link #isConnectorError()} and if the status is {@link + * #CLIENT_ERROR_REQUEST_TIMEOUT} or {@link #SERVER_ERROR_GATEWAY_TIMEOUT} or {@link + * #SERVER_ERROR_SERVICE_UNAVAILABLE}. + * + * @return True if the error is recoverable. + */ + public boolean isRecoverableError() { + return isConnectorError() + || equals(Status.CLIENT_ERROR_REQUEST_TIMEOUT) + || equals(Status.SERVER_ERROR_GATEWAY_TIMEOUT) + || equals(Status.SERVER_ERROR_SERVICE_UNAVAILABLE); + } + + /** + * Indicates if the status is a redirection status, meaning "Further action must be taken in + * order to complete the request". + * + * @return True if the status is a redirection status. + */ + public boolean isRedirection() { + return isRedirection(getCode()); + } + + /** + * Indicates if the status is a server error status, meaning "The server failed to fulfill an + * apparently valid request". + * + * @return True if the status is a server error status. + */ + public boolean isServerError() { + return isServerError(getCode()); + } + + /** + * Indicates if the status is a success status, meaning "The action was successfully received, + * understood, and accepted". + * + * @return True if the status is a success status. + */ + public boolean isSuccess() { + return isSuccess(getCode()); + } + + /** + * Returns the reason phrase of the status followed by its HTTP code. + * + * @return The reason phrase of the status followed by its HTTP code. + */ + @Override + public String toString() { + return getReasonPhrase() + + " (" + + this.code + + ")" + + ((getDescription() == null) ? "" : " - " + getDescription()); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Tag.java b/org.restlet/src/main/java/org/restlet/data/Tag.java index a008d74e44..4b0b6854a6 100644 --- a/org.restlet/src/main/java/org/restlet/data/Tag.java +++ b/org.restlet/src/main/java/org/restlet/data/Tag.java @@ -1,191 +1,176 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.restlet.Context; -import org.restlet.representation.RepresentationInfo; - import java.util.Objects; import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.representation.RepresentationInfo; /** - * Validation tag equivalent to an HTTP entity tag (E-Tag). "A strong entity tag - * may be shared by two entities of a resource only if they are equivalent by - * octet equality.
+ * Validation tag equivalent to an HTTP entity tag (E-Tag). A strong entity tag may be shared by two + * entities of a resource only if they are equivalent by octet equality.
*
- * A weak entity tag may be shared by two entities of a resource only if the - * entities are equivalent and could be substituted for each other with no - * significant change in semantics." - * + * A weak entity tag may be shared by two entities of a resource only if the entities are equivalent + * and could be substituted for each other with no significant change in semantics." + * * @see RepresentationInfo#getTag() - * @see HTTP - * Entity Tags - * @see HTTP - * Entity Tag Cache Validators + * @see HTTP Entity + * Tags + * @see HTTP Entity Tag + * Cache Validators * @author Jerome Louvel */ public final class Tag { - /** Tag matching any other tag, used in call's condition data. */ - public static final Tag ALL = Tag.parse("*"); - - /** - * Parses a tag formatted as defined by the HTTP standard. - * - * @param httpTag The HTTP tag string; if it starts with 'W/' the tag will be - * marked as weak and the data following the 'W/' used as the - * tag; otherwise it should be surrounded with quotes (e.g., - * "sometag"). - * @return A new tag instance. - * @see HTTP - * Entity Tags - */ - public static Tag parse(String httpTag) { - Tag result = null; - boolean weak = false; - String httpTagCopy = httpTag; - - if (httpTagCopy.startsWith("W/")) { - weak = true; - httpTagCopy = httpTagCopy.substring(2); - } - - if (httpTagCopy.startsWith("\"") && httpTagCopy.endsWith("\"")) { - result = new Tag(httpTagCopy.substring(1, httpTagCopy.length() - 1), weak); - } else if (httpTagCopy.equals("*")) { - result = new Tag("*", weak); - } else { - Context.getCurrentLogger().log(Level.WARNING, "Invalid tag format detected: " + httpTagCopy); - } - - return result; - } - - /** The name. */ - private volatile String name; - - /** The tag weakness. */ - private final boolean weak; - - /** - * Default constructor. The opaque tag is set to null and the weakness indicator - * is set to true. - */ - public Tag() { - this(null, true); - } - - /** - * Constructor of weak tags. - * - * @param opaqueTag The tag value. - */ - public Tag(String opaqueTag) { - this(opaqueTag, true); - } - - /** - * Constructor. - * - * @param opaqueTag The tag value. - * @param weak The weakness indicator. - */ - public Tag(final String opaqueTag, boolean weak) { - this.name = opaqueTag; - this.weak = weak; - } - - /** - * Indicates if both tags are equal. - * - * @param object The object to compare to. - * @return True if both tags are equal. - */ - @Override - public boolean equals(final Object object) { - return equals(object, true); - } - - /** - * Indicates if both tags are equal. - * - * @param object The object to compare to. - * @param checkWeakness The equality test takes care or not of the weakness. - * - * @return True if both tags are equal. - */ - public boolean equals(final Object object, boolean checkWeakness) { - if (object instanceof Tag that) { - if (checkWeakness && that.isWeak() != isWeak()) { - return false; - } - - return Objects.equals(getName(), that.getName()); + /** Tag matching any other tag used in call's condition data. */ + public static final Tag ALL = Tag.parse("*"); + + /** + * Parses a tag formatted as defined by the HTTP standard. + * + * @param httpTag The HTTP tag string; if it starts with 'W/' the tag will be marked as weak and + * the data following the 'W/' used as the tag; otherwise it should be surrounded with + * quotes (e.g., "sometag"). + * @return A new tag instance. + * @see HTTP Entity + * Tags + */ + public static Tag parse(String httpTag) { + Tag result = null; + boolean weak = false; + String httpTagCopy = httpTag; + + if (httpTagCopy.startsWith("W/")) { + weak = true; + httpTagCopy = httpTagCopy.substring(2); + } + + if (httpTagCopy.startsWith("\"") && httpTagCopy.endsWith("\"")) { + result = new Tag(httpTagCopy.substring(1, httpTagCopy.length() - 1), weak); + } else if (httpTagCopy.equals("*")) { + result = new Tag("*", weak); } else { + Context.getCurrentLogger() + .log(Level.WARNING, "Invalid tag format detected: {0}", httpTagCopy); + } + + return result; + } + + /** The name. */ + private final String name; + + /** The tag weakness. */ + private final boolean weak; + + /** + * Default constructor. The opaque tag is set to null, and the weakness indicator is set to + * true. + */ + public Tag() { + this(null, true); + } + + /** + * Constructor of weak tags. + * + * @param opaqueTag The tag value. + */ + public Tag(String opaqueTag) { + this(opaqueTag, true); + } + + /** + * Constructor. + * + * @param opaqueTag The tag value. + * @param weak The weakness indicator. + */ + public Tag(final String opaqueTag, boolean weak) { + this.name = opaqueTag; + this.weak = weak; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(final Object obj) { + return equals(obj, true); + } + + /** + * Indicates if both tags are equal. + * + * @param obj The object to compare to. + * @param checkWeakness The equality test takes care or not of the weakness. + * @return True if both tags are equal. + */ + public boolean equals(final Object obj, boolean checkWeakness) { + if (obj == this) { + return true; + } + if (!(obj instanceof Tag that)) { return false; } + return Objects.equals(getName(), that.getName()) + && (checkWeakness || isWeak() == that.isWeak()); } - /** - * Returns tag formatted as an HTTP tag string. - * - * @return The formatted HTTP tag string. - * @see HTTP - * Entity Tags - */ - public String format() { - if ("*".equals(getName())) { - return "*"; - } - - final StringBuilder sb = new StringBuilder(); - if (isWeak()) { - sb.append("W/"); - } - return sb.append('"').append(getName()).append('"').toString(); - } - - /** - * Returns the name, corresponding to an HTTP opaque tag value. - * - * @return The name, corresponding to an HTTP opaque tag value. - */ - public String getName() { - return this.name; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return format().hashCode(); - } - - /** - * Indicates if the tag is weak. - * - * @return True if the tag is weak, false if the tag is strong. - */ - public boolean isWeak() { - return this.weak; - } - - /** - * Returns the name. - * - * @return The name. - */ - @Override - public String toString() { - return getName(); - } + /** + * Returns tag formatted as an HTTP tag string. + * + * @return The formatted HTTP tag string. + * @see HTTP Entity + * Tags + */ + public String format() { + if ("*".equals(getName())) { + return "*"; + } + + final StringBuilder sb = new StringBuilder(); + if (isWeak()) { + sb.append("W/"); + } + return sb.append('"').append(getName()).append('"').toString(); + } + + /** + * Returns the name, corresponding to an HTTP opaque tag value. + * + * @return The name, corresponding to an HTTP opaque tag value. + */ + public String getName() { + return this.name; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return format().hashCode(); + } + + /** + * Indicates if the tag is weak. + * + * @return True if the tag is weak, false if the tag is strong. + */ + public boolean isWeak() { + return this.weak; + } + + /** + * Returns the name. + * + * @return The name. + */ + @Override + public String toString() { + return getName(); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Warning.java b/org.restlet/src/main/java/org/restlet/data/Warning.java index ef337faee7..adc5f678ff 100644 --- a/org.restlet/src/main/java/org/restlet/data/Warning.java +++ b/org.restlet/src/main/java/org/restlet/data/Warning.java @@ -1,120 +1,115 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; import java.util.Date; /** - * Additional information about the status or transformation of a request or - * response. This is typically used to warn about a possible issues with caching - * operations or transformations applied to the entity body.
+ * Additional information about the status or transformation of a request or response. This is + * typically used to warn about a possible issue with caching operations or transformations applied + * to the entity body.
*
- * Note that when used with HTTP connectors, this class maps to the "Warning" - * header. - * + * Note that when used with HTTP connectors, this class maps to the "Warning" header. + * * @author Jerome Louvel */ public class Warning { - /** The agent. Typically, a caching agent. */ - private volatile String agent; - - /** The warning date. */ - private volatile Date date; - - /** The special status. */ - private volatile Status status; - - /** The warning text. */ - private volatile String text; - - /** - * Constructor. - */ - public Warning() { - this.agent = null; - this.date = null; - this.status = null; - this.text = null; - } - - /** - * Returns the agent. Typically a caching agent. - * - * @return The agent. Typically a caching agent. - */ - public String getAgent() { - return agent; - } - - /** - * Returns the warning date. - * - * @return The warning date. - */ - public Date getDate() { - return date; - } - - /** - * Returns the special status. - * - * @return The special status. - */ - public Status getStatus() { - return status; - } - - /** - * Returns the warning text. - * - * @return The warning text. - */ - public String getText() { - return text; - } - - /** - * Sets the agent. Typically a caching agent. - * - * @param agent The agent. Typically a caching agent. - */ - public void setAgent(String agent) { - this.agent = agent; - } - - /** - * Sets the warning date. - * - * @param date The warning date. - */ - public void setDate(Date date) { - this.date = date; - } - - /** - * Sets the special status. - * - * @param status The special status. - */ - public void setStatus(Status status) { - this.status = status; - } - - /** - * Sets the warning text. - * - * @param text The warning text. - */ - public void setText(String text) { - this.text = text; - } - + /** The agent. Typically, a caching agent. */ + private volatile String agent; + + /** The warning date. */ + private volatile Date date; + + /** The special status. */ + private volatile Status status; + + /** The warning text. */ + private volatile String text; + + /** Constructor. */ + public Warning() { + this.agent = null; + this.date = null; + this.status = null; + this.text = null; + } + + /** + * Returns the agent. Typically, a caching agent. + * + * @return The agent. Typically, a caching agent. + */ + public String getAgent() { + return agent; + } + + /** + * Returns the warning date. + * + * @return The warning date. + */ + public Date getDate() { + return date; + } + + /** + * Returns the special status. + * + * @return The special status. + */ + public Status getStatus() { + return status; + } + + /** + * Returns the warning text. + * + * @return The warning text. + */ + public String getText() { + return text; + } + + /** + * Sets the agent. Typically, a caching agent. + * + * @param agent The agent. Typically, a caching agent. + */ + public void setAgent(String agent) { + this.agent = agent; + } + + /** + * Sets the warning date. + * + * @param date The warning date. + */ + public void setDate(Date date) { + this.date = date; + } + + /** + * Sets the special status. + * + * @param status The special status. + */ + public void setStatus(Status status) { + this.status = status; + } + + /** + * Sets the warning text. + * + * @param text The warning text. + */ + public void setText(String text) { + this.text = text; + } } diff --git a/org.restlet/src/main/java/org/restlet/data/package-info.java b/org.restlet/src/main/java/org/restlet/data/package-info.java new file mode 100644 index 0000000000..76c67eacf1 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/data/package-info.java @@ -0,0 +1,14 @@ +/** + * Information exchanged by components. "A datum is an element of information that is transferred + * from a component, or received by a component, via a connector." Roy T. Fielding. + * + * @since Restlet 1.0 + * @see Source + * dissertation + * @see User + * Guide - Mapping HTTP headers + * @see User Guide - + * Data package + */ +package org.restlet.data; diff --git a/org.restlet/src/main/java/org/restlet/data/package.html b/org.restlet/src/main/java/org/restlet/data/package.html deleted file mode 100644 index 1d104a79ca..0000000000 --- a/org.restlet/src/main/java/org/restlet/data/package.html +++ /dev/null @@ -1,12 +0,0 @@ - - - Information exchanged by components. "A datum is an element of information - that is transferred from a component, or received by a component, via a - connector." Roy T. Fielding. -

- @since Restlet 1.0 - @see Source dissertation - @see User Guide - Mapping HTTP headers - @see User Guide - Data package - - diff --git a/org.restlet/src/main/java/org/restlet/engine/CompositeHelper.java b/org.restlet/src/main/java/org/restlet/engine/CompositeHelper.java index 3642e818e7..46b35cb642 100644 --- a/org.restlet/src/main/java/org/restlet/engine/CompositeHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/CompositeHelper.java @@ -1,258 +1,257 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine; +import java.util.logging.Level; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.data.Status; import org.restlet.routing.Filter; -import java.util.logging.Level; - /** - * Chain helper serving as base class for Application and Component helpers. - * + * Chain helper serving as a base class for Application and Component helpers. + * * @author Jerome Louvel */ public abstract class CompositeHelper extends RestletHelper { - /** The first inbound filter. */ - private volatile Filter firstInboundFilter; - - /** The first outbound Filter. */ - private volatile Filter firstOutboundFilter; - - /** The next Restlet after the inbound chain. */ - private volatile Restlet inboundNext; - - /** The last inbound filter. */ - private volatile Filter lastInboundFilter; - - /** The last outbound filter. */ - private volatile Filter lastOutboundFilter; - - /** The next Restlet after the outbound chain. */ - private volatile Restlet outboundNext; - - /** - * Constructor. - * - * @param helped The helped Restlet. - */ - public CompositeHelper(T helped) { - super(helped); - this.inboundNext = null; - this.firstInboundFilter = null; - this.firstOutboundFilter = null; - this.lastInboundFilter = null; - this.lastOutboundFilter = null; - this.outboundNext = null; - } - - /** - * Adds a new inbound filter to the chain. - * - * @param filter The inbound filter to add. - */ - protected synchronized void addInboundFilter(Filter filter) { - Restlet next = getInboundNext(); - - if (getFirstInboundFilter() == null) { - setFirstInboundFilter(filter); - } else if (getLastInboundFilter() != null) { - getLastInboundFilter().setNext(filter); - } - - setLastInboundFilter(filter); - setInboundNext(next); - } - - /** - * Adds a new outbound filter to the chain. - * - * @param filter The outbound filter to add. - */ - protected synchronized void addOutboundFilter(Filter filter) { - Restlet next = getOutboundNext(); - - if (getFirstOutboundFilter() == null) { - setFirstOutboundFilter(filter); - } else if (getLastOutboundFilter() != null) { - getLastOutboundFilter().setNext(filter); - } - - setLastOutboundFilter(filter); - setOutboundNext(next); - } - - /** - * Clears the chain. Sets the first and last filters to null. - */ - public void clear() { - setFirstInboundFilter(null); - setFirstOutboundFilter(null); - setInboundNext(null); - setLastInboundFilter(null); - setLastOutboundFilter(null); - setOutboundNext(null); - } - - /** - * Returns the first inbound filter. - * - * @return The first inbound filter. - */ - public Filter getFirstInboundFilter() { - return firstInboundFilter; - } - - /** - * Returns the first outbound filter. - * - * @return The first outbound filter. - */ - public Filter getFirstOutboundFilter() { - return firstOutboundFilter; - } - - /** - * Returns the next Restlet in the inbound chain. - * - * @return The next Restlet in the inbound chain. - */ - protected synchronized Restlet getInboundNext() { - Restlet result = null; - - if (getLastInboundFilter() != null) { - result = getLastInboundFilter().getNext(); - } else { - result = this.inboundNext; - } - - return result; - } - - /** - * Returns the last inbound filter. - * - * @return the last inbound filter. - */ - protected Filter getLastInboundFilter() { - return this.lastInboundFilter; - } - - /** - * Returns the last outbound filter. - * - * @return the last outbound filter. - */ - protected Filter getLastOutboundFilter() { - return this.lastOutboundFilter; - } - - /** - * Returns the next Restlet in the outbound chain. - * - * @return The next Restlet in the outbound chain. - */ - public synchronized Restlet getOutboundNext() { - Restlet result = null; - - if (getLastOutboundFilter() != null) { - result = getLastOutboundFilter().getNext(); - } else { - result = this.outboundNext; - } - - return result; - } - - @Override - public void handle(Request request, Response response) { - super.handle(request, response); - - if (getFirstInboundFilter() != null) { - getFirstInboundFilter().handle(request, response); - } else { - final Restlet next = this.inboundNext; - if (next != null) { - next.handle(request, response); - } else { - response.setStatus(Status.SERVER_ERROR_INTERNAL); - getHelped().getLogger().log(Level.SEVERE, "The " + getHelped().getClass().getName() - + " class has no Restlet defined to process calls. Maybe it wasn't properly started."); - } - } - } - - /** - * Sets the first inbound filter. - * - * @param firstInboundFilter The first inbound filter. - */ - protected void setFirstInboundFilter(Filter firstInboundFilter) { - this.firstInboundFilter = firstInboundFilter; - } - - /** - * Sets the first outbound filter. - * - * @param firstOutboundFilter The first outbound filter. - */ - protected void setFirstOutboundFilter(Filter firstOutboundFilter) { - this.firstOutboundFilter = firstOutboundFilter; - } - - /** - * Sets the next Restlet after the inbound chain. - * - * @param next The Restlet to process after the inbound chain. - */ - protected synchronized void setInboundNext(Restlet next) { - if (getLastInboundFilter() != null) { - getLastInboundFilter().setNext(next); - } - - this.inboundNext = next; - } - - /** - * Sets the last inbound filter. - * - * @param last The last inbound filter. - */ - protected void setLastInboundFilter(Filter last) { - this.lastInboundFilter = last; - } - - /** - * Sets the last outbound filter. - * - * @param last The last outbound filter. - */ - protected void setLastOutboundFilter(Filter last) { - this.lastOutboundFilter = last; - } - - /** - * Sets the next Restlet after the outbound chain. - * - * @param next The Restlet to process after the outbound chain. - */ - protected synchronized void setOutboundNext(Restlet next) { - if (getLastOutboundFilter() != null) { - getLastOutboundFilter().setNext(next); - } - - this.outboundNext = next; - } - + /** The first inbound filter. */ + private volatile Filter firstInboundFilter; + + /** The first outbound Filter. */ + private volatile Filter firstOutboundFilter; + + /** The next Restlet after the inbound chain. */ + private volatile Restlet inboundNext; + + /** The last inbound filter. */ + private volatile Filter lastInboundFilter; + + /** The last outbound filter. */ + private volatile Filter lastOutboundFilter; + + /** The next Restlet after the outbound chain. */ + private volatile Restlet outboundNext; + + /** + * Constructor. + * + * @param helped The helped Restlet. + */ + protected CompositeHelper(T helped) { + super(helped); + this.inboundNext = null; + this.firstInboundFilter = null; + this.firstOutboundFilter = null; + this.lastInboundFilter = null; + this.lastOutboundFilter = null; + this.outboundNext = null; + } + + /** + * Adds a new inbound filter to the chain. + * + * @param filter The inbound filter to add. + */ + protected synchronized void addInboundFilter(Filter filter) { + Restlet next = getInboundNext(); + + if (getFirstInboundFilter() == null) { + setFirstInboundFilter(filter); + } else if (getLastInboundFilter() != null) { + getLastInboundFilter().setNext(filter); + } + + setLastInboundFilter(filter); + setInboundNext(next); + } + + /** + * Adds a new outbound filter to the chain. + * + * @param filter The outbound filter to add. + */ + protected synchronized void addOutboundFilter(Filter filter) { + Restlet next = getOutboundNext(); + + if (getFirstOutboundFilter() == null) { + setFirstOutboundFilter(filter); + } else if (getLastOutboundFilter() != null) { + getLastOutboundFilter().setNext(filter); + } + + setLastOutboundFilter(filter); + setOutboundNext(next); + } + + /** Clears the chain. Sets the first and last filters to null. */ + public void clear() { + setFirstInboundFilter(null); + setFirstOutboundFilter(null); + setInboundNext(null); + setLastInboundFilter(null); + setLastOutboundFilter(null); + setOutboundNext(null); + } + + /** + * Returns the first inbound filter. + * + * @return The first inbound filter. + */ + public Filter getFirstInboundFilter() { + return firstInboundFilter; + } + + /** + * Returns the first outbound filter. + * + * @return The first outbound filter. + */ + public Filter getFirstOutboundFilter() { + return firstOutboundFilter; + } + + /** + * Returns the next Restlet in the inbound chain. + * + * @return The next Restlet in the inbound chain. + */ + protected synchronized Restlet getInboundNext() { + final Restlet result; + + if (getLastInboundFilter() != null) { + result = getLastInboundFilter().getNext(); + } else { + result = this.inboundNext; + } + + return result; + } + + /** + * Returns the last inbound filter. + * + * @return the last inbound filter. + */ + protected Filter getLastInboundFilter() { + return this.lastInboundFilter; + } + + /** + * Returns the last outbound filter. + * + * @return the last outbound filter. + */ + protected Filter getLastOutboundFilter() { + return this.lastOutboundFilter; + } + + /** + * Returns the next Restlet in the outbound chain. + * + * @return The next Restlet in the outbound chain. + */ + public synchronized Restlet getOutboundNext() { + final Restlet result; + + if (getLastOutboundFilter() != null) { + result = getLastOutboundFilter().getNext(); + } else { + result = this.outboundNext; + } + + return result; + } + + @Override + public void handle(Request request, Response response) { + super.handle(request, response); + + if (getFirstInboundFilter() != null) { + getFirstInboundFilter().handle(request, response); + } else { + final Restlet next = this.inboundNext; + if (next != null) { + next.handle(request, response); + } else { + response.setStatus(Status.SERVER_ERROR_INTERNAL); + getHelped() + .getLogger() + .log( + Level.SEVERE, + "The {0} class has no Restlet defined to process calls. Maybe it wasn''t properly started.", + getHelped().getClass().getName()); + } + } + } + + /** + * Sets the first inbound filter. + * + * @param firstInboundFilter The first inbound filter. + */ + protected void setFirstInboundFilter(Filter firstInboundFilter) { + this.firstInboundFilter = firstInboundFilter; + } + + /** + * Sets the first outbound filter. + * + * @param firstOutboundFilter The first outbound filter. + */ + protected void setFirstOutboundFilter(Filter firstOutboundFilter) { + this.firstOutboundFilter = firstOutboundFilter; + } + + /** + * Sets the next Restlet after the inbound chain. + * + * @param next The Restlet to process after the inbound chain. + */ + protected synchronized void setInboundNext(Restlet next) { + if (getLastInboundFilter() != null) { + getLastInboundFilter().setNext(next); + } + + this.inboundNext = next; + } + + /** + * Sets the last inbound filter. + * + * @param last The last inbound filter. + */ + protected void setLastInboundFilter(Filter last) { + this.lastInboundFilter = last; + } + + /** + * Sets the last outbound filter. + * + * @param last The last outbound filter. + */ + protected void setLastOutboundFilter(Filter last) { + this.lastOutboundFilter = last; + } + + /** + * Sets the next Restlet after the outbound chain. + * + * @param next The Restlet to process after the outbound chain. + */ + protected synchronized void setOutboundNext(Restlet next) { + if (getLastOutboundFilter() != null) { + getLastOutboundFilter().setNext(next); + } + + this.outboundNext = next; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/Edition.java b/org.restlet/src/main/java/org/restlet/engine/Edition.java index d265706824..c88e91e1ca 100644 --- a/org.restlet/src/main/java/org/restlet/engine/Edition.java +++ b/org.restlet/src/main/java/org/restlet/engine/Edition.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine; /** @@ -16,88 +15,82 @@ */ public enum Edition { - /** - * Android mobile OS, JSE. - */ - ANDROID("Android", "Android", "Android"), - JSE("Java Standard Edition", "Java SE", "JSE"); + /** Android mobile OS, JSE. */ + ANDROID("Android", "Android", "Android"), + JSE("Java Standard Edition", "Java SE", "JSE"); - /** The current engine edition. */ - public static Edition CURRENT = Edition.JSE; + /** The current engine edition. */ + public static Edition CURRENT = Edition.JSE; - private final String fullName; - private final String mediumName; - private final String shortName; + private final String fullName; + private final String mediumName; + private final String shortName; - Edition(final String fullName, final String mediumName, final String shortName) { - this.fullName = fullName; - this.mediumName = mediumName; - this.shortName = shortName; - } + Edition(final String fullName, final String mediumName, final String shortName) { + this.fullName = fullName; + this.mediumName = mediumName; + this.shortName = shortName; + } - /** - * Returns the full size name of the edition. - * - * @return The full size of the edition. - */ - public String getFullName() { - return fullName; - } + /** + * Returns the full size name of the edition. + * + * @return The full size of the edition. + */ + public String getFullName() { + return fullName; + } - /** - * Returns the medium size name of the edition. - * - * @return The medium size name of the edition. - */ - public String getMediumName() { - return mediumName; - } + /** + * Returns the medium size name of the edition. + * + * @return The medium size name of the edition. + */ + public String getMediumName() { + return mediumName; + } - /** - * Returns the short size name of the edition. - * - * @return The short size name of the edition. - */ - public String getShortName() { - return shortName; - } - - /** - * Returns true if this edition is the current one. - * - * @return True if this edition is the current one. - */ - public boolean isCurrentEdition() { - return this == CURRENT; - } - - /** - * Returns true if this edition is not the current one. - * - * @return True if this edition is not the current one. - */ - public boolean isNotCurrentEdition() { - return this != CURRENT; - } + /** + * Returns the short size name of the edition. + * + * @return The short size name of the edition. + */ + public String getShortName() { + return shortName; + } - public static boolean isCurrentEditionOneOf(Edition... editions) { - boolean result = false; + /** + * Returns true if this edition is the current one. + * + * @return True if this edition is the current one. + */ + public boolean isCurrentEdition() { + return this == CURRENT; + } - if (editions != null) { - for (int i = 0; i < editions.length && !result; i++) { - result = editions[i].isCurrentEdition(); - } - } + /** + * Returns true if this edition is not the current one. + * + * @return True if this edition is not the current one. + */ + public boolean isNotCurrentEdition() { + return this != CURRENT; + } - return result; - } + public static boolean isCurrentEditionOneOf(Edition... editions) { + boolean result = false; - /** - * Set this edition as the current one. - */ - public void setCurrentEdition() { - CURRENT = this; - } + if (editions != null) { + for (int i = 0; i < editions.length && !result; i++) { + result = editions[i].isCurrentEdition(); + } + } + return result; + } + /** Set this edition as the current one. */ + public void setCurrentEdition() { + CURRENT = this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/Engine.java b/org.restlet/src/main/java/org/restlet/engine/Engine.java index e915ca0344..384527271f 100644 --- a/org.restlet/src/main/java/org/restlet/engine/Engine.java +++ b/org.restlet/src/main/java/org/restlet/engine/Engine.java @@ -1,18 +1,20 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine; +import static java.net.URL.setURLStreamHandlerFactory; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; @@ -22,60 +24,56 @@ import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; - import org.restlet.Client; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; +import org.restlet.Server; import org.restlet.data.ChallengeScheme; import org.restlet.data.Method; import org.restlet.data.Protocol; +import org.restlet.engine.connector.ConnectorHelper; import org.restlet.engine.io.IoUtils; import org.restlet.engine.log.LoggerFacade; /** - * Engine supporting the Restlet API. The engine acts as a registry of various - * {@link Helper} types: {@link org.restlet.engine.security.AuthenticatorHelper} - * , {@link org.restlet.engine.connector.ClientHelper}, - * {@link org.restlet.engine.converter.ConverterHelper} and - * {@link org.restlet.engine.connector.ServerHelper} classes.
+ * Engine supporting the Restlet API. The engine acts as a registry of various {@link Helper} types: + * {@link org.restlet.engine.security.AuthenticatorHelper} , {@link + * org.restlet.engine.connector.ClientHelper}, {@link org.restlet.engine.converter.ConverterHelper} + * and {@link org.restlet.engine.connector.ServerHelper} classes.
*
- * Note that by default the JULI logging mechanism is used but it is possible to - * replace it by providing an alternate {@link LoggerFacade} implementation. For - * this, just pass a system property named - * "org.restlet.engine.loggerFacadeClass" with the qualified class name as a - * value. - * + * Note that by default the JULI logging mechanism is used but it is possible to replace it by + * providing an alternate {@link LoggerFacade} implementation. For this, just pass a system property + * named "org.restlet.engine.loggerFacadeClass" with the qualified class name as a value. + * * @author Jerome Louvel */ public class Engine { public static final String DESCRIPTOR = "META-INF/services"; - public static final String DESCRIPTOR_AUTHENTICATOR = "org.restlet.engine.security.AuthenticatorHelper"; + public static final String DESCRIPTOR_AUTHENTICATOR = + "org.restlet.engine.security.AuthenticatorHelper"; - public static final String DESCRIPTOR_AUTHENTICATOR_PATH = DESCRIPTOR + "/" - + DESCRIPTOR_AUTHENTICATOR; + public static final String DESCRIPTOR_AUTHENTICATOR_PATH = + DESCRIPTOR + "/" + DESCRIPTOR_AUTHENTICATOR; public static final String DESCRIPTOR_CLIENT = "org.restlet.engine.ClientHelper"; - public static final String DESCRIPTOR_CLIENT_PATH = DESCRIPTOR + "/" - + DESCRIPTOR_CLIENT; + public static final String DESCRIPTOR_CLIENT_PATH = DESCRIPTOR + "/" + DESCRIPTOR_CLIENT; - public static final String DESCRIPTOR_CONVERTER = "org.restlet.engine.converter.ConverterHelper"; + public static final String DESCRIPTOR_CONVERTER = + "org.restlet.engine.converter.ConverterHelper"; - public static final String DESCRIPTOR_CONVERTER_PATH = DESCRIPTOR + "/" - + DESCRIPTOR_CONVERTER; + public static final String DESCRIPTOR_CONVERTER_PATH = DESCRIPTOR + "/" + DESCRIPTOR_CONVERTER; public static final String DESCRIPTOR_PROTOCOL = "org.restlet.engine.ProtocolHelper"; - public static final String DESCRIPTOR_PROTOCOL_PATH = DESCRIPTOR + "/" - + DESCRIPTOR_PROTOCOL; + public static final String DESCRIPTOR_PROTOCOL_PATH = DESCRIPTOR + "/" + DESCRIPTOR_PROTOCOL; public static final String DESCRIPTOR_SERVER = "org.restlet.engine.ServerHelper"; - public static final String DESCRIPTOR_SERVER_PATH = DESCRIPTOR + "/" - + DESCRIPTOR_SERVER; + public static final String DESCRIPTOR_SERVER_PATH = DESCRIPTOR + "/" + DESCRIPTOR_SERVER; /** The registered engine. */ private static volatile Engine instance = null; @@ -84,7 +82,8 @@ public class Engine { private static volatile boolean logConfigured = false; /** The general log formatter. */ - private static volatile Class logFormatter = org.restlet.engine.log.SimplestFormatter.class; + private static volatile Class logFormatter = + org.restlet.engine.log.SimplestFormatter.class; /** The general log level. */ private static volatile Level logLevel = Level.INFO; @@ -102,64 +101,50 @@ public class Engine { private static volatile Level restletLogLevel; /** Complete version. */ - public static final String VERSION = MAJOR_NUMBER + '.' + MINOR_NUMBER - + RELEASE_NUMBER; + public static final String VERSION = MAJOR_NUMBER + '.' + MINOR_NUMBER + RELEASE_NUMBER; /** Complete version header. */ public static final String VERSION_HEADER = "Restlet-Framework/" + VERSION; - /** - * Clears the current Restlet Engine altogether. - */ + /** Clears the current Restlet Engine altogether. */ public static synchronized void clear() { instance = null; } /** - * Creates a new standalone thread with local Restlet thread variable - * properly set. - * + * Creates a new standalone thread with the local Restlet thread variable properly set. + * * @param runnable The runnable task to execute. - * @param name The thread name. - * @return The thread with proper variables ready to run the given runnable - * task. + * @param name The thread name. + * @return The thread with proper variables ready to run the given runnable task. */ - public static Thread createThreadWithLocalVariables(final Runnable runnable, - String name) { + public static Thread createThreadWithLocalVariables(final Runnable runnable, String name) { // Save the thread local variables - final org.restlet.Application currentApplication = org.restlet.Application - .getCurrent(); + final org.restlet.Application currentApplication = org.restlet.Application.getCurrent(); final Context currentContext = Context.getCurrent(); - final Integer currentVirtualHost = org.restlet.routing.VirtualHost - .getCurrent(); + final Integer currentVirtualHost = org.restlet.routing.VirtualHost.getCurrent(); final Response currentResponse = Response.getCurrent(); - Runnable r = new Runnable() { - - @Override - public void run() { - // Copy the thread local variables - Response.setCurrent(currentResponse); - Context.setCurrent(currentContext); - org.restlet.routing.VirtualHost.setCurrent(currentVirtualHost); - org.restlet.Application.setCurrent(currentApplication); - - try { - // Run the user task - runnable.run(); - } finally { - Engine.clearThreadLocalVariables(); - } - } - - }; + Runnable r = + () -> { + // Copy the thread local variables + Response.setCurrent(currentResponse); + Context.setCurrent(currentContext); + org.restlet.routing.VirtualHost.setCurrent(currentVirtualHost); + org.restlet.Application.setCurrent(currentApplication); + + try { + // Run the user task + runnable.run(); + } finally { + Engine.clearThreadLocalVariables(); + } + }; return new Thread(r, name); } - /** - * Clears the thread local variables set by the Restlet API and engine. - */ + /** Clears the thread local variables set by the Restlet API and engine. */ public static void clearThreadLocalVariables() { Response.setCurrent(null); Context.setCurrent(null); @@ -167,46 +152,43 @@ public static void clearThreadLocalVariables() { org.restlet.Application.setCurrent(null); } - /** - * Updates the global log configuration of the JVM programmatically. - */ + /** Updates the global log configuration of the JVM programmatically. */ public static void configureLog() { if ((System.getProperty("java.util.logging.config.file") == null) - && (System.getProperty( - "java.util.logging.config.class") == null)) { + && (System.getProperty("java.util.logging.config.class") == null)) { StringBuilder sb = new StringBuilder(); - sb.append("handlers=").append( - java.util.logging.ConsoleHandler.class.getCanonicalName()) + sb.append("handlers=") + .append(java.util.logging.ConsoleHandler.class.getCanonicalName()) .append('\n'); if (getLogLevel() != null) { - sb.append(".level=").append(getLogLevel().getName()) - .append('\n'); + sb.append(".level=").append(getLogLevel().getName()).append('\n'); } if (getRestletLogLevel() != null) { - sb.append("org.restlet.level=") - .append(getRestletLogLevel().getName()).append('\n'); + sb.append("org.restlet.level=").append(getRestletLogLevel().getName()).append('\n'); } if (getLogFormatter() != null) { - String handler = java.util.logging.ConsoleHandler.class - .getCanonicalName(); - sb.append(handler).append(".formatter=") + String handler = java.util.logging.ConsoleHandler.class.getCanonicalName(); + sb.append(handler) + .append(".formatter=") .append(getLogFormatter().getCanonicalName()) .append("\n"); if (getLogLevel() != null) { - sb.append(handler).append(".level=") - .append(getLogLevel().getName()).append("\n"); + sb.append(handler) + .append(".level=") + .append(getLogLevel().getName()) + .append("\n"); } } try { - LogManager.getLogManager().readConfiguration( - new ByteArrayInputStream(sb.toString().getBytes())); - } catch (Throwable t) { - t.printStackTrace(); + LogManager.getLogManager() + .readConfiguration(new ByteArrayInputStream(sb.toString().getBytes())); + } catch (Exception exception) { + exception.printStackTrace(); } } @@ -214,9 +196,8 @@ public static void configureLog() { } /** - * Returns an anonymous logger. By default, it calls - * {@link #getLogger(String)} with a "" name. - * + * Returns an anonymous logger. By default, it calls {@link #getLogger(String)} with a "" name. + * * @return The logger. */ public static Logger getAnonymousLogger() { @@ -225,7 +206,7 @@ public static Logger getAnonymousLogger() { /** * Returns the registered Restlet engine. - * + * * @return The registered Restlet engine. */ public static synchronized Engine getInstance() { @@ -240,7 +221,7 @@ public static synchronized Engine getInstance() { /** * Returns the general log formatter. - * + * * @return The general log formatter. */ public static Class getLogFormatter() { @@ -249,7 +230,7 @@ public static Class getLogFormatter() { /** * Returns a logger based on the class name of the given object. - * + * * @param clazz The parent class. * @return The logger. */ @@ -259,33 +240,31 @@ public static Logger getLogger(Class clazz) { /** * Returns a logger based on the class name of the given object. - * - * @param clazz The parent class. - * @param defaultLoggerName The default logger name to use if no one can be - * inferred from the class. + * + * @param clazz The parent class. + * @param defaultLoggerName The default logger name to use if no one can be inferred from the + * class. * @return The logger. */ public static Logger getLogger(Class clazz, String defaultLoggerName) { - return getInstance().getLoggerFacade().getLogger(clazz, - defaultLoggerName); + return getInstance().getLoggerFacade().getLogger(clazz, defaultLoggerName); } /** * Returns a logger based on the class name of the given object. - * - * @param object The parent object. - * @param defaultLoggerName The default logger name to use if no one can be - * inferred from the object class. + * + * @param object The parent object. + * @param defaultLoggerName The default logger name to use if no one can be inferred from the + * object class. * @return The logger. */ public static Logger getLogger(Object object, String defaultLoggerName) { - return getInstance().getLoggerFacade().getLogger(object, - defaultLoggerName); + return getInstance().getLoggerFacade().getLogger(object, defaultLoggerName); } /** * Returns a logger based on the given logger name. - * + * * @param loggerName The logger name. * @return The logger. */ @@ -295,7 +274,7 @@ public static Logger getLogger(String loggerName) { /** * Returns the general log level. - * + * * @return The general log level. */ public static Level getLogLevel() { @@ -304,7 +283,7 @@ public static Level getLogLevel() { /** * Returns the classloader resource for a given name/path. - * + * * @param name The name/path to lookup. * @return The resource URL. */ @@ -313,9 +292,8 @@ public static java.net.URL getResource(String name) { } /** - * Returns the Restlet log level. For loggers with a name starting with - * "org.restlet". - * + * Returns the Restlet log level. For loggers with a name starting with "org.restlet". + * * @return The Restlet log level. */ public static Level getRestletLogLevel() { @@ -324,19 +302,18 @@ public static Level getRestletLogLevel() { /** * Returns the class object for the given name using the engine classloader. - * + * * @param className The class name to lookup. * @return The class object or null if the class was not found. * @see #getClassLoader() */ - public static Class loadClass(String className) - throws ClassNotFoundException { + public static Class loadClass(String className) throws ClassNotFoundException { return getInstance().getClassLoader().loadClass(className); } /** * Registers a new Restlet Engine. - * + * * @return The registered engine. */ public static synchronized Engine register() { @@ -345,9 +322,8 @@ public static synchronized Engine register() { /** * Registers a new Restlet Engine. - * - * @param discoverPlugins True if plug-ins should be automatically - * discovered. + * + * @param discoverPlugins True if plug-ins should be automatically discovered. * @return The registered engine. */ public static synchronized Engine register(boolean discoverPlugins) { @@ -362,18 +338,17 @@ public static synchronized Engine register(boolean discoverPlugins) { /** * Sets the general log formatter. - * + * * @param logFormatter The general log formatter. */ - public static void setLogFormatter( - Class logFormatter) { + public static void setLogFormatter(Class logFormatter) { Engine.logFormatter = logFormatter; configureLog(); } /** * Sets the general log level. Modifies the global JVM's {@link LogManager}. - * + * * @param logLevel The general log level. */ public static void setLogLevel(Level logLevel) { @@ -382,9 +357,8 @@ public static void setLogLevel(Level logLevel) { } /** - * Sets the Restlet log level. For loggers with a name starting with - * "org.restlet". - * + * Sets the Restlet log level. For loggers with a name starting with "org.restlet". + * * @param restletLogLevel The Restlet log level. */ public static void setRestletLogLevel(Level restletLogLevel) { @@ -411,23 +385,20 @@ public static void setRestletLogLevel(Level restletLogLevel) { private final List registeredProtocols; /** List of available server connectors. */ - private final List> registeredServers; + private final List> registeredServers; /** User class loader to use for dynamic class loading. */ private volatile ClassLoader userClassLoader; - /** - * Constructor that will automatically attempt to discover connectors. - */ + /** Constructor that will automatically attempt to discover connectors. */ public Engine() { this(true); } /** * Constructor. - * - * @param discoverHelpers True if helpers should be automatically - * discovered. + * + * @param discoverHelpers True if helpers should be automatically discovered. */ public Engine(boolean discoverHelpers) { // Prevent engine initialization code from recreating other engines @@ -437,17 +408,22 @@ public Engine(boolean discoverHelpers) { this.classLoader = createClassLoader(); this.userClassLoader = null; - String loggerFacadeClass = System.getProperty( - "org.restlet.engine.loggerFacadeClass", - "org.restlet.engine.log.LoggerFacade"); + String loggerFacadeClass = + System.getProperty( + "org.restlet.engine.loggerFacadeClass", + "org.restlet.engine.log.LoggerFacade"); try { - this.loggerFacade = (LoggerFacade) getClassLoader() - .loadClass(loggerFacadeClass).getDeclaredConstructor() - .newInstance(); + this.loggerFacade = + (LoggerFacade) + getClassLoader() + .loadClass(loggerFacadeClass) + .getDeclaredConstructor() + .newInstance(); } catch (Exception e) { this.loggerFacade = new LoggerFacade(); - this.loggerFacade.getLogger("org.restlet").log(Level.WARNING, - "Unable to register the logger facade", e); + this.loggerFacade + .getLogger("org.restlet") + .log(Level.WARNING, "Unable to register the logger facade", e); } this.registeredClients = new CopyOnWriteArrayList<>(); @@ -465,17 +441,19 @@ public Engine(boolean discoverHelpers) { discoverAuthenticators(); discoverConverters(); } catch (IOException e) { - Context.getCurrentLogger().log(Level.WARNING, - "An error occurred while discovering the engine helpers.", - e); + Context.getCurrentLogger() + .log( + Level.WARNING, + "An error occurred while discovering the engine helpers.", + e); } } } /** - * Creates a new class loader. By default, it returns an instance of - * {@link org.restlet.engine.util.EngineClassLoader}. - * + * Creates a new class loader. By default, it returns an instance of {@link + * org.restlet.engine.util.EngineClassLoader}. + * * @return A new class loader. */ protected ClassLoader createClassLoader() { @@ -484,8 +462,8 @@ protected ClassLoader createClassLoader() { /** * Creates a new helper for a given client connector. - * - * @param client The client to help. + * + * @param client The client to help. * @param helperClass Optional helper class name. * @return The new helper. */ @@ -496,23 +474,26 @@ public org.restlet.engine.connector.ConnectorHelper createHelper( if (!client.getProtocols().isEmpty()) { org.restlet.engine.connector.ConnectorHelper connector = null; - for (final Iterator> iter = getRegisteredClients() - .iterator(); (result == null) && iter.hasNext();) { + for (final Iterator> iter = + getRegisteredClients().iterator(); + (result == null) && iter.hasNext(); ) { connector = iter.next(); - if (new HashSet<>(connector.getProtocols()) - .containsAll(client.getProtocols())) { - if ((helperClass == null) || connector.getClass() - .getCanonicalName().equals(helperClass)) { - try { - result = connector.getClass() - .getConstructor(Client.class) - .newInstance(client); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.SEVERE, - "Exception during the instantiation of the client connector.", - e); - } + if (new HashSet<>(connector.getProtocols()).containsAll(client.getProtocols()) + && ((helperClass == null) + || connector.getClass().getCanonicalName().equals(helperClass))) { + try { + result = + connector + .getClass() + .getConstructor(Client.class) + .newInstance(client); + } catch (Exception e) { + Context.getCurrentLogger() + .log( + Level.SEVERE, + "Exception during the instantiation of the client connector.", + e); } } } @@ -520,22 +501,19 @@ public org.restlet.engine.connector.ConnectorHelper createHelper( if (result == null) { // Couldn't find a matching connector StringBuilder sb = new StringBuilder(); - sb.append( - "No available client connector supports the required protocols: "); + sb.append("No available client connector supports the required protocols: "); for (Protocol p : client.getProtocols()) { sb.append("'").append(p.getName()).append("' "); } - sb.append( - ". Please add the JAR of a matching connector to your classpath."); + sb.append(". Please add the JAR of a matching connector to your classpath."); if (Edition.ANDROID.isCurrentEdition()) { - sb.append( - " Then, register this connector helper manually."); + sb.append(" Then, register this connector helper manually."); } - Context.getCurrentLogger().log(Level.WARNING, sb.toString()); + Context.getCurrentLogger().log(Level.WARNING, sb::toString); } } @@ -544,35 +522,37 @@ public org.restlet.engine.connector.ConnectorHelper createHelper( /** * Creates a new helper for a given server connector. - * - * @param server The server to help. + * + * @param server The server to help. * @param helperClass Optional helper class name. * @return The new helper. */ @SuppressWarnings("unchecked") - public org.restlet.engine.connector.ConnectorHelper createHelper( - org.restlet.Server server, String helperClass) { - org.restlet.engine.connector.ConnectorHelper result = null; + public ConnectorHelper createHelper(Server server, String helperClass) { + ConnectorHelper result = null; if (!server.getProtocols().isEmpty()) { - org.restlet.engine.connector.ConnectorHelper connector = null; - for (final Iterator> iter = getRegisteredServers() - .iterator(); (result == null) && iter.hasNext();) { + ConnectorHelper connector = null; + for (final Iterator> iter = getRegisteredServers().iterator(); + (result == null) && iter.hasNext(); ) { connector = iter.next(); - if ((helperClass == null) || connector.getClass() - .getCanonicalName().equals(helperClass)) { - if (new HashSet<>(connector.getProtocols()) - .containsAll(server.getProtocols())) { - try { - result = connector.getClass() - .getConstructor(org.restlet.Server.class) - .newInstance(server); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.SEVERE, - "Exception while instantiation the server connector.", - e); - } + if (((helperClass == null) + || connector.getClass().getCanonicalName().equals(helperClass)) + && new HashSet<>(connector.getProtocols()) + .containsAll(server.getProtocols())) { + try { + result = + connector + .getClass() + .getConstructor(Server.class) + .newInstance(server); + } catch (Exception e) { + Context.getCurrentLogger() + .log( + Level.SEVERE, + "Exception while instantiation the server connector.", + e); } } } @@ -580,22 +560,19 @@ public org.restlet.engine.connector.ConnectorHelper createHe if (result == null) { // Couldn't find a matching connector final StringBuilder sb = new StringBuilder(); - sb.append( - "No available server connector supports the required protocols: "); + sb.append("No available server connector supports the required protocols: "); for (final Protocol p : server.getProtocols()) { sb.append("'").append(p.getName()).append("' "); } - sb.append( - ". Please add the JAR of a matching connector to your classpath."); + sb.append(". Please add the JAR of a matching connector to your classpath."); if (Edition.ANDROID.isCurrentEdition()) { - sb.append( - " Then, register this connector helper manually."); + sb.append(" Then, register this connector helper manually."); } - Context.getCurrentLogger().log(Level.WARNING, sb.toString()); + Context.getCurrentLogger().log(Level.WARNING, sb::toString); } } @@ -604,54 +581,48 @@ public org.restlet.engine.connector.ConnectorHelper createHe /** * Discovers the authenticator helpers and register the default helpers. - * + * * @throws IOException */ private void discoverAuthenticators() throws IOException { - registerHelpers(DESCRIPTOR_AUTHENTICATOR_PATH, - getRegisteredAuthenticators(), null); + registerHelpers(DESCRIPTOR_AUTHENTICATOR_PATH, getRegisteredAuthenticators(), null); registerDefaultAuthentications(); } /** - * Discovers the server and client connectors and register the default - * connectors. - * + * Discovers the server and client connectors and register the default connectors. + * * @throws IOException */ private void discoverConnectors() throws IOException { - registerHelpers(DESCRIPTOR_CLIENT_PATH, getRegisteredClients(), - Client.class); - registerHelpers(DESCRIPTOR_SERVER_PATH, getRegisteredServers(), - org.restlet.Server.class); + registerHelpers(DESCRIPTOR_CLIENT_PATH, getRegisteredClients(), Client.class); + registerHelpers(DESCRIPTOR_SERVER_PATH, getRegisteredServers(), Server.class); registerDefaultConnectors(); } /** * Discovers the converter helpers and register the default helpers. - * + * * @throws IOException */ private void discoverConverters() throws IOException { - registerHelpers(DESCRIPTOR_CONVERTER_PATH, getRegisteredConverters(), - null); + registerHelpers(DESCRIPTOR_CONVERTER_PATH, getRegisteredConverters(), null); registerDefaultConverters(); } /** * Discovers the protocol helpers and register the default helpers. - * + * * @throws IOException */ private void discoverProtocols() throws IOException { - registerHelpers(DESCRIPTOR_PROTOCOL_PATH, getRegisteredProtocols(), - null); + registerHelpers(DESCRIPTOR_PROTOCOL_PATH, getRegisteredProtocols(), null); registerDefaultProtocols(); } /** * Finds the converter helper supporting the given conversion. - * + * * @return The converter helper or null. */ public org.restlet.engine.converter.ConverterHelper findHelper() { @@ -661,17 +632,17 @@ public org.restlet.engine.converter.ConverterHelper findHelper() { /** * Finds the authenticator helper supporting the given scheme. - * + * * @param challengeScheme The challenge scheme to match. - * @param clientSide Indicates if client side support is required. - * @param serverSide Indicates if server side support is required. + * @param clientSide Indicates if client side support is required. + * @param serverSide Indicates if server side support is required. * @return The authenticator helper or null. */ public org.restlet.engine.security.AuthenticatorHelper findHelper( - ChallengeScheme challengeScheme, boolean clientSide, - boolean serverSide) { + ChallengeScheme challengeScheme, boolean clientSide, boolean serverSide) { org.restlet.engine.security.AuthenticatorHelper result = null; - List helpers = getRegisteredAuthenticators(); + List helpers = + getRegisteredAuthenticators(); org.restlet.engine.security.AuthenticatorHelper current; for (int i = 0; (result == null) && (i < helpers.size()); i++) { @@ -688,12 +659,11 @@ public org.restlet.engine.security.AuthenticatorHelper findHelper( } /** - * Returns the class loader. It uses the delegation model with the Engine - * class's class loader as a parent. If this parent doesn't find a class or - * resource, it then tries the user class loader (via - * {@link #getUserClassLoader()} and finally the - * {@link Thread#getContextClassLoader()}. - * + * Returns the class loader. It uses the delegation model with the Engine class's class loader + * as a parent. If this parent doesn't find a class or resource, it then tries the user class + * loader (via {@link #getUserClassLoader()} and finally the {@link + * Thread#getContextClassLoader()}. + * * @return The engine class loader. * @see org.restlet.engine.util.EngineClassLoader */ @@ -703,7 +673,7 @@ public ClassLoader getClassLoader() { /** * Returns the logger facade to use. - * + * * @return The logger facade to use. */ public LoggerFacade getLoggerFacade() { @@ -712,7 +682,7 @@ public LoggerFacade getLoggerFacade() { /** * Parses a line to extract the provider class name. - * + * * @param line The line to parse. * @return The provider's class name or an empty string. */ @@ -726,7 +696,7 @@ private String getProviderClassName(String line) { /** * Returns the list of available authentication helpers. - * + * * @return The list of available authentication helpers. */ public List getRegisteredAuthenticators() { @@ -735,7 +705,7 @@ public List getRegisteredAuthen /** * Returns the list of available client connectors. - * + * * @return The list of available client connectors. */ public List> getRegisteredClients() { @@ -744,7 +714,7 @@ public List> getRegisteredC /** * Returns the list of available converters. - * + * * @return The list of available converters. */ public List getRegisteredConverters() { @@ -753,7 +723,7 @@ public List getRegisteredConverter /** * Returns the list of available protocol connectors. - * + * * @return The list of available protocol connectors. */ public List getRegisteredProtocols() { @@ -762,225 +732,189 @@ public List getRegisteredProtocols( /** * Returns the list of available server connectors. - * + * * @return The list of available server connectors. */ - public List> getRegisteredServers() { + public List> getRegisteredServers() { return this.registeredServers; } /** - * Returns the class loader specified by the user and that should be used in - * priority. - * + * Returns the class loader specified by the user and that should be used in priority. + * * @return The user class loader */ public ClassLoader getUserClassLoader() { return userClassLoader; } - /** - * Registers the default authentication helpers. - */ + /** Registers the default authentication helpers. */ public void registerDefaultAuthentications() { - getRegisteredAuthenticators() - .add(new org.restlet.engine.security.HttpBasicHelper()); + getRegisteredAuthenticators().add(new org.restlet.engine.security.HttpBasicHelper()); } - /** - * Registers the default client and server connectors. - */ + /** Registers the default client and server connectors. */ public void registerDefaultConnectors() { - getRegisteredClients() - .add(new org.restlet.engine.local.ClapClientHelper(null)); - getRegisteredClients() - .add(new org.restlet.engine.local.RiapClientHelper(null)); - getRegisteredServers() - .add(new org.restlet.engine.local.RiapServerHelper(null)); - getRegisteredServers() - .add(new org.restlet.engine.connector.HttpServerHelper(null)); - getRegisteredServers() - .add(new org.restlet.engine.connector.HttpsServerHelper(null)); - getRegisteredClients() - .add(new org.restlet.engine.local.FileClientHelper(null)); - getRegisteredClients() - .add(new org.restlet.engine.local.ZipClientHelper(null)); - getRegisteredClients() - .add(new org.restlet.engine.connector.HttpClientHelper(null)); + getRegisteredClients().add(new org.restlet.engine.local.ClapClientHelper(null)); + getRegisteredClients().add(new org.restlet.engine.local.RiapClientHelper(null)); + getRegisteredServers().add(new org.restlet.engine.local.RiapServerHelper(null)); + getRegisteredServers().add(new org.restlet.engine.connector.HttpServerHelper(null)); + getRegisteredServers().add(new org.restlet.engine.connector.HttpsServerHelper(null)); + getRegisteredClients().add(new org.restlet.engine.local.FileClientHelper(null)); + getRegisteredClients().add(new org.restlet.engine.local.ZipClientHelper(null)); + getRegisteredClients().add(new org.restlet.engine.connector.HttpClientHelper(null)); } - /** - * Registers the default converters. - */ + /** Registers the default converters. */ public void registerDefaultConverters() { - getRegisteredConverters() - .add(new org.restlet.engine.converter.DefaultConverter()); - getRegisteredConverters().add( - new org.restlet.engine.converter.StatusInfoHtmlConverter()); + getRegisteredConverters().add(new org.restlet.engine.converter.DefaultConverter()); + getRegisteredConverters().add(new org.restlet.engine.converter.StatusInfoHtmlConverter()); } - /** - * Registers the default protocols. - */ + /** Registers the default protocols. */ public void registerDefaultProtocols() { - getRegisteredProtocols() - .add(new org.restlet.engine.connector.HttpProtocolHelper()); + getRegisteredProtocols().add(new org.restlet.engine.connector.HttpProtocolHelper()); } /** * Registers a helper. - * - * @param classLoader The classloader to use. - * @param provider Bynary name of the helper's class. - * @param helpers The list of helpers to update. + * + * @param classLoader The classloader to use. + * @param provider Bynary name of the helper's class. + * @param helpers The list of helpers to update. * @param constructorClass The constructor parameter class to look for. */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void registerHelper(ClassLoader classLoader, String provider, - List helpers, Class constructorClass) { + @SuppressWarnings({"unchecked", "rawtypes"}) + public void registerHelper( + ClassLoader classLoader, String provider, List helpers, Class constructorClass) { if ((provider != null) && (!provider.isEmpty())) { // Instantiate the factory try { Class providerClass = classLoader.loadClass(provider); if (constructorClass == null) { - helpers.add(providerClass.getDeclaredConstructor() - .newInstance()); + helpers.add(providerClass.getDeclaredConstructor().newInstance()); } else { - helpers.add(providerClass.getConstructor(constructorClass) - .newInstance(constructorClass.cast(null))); + helpers.add( + providerClass + .getConstructor(constructorClass) + .newInstance(constructorClass.cast(null))); } - } catch (Throwable t) { - Context.getCurrentLogger().log(Level.INFO, - "Unable to register the helper " + provider, t); + } catch (Exception exception) { + Context.getCurrentLogger() + .log( + Level.INFO, + exception, + () -> "Unable to register the helper " + provider); } } } /** * Registers a helper. - * - * @param classLoader The classloader to use. - * @param configUrl Configuration URL to parse - * @param helpers The list of helpers to update. + * + * @param classLoader The classloader to use. + * @param configUrl Configuration URL to parse + * @param helpers The list of helpers to update. * @param constructorClass The constructor parameter class to look for. */ - public void registerHelpers(ClassLoader classLoader, java.net.URL configUrl, - List helpers, Class constructorClass) { - try { - java.io.BufferedReader reader = null; - try { - reader = new java.io.BufferedReader( - new InputStreamReader(configUrl.openStream(), "utf-8"), - IoUtils.BUFFER_SIZE); - String line = reader.readLine(); - - while (line != null) { - registerHelper(classLoader, getProviderClassName(line), - helpers, constructorClass); - line = reader.readLine(); - } - } catch (IOException e) { - Context.getCurrentLogger().log(Level.SEVERE, - "Unable to read the provider descriptor: " - + configUrl.toString()); - } finally { - if (reader != null) { - reader.close(); - } + public void registerHelpers( + ClassLoader classLoader, + java.net.URL configUrl, + List helpers, + Class constructorClass) { + + try (java.io.BufferedReader reader = + new java.io.BufferedReader( + new InputStreamReader(configUrl.openStream(), StandardCharsets.UTF_8), + IoUtils.BUFFER_SIZE)) { + String line = reader.readLine(); + + while (line != null) { + registerHelper(classLoader, getProviderClassName(line), helpers, constructorClass); + line = reader.readLine(); } - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.SEVERE, - "Exception while detecting the helpers.", ioe); + } catch (IOException e) { + Context.getCurrentLogger() + .log(Level.SEVERE, "Unable to read the provider descriptor: {0}", configUrl); } } /** * Registers a list of helpers. - * - * @param descriptorPath Classpath to the descriptor file. - * @param helpers The list of helpers to update. + * + * @param descriptorPath Classpath to the descriptor file. + * @param helpers The list of helpers to update. * @param constructorClass The constructor parameter class to look for. * @throws IOException */ - public void registerHelpers(String descriptorPath, List helpers, - Class constructorClass) throws IOException { - ClassLoader classLoader = getClassLoader(); - Enumeration configUrls = classLoader - .getResources(descriptorPath); + public void registerHelpers(String descriptorPath, List helpers, Class constructorClass) + throws IOException { + ClassLoader currentClassLoader = getClassLoader(); + Enumeration configUrls = currentClassLoader.getResources(descriptorPath); if (configUrls != null) { while (configUrls.hasMoreElements()) { - registerHelpers(classLoader, configUrls.nextElement(), helpers, - constructorClass); + registerHelpers( + currentClassLoader, configUrls.nextElement(), helpers, constructorClass); } } } /** - * Registers a factory used by the URL class to create the - * {@link java.net.URLConnection} instances when the - * {@link java.net.URL#openConnection()} or - * {@link java.net.URL#openStream()} methods are invoked. - *

- * The implementation is based on the client dispatcher of the current - * context, as provided by {@link Context#getCurrent()} method. + * Registers a factory used by the URL class to create the {@link java.net.URLConnection} + * instances when the {@link java.net.URL#openConnection()} or {@link java.net.URL#openStream()} + * methods are invoked. + * + *

The implementation is based on the client dispatcher of the current context, as provided + * by {@link Context#getCurrent()} method. */ public void registerUrlFactory() { - // Set up an java.net.URLStreamHandlerFactory for - // proper creation of java.net.URL instances - java.net.URL.setURLStreamHandlerFactory( - new java.net.URLStreamHandlerFactory() { - public java.net.URLStreamHandler createURLStreamHandler( - String protocol) { - return new java.net.URLStreamHandler() { + // Set up an java.net.URLStreamHandlerFactory for proper creation of java.net.URL instances + setURLStreamHandlerFactory( + protocol -> + new java.net.URLStreamHandler() { @Override - protected java.net.URLConnection openConnection( - java.net.URL url) throws IOException { + protected java.net.URLConnection openConnection(java.net.URL url) + throws IOException { return new java.net.URLConnection(url) { @Override - public void connect() throws IOException { + public void connect() { + // no connection to open } @Override - public InputStream getInputStream() - throws IOException { - InputStream result1 = null; + public InputStream getInputStream() throws IOException { + InputStream result = null; // Retrieve the current context - final Context context = Context - .getCurrent(); + final Context context = Context.getCurrent(); if (context != null) { - Response response = context - .getClientDispatcher() - .handle(new Request( - Method.GET, - this.url.toString())); - - if (response.getStatus() - .isSuccess()) { - result1 = response.getEntity() - .getStream(); + Response response = + context.getClientDispatcher() + .handle( + new Request( + Method.GET, + this.url.toString())); + + if (response.getStatus().isSuccess()) { + result = response.getEntity().getStream(); } } - return result1; + return result; } }; } - - }; - } - - }); + }); } /** * Sets the engine class loader. - * + * * @param newClassLoader The new user class loader to use. */ public void setClassLoader(ClassLoader newClassLoader) { @@ -989,7 +923,7 @@ public void setClassLoader(ClassLoader newClassLoader) { /** * Sets the logger facade to use. - * + * * @param loggerFacade The logger facade to use. */ public void setLoggerFacade(LoggerFacade loggerFacade) { @@ -998,9 +932,8 @@ public void setLoggerFacade(LoggerFacade loggerFacade) { /** * Sets the list of available authentication helpers. - * - * @param registeredAuthenticators The list of available authentication - * helpers. + * + * @param registeredAuthenticators The list of available authentication helpers. */ public void setRegisteredAuthenticators( List registeredAuthenticators) { @@ -1009,8 +942,7 @@ public void setRegisteredAuthenticators( this.registeredAuthenticators.clear(); if (registeredAuthenticators != null) { - this.registeredAuthenticators - .addAll(registeredAuthenticators); + this.registeredAuthenticators.addAll(registeredAuthenticators); } } } @@ -1018,7 +950,7 @@ public void setRegisteredAuthenticators( /** * Sets the list of available client helpers. - * + * * @param registeredClients The list of available client helpers. */ public void setRegisteredClients( @@ -1036,7 +968,7 @@ public void setRegisteredClients( /** * Sets the list of available converter helpers. - * + * * @param registeredConverters The list of available converter helpers. */ public void setRegisteredConverters( @@ -1054,7 +986,7 @@ public void setRegisteredConverters( /** * Sets the list of available protocol helpers. - * + * * @param registeredProtocols The list of available protocol helpers. */ public void setRegisteredProtocols( @@ -1072,11 +1004,10 @@ public void setRegisteredProtocols( /** * Sets the list of available server helpers. - * + * * @param registeredServers The list of available server helpers. */ - public void setRegisteredServers( - List> registeredServers) { + public void setRegisteredServers(List> registeredServers) { synchronized (this.registeredServers) { if (registeredServers != this.registeredServers) { this.registeredServers.clear(); @@ -1090,11 +1021,10 @@ public void setRegisteredServers( /** * Sets the user class loader that should be used in priority. - * + * * @param newClassLoader The new user class loader to use. */ public void setUserClassLoader(ClassLoader newClassLoader) { this.userClassLoader = newClassLoader; } - } diff --git a/org.restlet/src/main/java/org/restlet/engine/Helper.java b/org.restlet/src/main/java/org/restlet/engine/Helper.java index 8432814460..52275aeeef 100644 --- a/org.restlet/src/main/java/org/restlet/engine/Helper.java +++ b/org.restlet/src/main/java/org/restlet/engine/Helper.java @@ -1,19 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine; /** * Abstract marker class parent of all engine helpers. - * + * * @author Jerome Louvel */ -public abstract class Helper { - -} +public abstract class Helper {} diff --git a/org.restlet/src/main/java/org/restlet/engine/RestletHelper.java b/org.restlet/src/main/java/org/restlet/engine/RestletHelper.java index 33b6a162c4..71b77a74e0 100644 --- a/org.restlet/src/main/java/org/restlet/engine/RestletHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/RestletHelper.java @@ -1,14 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -17,156 +19,145 @@ import org.restlet.service.MetadataService; import org.restlet.util.Series; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; - /** - * Delegate used by API classes to get support from the implementation classes. - * Note that this is an SPI class that is not intended for public usage. - * + * Delegate used by API classes to get support from the implementation classes. Note that this is an + * SPI class that is not intended for public usage. + * * @author Jerome Louvel */ public abstract class RestletHelper extends Helper { - /** - * The map of attributes exchanged between the API and the Engine via this - * helper. - */ - private final Map attributes; - - /** - * The helped Restlet. - */ - private volatile T helped; - - /** - * Constructor. - * - * @param helped The helped Restlet. - */ - public RestletHelper(T helped) { - this.attributes = new ConcurrentHashMap(); - this.helped = helped; - } - - /** - * Returns the map of attributes exchanged between the API and the Engine via - * this helper. - * - * @return The map of attributes. - */ - public Map getAttributes() { - return this.attributes; - } - - /** - * Returns the helped Restlet context. - * - * @return The helped Restlet context. - */ - public Context getContext() { - return getHelped().getContext(); - } - - /** - * Returns the helped Restlet. - * - * @return The helped Restlet. - */ - public T getHelped() { - return this.helped; - } - - /** - * Returns the helped Restlet parameters. - * - * @return The helped Restlet parameters. - */ - public Series getHelpedParameters() { - final Series result; - - if ((getHelped() != null) && (getHelped().getContext() != null)) { - result = getHelped().getContext().getParameters(); - } else { - result = new Series<>(Parameter.class); - } - - return result; - } - - /** - * Returns the helped Restlet logger. - * - * @return The helped Restlet logger. - */ - public Logger getLogger() { - if (getHelped() != null && getHelped().getContext() != null) { - return getHelped().getContext().getLogger(); - } - return Context.getCurrentLogger(); - } - - /** - * Returns the metadata service. If the parent application doesn't exist, a new - * instance is created. - * - * @return The metadata service. - */ - public MetadataService getMetadataService() { - MetadataService result = null; - - if (getHelped() != null) { - org.restlet.Application application = getHelped().getApplication(); - - if (application != null) { - result = application.getMetadataService(); - } - } - - if (result == null) { - result = new MetadataService(); - } - - return result; - } - - /** - * Handles a call. - * - * @param request The request to handle. - * @param response The response to update. - */ - public void handle(Request request, Response response) { - // Associate the response to the current thread - Response.setCurrent(response); - - // Associate the context to the current thread - if (getContext() != null) { - Context.setCurrent(getContext()); - } - } - - /** - * Sets the helped Restlet. - * - * @param helpedRestlet The helped Restlet. - */ - public void setHelped(T helpedRestlet) { - this.helped = helpedRestlet; - } - - /** Start callback. */ - public abstract void start() throws Exception; - - /** Stop callback. */ - public abstract void stop() throws Exception; - - /** - * Update callback with less impact than a {@link #stop()} followed by a - * {@link #start()}. - * - * @throws Exception - */ - public abstract void update() throws Exception; + /** The map of attributes exchanged between the API and the Engine via this helper. */ + private final Map attributes; + + /** The helped Restlet. */ + private volatile T helped; + + /** + * Constructor. + * + * @param helped The helped Restlet. + */ + protected RestletHelper(T helped) { + this.attributes = new ConcurrentHashMap<>(); + this.helped = helped; + } + + /** + * Returns the map of attributes exchanged between the API and the Engine via this helper. + * + * @return The map of attributes. + */ + public Map getAttributes() { + return this.attributes; + } + + /** + * Returns the helped Restlet context. + * + * @return The helped Restlet context. + */ + public Context getContext() { + return getHelped().getContext(); + } + + /** + * Returns the helped Restlet. + * + * @return The helped Restlet. + */ + public T getHelped() { + return this.helped; + } + + /** + * Returns the helped Restlet parameters. + * + * @return The helped Restlet parameters. + */ + public Series getHelpedParameters() { + final Series result; + + if ((getHelped() != null) && (getHelped().getContext() != null)) { + result = getHelped().getContext().getParameters(); + } else { + result = new Series<>(Parameter.class); + } + + return result; + } + + /** + * Returns the helped Restlet logger. + * + * @return The helped Restlet logger. + */ + public Logger getLogger() { + if (getHelped() != null && getHelped().getContext() != null) { + return getHelped().getContext().getLogger(); + } + return Context.getCurrentLogger(); + } + + /** + * Returns the metadata service. If the parent application doesn't exist, a new instance is + * created. + * + * @return The metadata service. + */ + public MetadataService getMetadataService() { + MetadataService result = null; + + if (getHelped() != null) { + org.restlet.Application application = getHelped().getApplication(); + + if (application != null) { + result = application.getMetadataService(); + } + } + + if (result == null) { + result = new MetadataService(); + } + + return result; + } + + /** + * Handles a call. + * + * @param request The request to handle. + * @param response The response to update. + */ + public void handle(Request request, Response response) { + // Associate the response to the current thread + Response.setCurrent(response); + + // Associate the context to the current thread + if (getContext() != null) { + Context.setCurrent(getContext()); + } + } + + /** + * Sets the helped Restlet. + * + * @param helpedRestlet The helped Restlet. + */ + public void setHelped(T helpedRestlet) { + this.helped = helpedRestlet; + } + + /** Start callback. */ + public abstract void start() throws Exception; + + /** Stop callback. */ + public abstract void stop() throws Exception; + + /** + * Update callback with less impact than a {@link #stop()} followed by a {@link #start()}. + * + * @throws Exception + */ + public abstract void update() throws Exception; } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/Adapter.java b/org.restlet/src/main/java/org/restlet/engine/adapter/Adapter.java index dbe50bdb02..38bbabd770 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/Adapter.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/Adapter.java @@ -1,54 +1,51 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; -import org.restlet.Context; - import java.util.logging.Logger; +import org.restlet.Context; /** * Converter between high-level and low-level HTTP calls. - * + * * @author Jerome Louvel */ public class Adapter { - /** The context. */ - private volatile Context context; - - /** - * Constructor. - * - * @param context The context to use. - */ - public Adapter(Context context) { - this.context = context; - } - - /** - * Returns the context. - * - * @return The context. - */ - public Context getContext() { - return this.context; - } - - /** - * Returns the logger. - * - * @return The logger. - */ - public Logger getLogger() { - Logger result = (getContext() != null) ? getContext().getLogger() : null; - return (result != null) ? result : Context.getCurrentLogger(); - } - + /** The context. */ + private volatile Context context; + + /** + * Constructor. + * + * @param context The context to use. + */ + public Adapter(Context context) { + this.context = context; + } + + /** + * Returns the context. + * + * @return The context. + */ + public Context getContext() { + return this.context; + } + + /** + * Returns the logger. + * + * @return The logger. + */ + public Logger getLogger() { + Logger result = (getContext() != null) ? getContext().getLogger() : null; + return (result != null) ? result : Context.getCurrentLogger(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/Call.java b/org.restlet/src/main/java/org/restlet/engine/adapter/Call.java index 26f6eeaa21..cd513dc79a 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/Call.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/Call.java @@ -1,14 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; import org.restlet.Context; import org.restlet.data.Header; import org.restlet.data.Protocol; @@ -17,19 +19,15 @@ import org.restlet.representation.Representation; import org.restlet.util.Series; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Logger; - /** * Low-level call for the HTTP connectors. - * + * * @author Jerome Louvel */ public abstract class Call { /** * Returns true if the given exception is caused by a broken connection. - * + * * @param exception The exception to inspect. * @return True if the given exception is caused by a broken connection. */ @@ -39,19 +37,21 @@ public static boolean isBroken(Throwable exception) { // detect Tomcat and Jetty exceptions if (exception instanceof IOException) { String exceptionName = exception.getClass().getName(); - result = (exceptionName.endsWith("ClientAbortException") - || exceptionName.endsWith("jetty.io.EofException")); + result = + (exceptionName.endsWith("ClientAbortException") + || exceptionName.endsWith("jetty.io.EofException")); } // check for known exception messages if (!result) { String exceptionMessage = exception.getMessage(); if (exceptionMessage != null) { - result = (exceptionMessage.indexOf("Broken pipe") != -1) - || (exceptionMessage.equals( - "An existing connection must have been closed by the remote party.") + result = + (exceptionMessage.contains("Broken pipe")) || (exceptionMessage.equals( - "An open connection has been abandonned by your network stack."))); + "An existing connection must have been closed by the remote party.") + || (exceptionMessage.equals( + "An open connection has been abandonned by your network stack."))); } } @@ -110,10 +110,8 @@ public static boolean isBroken(Throwable exception) { /** The protocol version. */ private volatile String version; - /** - * Constructor. - */ - public Call() { + /** Constructor. */ + protected Call() { this.hostDomain = null; this.hostPort = -1; this.clientAddress = null; @@ -122,9 +120,9 @@ public Call() { this.method = null; this.protocol = null; this.reasonPhrase = ""; - this.requestHeaders = new Series

(Header.class); + this.requestHeaders = new Series<>(Header.class); this.requestUri = null; - this.responseHeaders = new Series
(Header.class); + this.responseHeaders = new Series<>(Header.class); this.serverAddress = null; this.serverPort = -1; this.statusCode = 200; @@ -135,7 +133,7 @@ public Call() { /** * Returns the client address.
* Corresponds to the IP address of the requesting client. - * + * * @return The client address. */ public String getClientAddress() { @@ -145,7 +143,7 @@ public String getClientAddress() { /** * Returns the client port.
* Corresponds to the TCP/IP port of the requesting client. - * + * * @return The client port. */ public int getClientPort() { @@ -154,7 +152,7 @@ public int getClientPort() { /** * Returns the host domain. - * + * * @return The host domain. */ public String getHostDomain() { @@ -163,7 +161,7 @@ public String getHostDomain() { /** * Returns the host port. - * + * * @return The host port. */ public int getHostPort() { @@ -172,7 +170,7 @@ public int getHostPort() { /** * Returns the logger. - * + * * @return The logger. */ public Logger getLogger() { @@ -181,7 +179,7 @@ public Logger getLogger() { /** * Returns the request method. - * + * * @return The request method. */ public String getMethod() { @@ -190,7 +188,7 @@ public String getMethod() { /** * Returns the exact protocol (HTTP or HTTPS). - * + * * @return The exact protocol (HTTP or HTTPS). */ public Protocol getProtocol() { @@ -202,7 +200,7 @@ public Protocol getProtocol() { /** * Returns the reason phrase. - * + * * @return The reason phrase. */ public String getReasonPhrase() { @@ -211,7 +209,7 @@ public String getReasonPhrase() { /** * Returns the representation wrapping the given stream. - * + * * @param stream The response input stream. * @return The wrapping representation. */ @@ -221,7 +219,7 @@ protected Representation getRepresentation(InputStream stream) { /** * Returns the modifiable list of request headers. - * + * * @return The modifiable list of request headers. */ public Series
getRequestHeaders() { @@ -229,9 +227,8 @@ public Series
getRequestHeaders() { } /** - * Returns the URI on the request line (most like a relative reference, but - * not necessarily). - * + * Returns the URI on the request line (most like a relative reference, but not necessarily). + * * @return The URI on the request line. */ public String getRequestUri() { @@ -240,7 +237,7 @@ public String getRequestUri() { /** * Returns the modifiable list of server headers. - * + * * @return The modifiable list of server headers. */ public Series
getResponseHeaders() { @@ -250,7 +247,7 @@ public Series
getResponseHeaders() { /** * Returns the response address.
* Corresponds to the IP address of the responding server. - * + * * @return The response address. */ public String getServerAddress() { @@ -259,7 +256,7 @@ public String getServerAddress() { /** * Returns the server port. - * + * * @return The server port. */ public int getServerPort() { @@ -268,17 +265,16 @@ public int getServerPort() { /** * Returns the status code. - * + * * @return The status code. - * @throws IOException */ - public int getStatusCode() throws IOException { + public int getStatusCode() { return this.statusCode; } /** * Returns the user principal. - * + * * @return The user principal. */ public java.security.Principal getUserPrincipal() { @@ -287,7 +283,7 @@ public java.security.Principal getUserPrincipal() { /** * Returns the protocol version used. - * + * * @return The protocol version used. */ public String getVersion() { @@ -296,14 +292,14 @@ public String getVersion() { /** * Indicates if the client wants a persistent connection. - * + * * @return True if the client wants a persistent connection. */ protected abstract boolean isClientKeepAlive(); /** * Indicates if the confidentiality of the call is ensured (ex: via SSL). - * + * * @return True if the confidentiality of the call is ensured (ex: via SSL). */ public boolean isConfidential() { @@ -312,7 +308,7 @@ public boolean isConfidential() { /** * Returns true if the given exception is caused by a broken connection. - * + * * @param exception The exception to inspect. * @return True if the given exception is caused by a broken connection. */ @@ -322,9 +318,8 @@ public boolean isConnectionBroken(Throwable exception) { /** * Indicates if both the client and the server want a persistent connection. - * - * @return True if the connection should be kept alive after the call - * processing. + * + * @return True if the connection should be kept alive after the call processing. */ protected boolean isKeepAlive() { return isClientKeepAlive() && isServerKeepAlive(); @@ -332,7 +327,7 @@ protected boolean isKeepAlive() { /** * Indicates if the request entity is chunked. - * + * * @return True if the request entity is chunked. */ protected boolean isRequestChunked() { @@ -341,7 +336,7 @@ protected boolean isRequestChunked() { /** * Indicates if the response entity is chunked. - * + * * @return True if the response entity is chunked. */ protected boolean isResponseChunked() { @@ -350,14 +345,14 @@ protected boolean isResponseChunked() { /** * Indicates if the server wants a persistent connection. - * + * * @return True if the server wants a persistent connection. */ protected abstract boolean isServerKeepAlive(); /** * Sets the client address. - * + * * @param clientAddress The client address. */ protected void setClientAddress(String clientAddress) { @@ -366,7 +361,7 @@ protected void setClientAddress(String clientAddress) { /** * Sets the client port. - * + * * @param clientPort The client port. */ protected void setClientPort(int clientPort) { @@ -375,9 +370,8 @@ protected void setClientPort(int clientPort) { /** * Indicates if the confidentiality of the call is ensured (ex: via SSL). - * - * @param confidential True if the confidentiality of the call is ensured - * (ex: via SSL). + * + * @param confidential True if the confidentiality of the call is ensured (ex: via SSL). */ protected void setConfidential(boolean confidential) { this.confidential = confidential; @@ -385,7 +379,7 @@ protected void setConfidential(boolean confidential) { /** * Sets the host domain name. - * + * * @param hostDomain The baseRef domain name. */ public void setHostDomain(String hostDomain) { @@ -394,7 +388,7 @@ public void setHostDomain(String hostDomain) { /** * Sets the host port. - * + * * @param hostPort The host port. */ public void setHostPort(int hostPort) { @@ -403,7 +397,7 @@ public void setHostPort(int hostPort) { /** * Sets the request method. - * + * * @param method The request method. */ protected void setMethod(String method) { @@ -412,7 +406,7 @@ protected void setMethod(String method) { /** * Sets the exact protocol used (HTTP or HTTPS). - * + * * @param protocol The protocol. */ public void setProtocol(Protocol protocol) { @@ -421,7 +415,7 @@ public void setProtocol(Protocol protocol) { /** * Sets the reason phrase. - * + * * @param reasonPhrase The reason phrase. */ public void setReasonPhrase(String reasonPhrase) { @@ -430,7 +424,7 @@ public void setReasonPhrase(String reasonPhrase) { /** * Sets the full request URI. - * + * * @param requestUri The full request URI. */ protected void setRequestUri(String requestUri) { @@ -444,7 +438,7 @@ protected void setRequestUri(String requestUri) { /** * Sets the response address.
* Corresponds to the IP address of the responding server. - * + * * @param responseAddress The response address. */ public void setServerAddress(String responseAddress) { @@ -453,7 +447,7 @@ public void setServerAddress(String responseAddress) { /** * Sets the server port. - * + * * @param serverPort The server port. */ public void setServerPort(int serverPort) { @@ -462,7 +456,7 @@ public void setServerPort(int serverPort) { /** * Sets the status code. - * + * * @param code The status code. */ public void setStatusCode(int code) { @@ -471,7 +465,7 @@ public void setStatusCode(int code) { /** * Sets the user principal. - * + * * @param principal The user principal. */ public void setUserPrincipal(java.security.Principal principal) { @@ -480,11 +474,10 @@ public void setUserPrincipal(java.security.Principal principal) { /** * Sets the protocol version used. - * + * * @param version The protocol version used. */ public void setVersion(String version) { this.version = version; } - } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/ClientAdapter.java b/org.restlet/src/main/java/org/restlet/engine/adapter/ClientAdapter.java index bd7fa75e11..e15ea6b292 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/ClientAdapter.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/ClientAdapter.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -20,9 +20,6 @@ import org.restlet.engine.header.HeaderUtils; import org.restlet.util.Series; -import java.io.IOException; -import java.util.logging.Level; - /** * Converter of high-level uniform calls into low-level HTTP client calls. * @@ -40,70 +37,70 @@ public ClientAdapter(Context context) { } /** - * Commits the changes to a handled HTTP client call back into the original - * uniform call. The default implementation first invokes the - * "addResponseHeaders" then asks the "htppCall" to send the response back - * to the client. + * Commits the changes to a handled HTTP client call back into the original uniform call. The + * default implementation first invokes the "addResponseHeaders" then asks the "htppCall" to + * send the response back to the client. * * @param httpCall The original HTTP call. - * @param request The high-level request. + * @param request The high-level request. * @param response The high-level response. - * @throws Exception */ - public void commit(final ClientCall httpCall, Request request, - Response response) throws Exception { + public void commit(final ClientCall httpCall, Request request, Response response) { if (httpCall != null) { // Check if the call is asynchronous if (request.isAsynchronous()) { final Uniform userCallback = request.getOnResponse(); // Send the request to the client - httpCall.sendRequest(request, response, new Uniform() { - public void handle(Request request, Response response) { - try { - updateResponse(response, - new Status(httpCall.getStatusCode(), - httpCall.getReasonPhrase()), - httpCall); - - if (userCallback != null) { - userCallback.handle(request, response); + httpCall.sendRequest( + request, + response, + (request1, response1) -> { + try { + updateResponse( + response1, + new Status( + httpCall.getStatusCode(), + httpCall.getReasonPhrase()), + httpCall); + + if (userCallback != null) { + userCallback.handle(request1, response1); + } + } catch (Exception exception) { + getLogger() + .log( + Level.WARNING, + "Unexpected error or exception inside the user call back", + exception); } - } catch (Throwable t) { - getLogger().log(Level.WARNING, - "Unexpected error or exception inside the user call back", - t); - } - } - }); + }); } else { - updateResponse(response, httpCall.sendRequest(request), - httpCall); + updateResponse(response, httpCall.sendRequest(request), httpCall); } } } /** - * Reads the response headers of a handled HTTP client call to update the - * original uniform call. + * Reads the response headers of a handled HTTP client call to update the original uniform call. * * @param httpCall The handled HTTP client call. * @param response The high-level response to update. */ protected void readResponseHeaders(ClientCall httpCall, Response response) { try { - Series

responseHeaders = httpCall - .getResponseHeaders(); + Series
responseHeaders = httpCall.getResponseHeaders(); // Put the response headers in the call's attributes map - response.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, - responseHeaders); + response.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, responseHeaders); HeaderUtils.copyResponseTransportHeaders(responseHeaders, response); } catch (Exception e) { - getLogger().log(Level.FINE, - "An error occurred during the processing of the HTTP response.", - e); + getLogger() + .log( + Level.FINE, + "An error occurred during the processing of the HTTP response.", + e); response.setStatus(Status.CONNECTOR_ERROR_INTERNAL, e); } } @@ -111,7 +108,7 @@ protected void readResponseHeaders(ClientCall httpCall, Response response) { /** * Converts a low-level HTTP call into a high-level uniform call. * - * @param client The HTTP client that will handle the call. + * @param client The HTTP client that will handle the call. * @param request The high-level request. * @return A new high-level uniform call. */ @@ -124,8 +121,7 @@ public ClientCall toSpecific(HttpClientHelper client, Request request) { HeaderUtils.addGeneralHeaders(request, result.getRequestHeaders()); if (request.getEntity() != null) { - HeaderUtils.addEntityHeaders(request.getEntity(), - result.getRequestHeaders()); + HeaderUtils.addEntityHeaders(request.getEntity(), result.getRequestHeaders()); } // NOTE: This must stay at the end because the AWS challenge @@ -137,16 +133,13 @@ public ClientCall toSpecific(HttpClientHelper client, Request request) { } /** - * Updates the response with information from the lower-level HTTP client - * call. + * Updates the response with information from the lower-level HTTP client call. * * @param response The response to update. - * @param status The response status to apply. + * @param status The response status to apply. * @param httpCall The source HTTP client call. - * @throws IOException */ - public void updateResponse(Response response, Status status, - ClientCall httpCall) { + public void updateResponse(Response response, Status status, ClientCall httpCall) { // Send the request to the client response.setStatus(status); @@ -168,16 +161,12 @@ public void updateResponse(Response response, Status status, response.getEntity().release(); } else if (response.getStatus().equals(Status.SUCCESS_NO_CONTENT)) { response.getEntity().release(); - } else if (response.getStatus() - .equals(Status.SUCCESS_RESET_CONTENT)) { + } else if (response.getStatus().equals(Status.SUCCESS_RESET_CONTENT) + || response.getStatus().isInformational()) { response.getEntity().release(); response.setEntity(null); - } else if (response.getStatus() - .equals(Status.REDIRECTION_NOT_MODIFIED)) { - response.getEntity().release(); - } else if (response.getStatus().isInformational()) { + } else if (response.getStatus().equals(Status.REDIRECTION_NOT_MODIFIED)) { response.getEntity().release(); - response.setEntity(null); } } } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/ClientCall.java b/org.restlet/src/main/java/org/restlet/engine/adapter/ClientCall.java index f75e608983..8bce9a0bd0 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/ClientCall.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/ClientCall.java @@ -1,14 +1,24 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; +import static org.restlet.data.Encoding.IDENTITY; +import static org.restlet.data.Status.CONNECTOR_ERROR_COMMUNICATION; +import static org.restlet.data.Status.REDIRECTION_NOT_MODIFIED; +import static org.restlet.data.Status.SUCCESS_NO_CONTENT; +import static org.restlet.data.Status.SUCCESS_RESET_CONTENT; +import static org.restlet.representation.Representation.UNKNOWN_SIZE; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -21,25 +31,16 @@ import org.restlet.representation.Representation; import org.restlet.util.Series; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.logging.Level; - -import static org.restlet.data.Encoding.IDENTITY; -import static org.restlet.data.Status.*; -import static org.restlet.representation.Representation.UNKNOWN_SIZE; - /** * Low-level HTTP client call. - * + * * @author Jerome Louvel */ public abstract class ClientCall extends Call { /** * Returns the local IP address or 127.0.0.1 if the resolution fails. - * + * * @return The local IP address or 127.0.0.1 if the resolution fails. */ public static String getLocalAddress() { @@ -55,13 +56,12 @@ public static String getLocalAddress() { /** * Constructor setting the request address to the local host. - * - * @param helper The parent HTTP client helper. - * @param method The method name. + * + * @param helper The parent HTTP client helper. + * @param method The method name. * @param requestUri The request URI. */ - public ClientCall(HttpClientHelper helper, String method, - String requestUri) { + protected ClientCall(HttpClientHelper helper, String method, String requestUri) { this.helper = helper; setMethod(method); setRequestUri(requestUri); @@ -69,9 +69,9 @@ public ClientCall(HttpClientHelper helper, String method, } /** - * Returns the content length of the request entity if know, - * {@link Representation#UNKNOWN_SIZE} otherwise. - * + * Returns the content length of the request entity if know, {@link Representation#UNKNOWN_SIZE} + * otherwise. + * * @return The request content length. */ protected long getContentLength() { @@ -80,7 +80,7 @@ protected long getContentLength() { /** * Returns the HTTP client helper. - * + * * @return The HTTP client helper. */ public HttpClientHelper getHelper() { @@ -89,35 +89,34 @@ public HttpClientHelper getHelper() { /** * Returns the request entity stream if it exists. - * + * * @return The request entity stream if it exists. */ public abstract OutputStream getRequestEntityStream(); /** * Returns the request head stream if it exists. - * + * * @return The request head stream if it exists. */ public abstract OutputStream getRequestHeadStream(); /** - * Returns the response entity if available. Note that no metadata is - * associated by default, you have to manually set them from your headers. - * + * Returns the response entity if available. Note that no metadata is associated by default, you + * have to manually set them from your headers. + * * @param response the Response to get the entity from * @return The response entity if available. */ public Representation getResponseEntity(Response response) { Representation result = null; - long size = UNKNOWN_SIZE; + final long size; // Compute the content length Series

responseHeaders = getResponseHeaders(); - String transferEncoding = responseHeaders - .getFirstValue(HeaderConstants.HEADER_TRANSFER_ENCODING, true); - if ((transferEncoding != null) - && !IDENTITY.getName().equalsIgnoreCase(transferEncoding)) { + String transferEncoding = + responseHeaders.getFirstValue(HeaderConstants.HEADER_TRANSFER_ENCODING, true); + if ((transferEncoding != null) && !IDENTITY.getName().equalsIgnoreCase(transferEncoding)) { size = UNKNOWN_SIZE; } else { size = getContentLength(); @@ -130,8 +129,7 @@ public Representation getResponseEntity(Response response) { && !response.getStatus().equals(SUCCESS_RESET_CONTENT)) { // Make sure that an InputRepresentation will not be instantiated // while the stream is closed. - InputStream stream = getUnClosedResponseEntityStream( - getResponseEntityStream(size)); + InputStream stream = getUnClosedResponseEntityStream(getResponseEntityStream(size)); if (stream != null) { result = getRepresentation(stream); @@ -143,8 +141,9 @@ public Representation getResponseEntity(Response response) { // Informs that the size has not been specified in the header. if (size == UNKNOWN_SIZE) { - getLogger().fine( - "The length of the message body is unknown. The entity must be handled carefully and consumed entirely in order to surely release the connection."); + getLogger() + .fine( + "The length of the message body is unknown. The entity must be handled carefully and consumed entirely to surely release the connection."); } } result = HeaderUtils.extractEntityHeaders(responseHeaders, result); @@ -154,22 +153,20 @@ public Representation getResponseEntity(Response response) { /** * Returns the response entity stream if it exists. - * + * * @param size The expected entity size or -1 if unknown. * @return The response entity stream if it exists. */ public abstract InputStream getResponseEntityStream(long size); /** - * Checks if the given input stream really contains bytes to be read. If so, - * returns the inputStream otherwise returns null. - * + * Checks if the given input stream really contains bytes to be read. If so, returns the + * inputStream otherwise returns null. + * * @param inputStream the inputStream to check. - * @return null if the given inputStream does not contain any byte, an - * inputStream otherwise. + * @return null if the given inputStream does not contain any byte, an inputStream otherwise. */ - private InputStream getUnClosedResponseEntityStream( - InputStream inputStream) { + private InputStream getUnClosedResponseEntityStream(InputStream inputStream) { InputStream result = null; if (inputStream != null) { @@ -177,8 +174,7 @@ private InputStream getUnClosedResponseEntityStream( if (inputStream.available() > 0) { result = inputStream; } else { - java.io.PushbackInputStream is = new java.io.PushbackInputStream( - inputStream); + java.io.PushbackInputStream is = new java.io.PushbackInputStream(inputStream); int i = is.read(); if (i >= 0) { @@ -187,10 +183,8 @@ private InputStream getUnClosedResponseEntityStream( } } } catch (IOException ioe) { - getLogger().log(Level.FINER, "End of response entity stream.", - ioe); + getLogger().log(Level.FINER, "End of response entity stream.", ioe); } - } return result; @@ -207,21 +201,19 @@ protected boolean isServerKeepAlive() { } /** - * Sends the request to the client. Commits the request line, headers and - * optional entity and send them over the network. - * + * Sends the request to the client. Commits the request line, headers and optional entity and + * send them over the network. + * * @param request The high-level request. * @return the status of the communication */ public Status sendRequest(Request request) { Status result = null; - Representation entity = request.isEntityAvailable() - ? request.getEntity() - : null; + Representation entity = request.isEntityAvailable() ? request.getEntity() : null; // Get the connector service to callback - org.restlet.service.ConnectorService connectorService = ConnectorHelper - .getConnectorService(); + org.restlet.service.ConnectorService connectorService = + ConnectorHelper.getConnectorService(); if (connectorService != null) { connectorService.beforeSend(entity); } @@ -229,7 +221,7 @@ public Status sendRequest(Request request) { try { if (entity != null) { - // In order to workaround bug #6472250 + // To work around bug #6472250 // (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6472250), // it is very important to reuse that exact same "requestStream" // reference when manipulating the request stream, otherwise @@ -248,9 +240,12 @@ public Status sendRequest(Request request) { // any open request stream. result = new Status(getStatusCode(), getReasonPhrase()); } catch (IOException ioe) { - getHelper().getLogger().log(Level.FINE, - "An error occurred during the communication with the remote HTTP server.", - ioe); + getHelper() + .getLogger() + .log( + Level.FINE, + "An error occurred during the communication with the remote HTTP server.", + ioe); result = new Status(CONNECTOR_ERROR_COMMUNICATION, ioe); } finally { if (entity != null) { @@ -267,26 +262,26 @@ public Status sendRequest(Request request) { } /** - * Sends the request to the client. Commits the request line, headers and - * optional entity and send them over the network. - * - * @param request The high-level request. + * Sends the request to the client. Commits the request line, headers, and optional entity and + * send them over the network. + * + * @param request The high-level request. * @param response The high-level response. * @param callback The callback invoked upon request completion. */ - public void sendRequest(Request request, Response response, - org.restlet.Uniform callback) throws Exception { - Context.getCurrentLogger().warning( - "Currently callbacks are not available for this connector."); + public void sendRequest(Request request, Response response, org.restlet.Uniform callback) { + Context.getCurrentLogger() + .warning("Currently callbacks are not available for this connector."); } /** * Indicates if the request entity should be chunked. - * + * * @return True if the request should be chunked */ protected boolean shouldRequestBeChunked(Request request) { - return request.isEntityAvailable() && (request.getEntity() != null) + return request.isEntityAvailable() + && (request.getEntity() != null) && !request.getEntity().hasKnownSize(); } } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/HttpClientHelper.java b/org.restlet/src/main/java/org/restlet/engine/adapter/HttpClientHelper.java index 2e0c78e18f..7d78746986 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/HttpClientHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/HttpClientHelper.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; +import java.util.logging.Level; import org.restlet.Client; import org.restlet.Context; import org.restlet.Request; @@ -16,11 +16,10 @@ import org.restlet.data.Status; import org.restlet.engine.connector.ClientHelper; -import java.util.logging.Level; - /** - * Base HTTP client connector. Here is the list of parameters that are - * supported. They should be set in the Client's context before it is started: + * Base HTTP client connector. Here is the list of parameters that are supported. They should be set + * in the Client's context before it is started: + * * * * @@ -37,74 +36,79 @@ * requests and responses. * *
list of supported parameters
- * + * * @author Jerome Louvel */ public abstract class HttpClientHelper extends ClientHelper { - /** The adapter from uniform calls to HTTP calls. */ - private volatile ClientAdapter adapter; + /** The adapter from uniform calls to HTTP calls. */ + private volatile ClientAdapter adapter; - /** - * Constructor. - * - * @param client The client to help. - */ - public HttpClientHelper(Client client) { - super(client); - this.adapter = null; - } + /** + * Constructor. + * + * @param client The client to help. + */ + protected HttpClientHelper(Client client) { + super(client); + this.adapter = null; + } - /** - * Creates a low-level HTTP client call from a high-level request. - * - * @param request The high-level request. - * @return A low-level HTTP client call. - */ - public abstract ClientCall create(Request request); + /** + * Creates a low-level HTTP client call from a high-level request. + * + * @param request The high-level request. + * @return A low-level HTTP client call. + */ + public abstract ClientCall create(Request request); - /** - * Returns the adapter from uniform calls to HTTP calls. - * - * @return the adapter from uniform calls to HTTP calls. - */ - public ClientAdapter getAdapter() throws Exception { - if (this.adapter == null) { - String adapterClass = getHelpedParameters().getFirstValue("adapter", - "org.restlet.engine.adapter.ClientAdapter"); - this.adapter = (ClientAdapter) Class.forName(adapterClass).getConstructor(Context.class) - .newInstance(getContext()); - } + /** + * Returns the adapter from uniform calls to HTTP calls. + * + * @return the adapter from uniform calls to HTTP calls. + */ + public ClientAdapter getAdapter() throws Exception { + if (this.adapter == null) { + String adapterClass = + getHelpedParameters() + .getFirstValue("adapter", "org.restlet.engine.adapter.ClientAdapter"); + this.adapter = + (ClientAdapter) + Class.forName(adapterClass) + .getConstructor(Context.class) + .newInstance(getContext()); + } - return this.adapter; - } + return this.adapter; + } - /** - * Returns the connection timeout. Defaults to 15000. - * - * @return The connection timeout. - */ - public int getSocketConnectTimeoutMs() { - return Integer.parseInt(getHelpedParameters().getFirstValue("socketConnectTimeoutMs", "15000")); - } + /** + * Returns the connection timeout. Defaults to 15000. + * + * @return The connection timeout. + */ + public int getSocketConnectTimeoutMs() { + return Integer.parseInt( + getHelpedParameters().getFirstValue("socketConnectTimeoutMs", "15000")); + } - @Override - public void handle(Request request, Response response) { - try { - ClientCall clientCall = getAdapter().toSpecific(this, request); - getAdapter().commit(clientCall, request, response); - } catch (Exception e) { - getLogger().log(Level.INFO, "Error while handling an HTTP client call", e); - response.setStatus(Status.CONNECTOR_ERROR_INTERNAL, e); - } - } + @Override + public void handle(Request request, Response response) { + try { + ClientCall clientCall = getAdapter().toSpecific(this, request); + getAdapter().commit(clientCall, request, response); + } catch (Exception e) { + getLogger().log(Level.INFO, "Error while handling an HTTP client call", e); + response.setStatus(Status.CONNECTOR_ERROR_INTERNAL, e); + } + } - /** - * Sets the adapter from uniform calls to HTTP calls. - * - * @param adapter The adapter to set. - */ - public void setAdapter(ClientAdapter adapter) { - this.adapter = adapter; - } + /** + * Sets the adapter from uniform calls to HTTP calls. + * + * @param adapter The adapter to set. + */ + public void setAdapter(ClientAdapter adapter) { + this.adapter = adapter; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/HttpRequest.java b/org.restlet/src/main/java/org/restlet/engine/adapter/HttpRequest.java index 699c747d2f..c191320323 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/HttpRequest.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/HttpRequest.java @@ -1,18 +1,49 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; +import static org.restlet.engine.header.HeaderConstants.HEADER_IF_MATCH; +import static org.restlet.engine.header.HeaderConstants.HEADER_IF_MODIFIED_SINCE; +import static org.restlet.engine.header.HeaderConstants.HEADER_IF_NONE_MATCH; +import static org.restlet.engine.header.HeaderConstants.HEADER_IF_RANGE; +import static org.restlet.engine.header.HeaderConstants.HEADER_IF_UNMODIFIED_SINCE; + +import java.io.IOException; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; -import org.restlet.data.*; -import org.restlet.engine.header.*; +import org.restlet.data.CacheDirective; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ClientInfo; +import org.restlet.data.Conditions; +import org.restlet.data.Cookie; +import org.restlet.data.Header; +import org.restlet.data.Method; +import org.restlet.data.Range; +import org.restlet.data.RecipientInfo; +import org.restlet.data.Reference; +import org.restlet.data.Tag; +import org.restlet.data.Warning; +import org.restlet.engine.header.CacheDirectiveReader; +import org.restlet.engine.header.CookieReader; +import org.restlet.engine.header.ExpectationReader; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.engine.header.HeaderReader; +import org.restlet.engine.header.PreferenceReader; +import org.restlet.engine.header.RangeReader; +import org.restlet.engine.header.RecipientInfoReader; +import org.restlet.engine.header.StringReader; +import org.restlet.engine.header.TagReader; +import org.restlet.engine.header.WarningReader; import org.restlet.engine.security.AuthenticatorUtils; import org.restlet.engine.util.DateUtils; import org.restlet.engine.util.ReferenceUtils; @@ -20,529 +51,582 @@ import org.restlet.representation.Representation; import org.restlet.util.Series; -import java.io.IOException; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.logging.Level; - -import static org.restlet.engine.header.HeaderConstants.*; - /** * Request wrapper for server HTTP calls. - * + * * @author Jerome Louvel */ public class HttpRequest extends Request { - /** - * Indicates if the access control data for request headers was parsed and added - */ - private volatile boolean accessControlRequestHeadersAdded; - - /** - * Indicates if the access control data for request methods was parsed and added - */ - private volatile boolean accessControlRequestMethodAdded; - - /** Indicates if the cache control data was parsed and added. */ - private volatile boolean cacheDirectivesAdded; - - /** Indicates if the client data was parsed and added. */ - private volatile boolean clientAdded; - - /** Indicates if the conditions were parsed and added. */ - private volatile boolean conditionAdded; - - /** The context of the HTTP server connector that issued the call. */ - private volatile Context context; - - /** Indicates if the cookies were parsed and added. */ - private volatile boolean cookiesAdded; - - /** Indicates if the request entity was added. */ - private volatile boolean entityAdded; - - /** The low-level HTTP call. */ - private volatile ServerCall httpCall; - - /** Indicates if the proxy security data was parsed and added. */ - private volatile boolean proxySecurityAdded; - - /** Indicates if the ranges data was parsed and added. */ - private volatile boolean rangesAdded; - - /** Indicates if the recipients info was parsed and added. */ - private volatile boolean recipientsInfoAdded; - - /** Indicates if the referrer was parsed and added. */ - private volatile boolean referrerAdded; - - /** Indicates if the security data was parsed and added. */ - private volatile boolean securityAdded; - - /** Indicates if the warning data was parsed and added. */ - private volatile boolean warningsAdded; - - /** - * Constructor. - * - * @param context The context of the HTTP server connector that issued the - * call. - * @param httpCall The low-level HTTP server call. - */ - public HttpRequest(Context context, ServerCall httpCall) { - this.context = context; - this.clientAdded = false; - this.conditionAdded = false; - this.cookiesAdded = false; - this.entityAdded = false; - this.referrerAdded = false; - this.securityAdded = false; - this.proxySecurityAdded = false; - this.recipientsInfoAdded = false; - this.warningsAdded = false; - this.httpCall = httpCall; - - // Set the properties - setMethod(Method.valueOf(httpCall.getMethod())); - - // Set the host reference - StringBuilder sb = new StringBuilder(); - sb.append(httpCall.getProtocol().getSchemeName()).append("://"); - sb.append(httpCall.getHostDomain()); - if ((httpCall.getHostPort() != -1) && (httpCall.getHostPort() != httpCall.getProtocol().getDefaultPort())) { - sb.append(':').append(httpCall.getHostPort()); - } - setHostRef(sb.toString()); - - // Set the resource reference - if (httpCall.getRequestUri() != null) { - setResourceRef(new Reference(getHostRef(), httpCall.getRequestUri())); - - if (getResourceRef().isRelative()) { - // Take care of the "/" between the host part and the segments. - if (!httpCall.getRequestUri().startsWith("/")) { - setResourceRef(new Reference(getHostRef().toString() + "/" + httpCall.getRequestUri())); - } else { - setResourceRef(new Reference(getHostRef().toString() + httpCall.getRequestUri())); - } - } - - setOriginalRef(ReferenceUtils.getOriginalRef(getResourceRef(), httpCall.getRequestHeaders())); - } - - // Set the request date - String dateHeader = httpCall.getRequestHeaders().getFirstValue(HeaderConstants.HEADER_DATE, true); - Date date = null; - if (dateHeader != null) { - date = DateUtils.parse(dateHeader); - } - - if (date == null) { - date = new Date(); - } - - setDate(date); - } - - @Override - public boolean abort() { - return getHttpCall().abort(); - } - - @Override - public void flushBuffers() throws IOException { - getHttpCall().flushBuffers(); - } - - @Override - public Set getAccessControlRequestHeaders() { - Set result = super.getAccessControlRequestHeaders(); - if (!accessControlRequestHeadersAdded) { - for (String header : getHttpCall().getRequestHeaders() - .getValuesArray(HeaderConstants.HEADER_ACCESS_CONTROL_REQUEST_HEADERS, true)) { - new StringReader(header).addValues(result); - } - accessControlRequestHeadersAdded = true; - } - return result; - } - - @Override - public Method getAccessControlRequestMethod() { - Method result = super.getAccessControlRequestMethod(); - if (!accessControlRequestMethodAdded) { - String header = getHttpCall().getRequestHeaders() - .getFirstValue(HeaderConstants.HEADER_ACCESS_CONTROL_REQUEST_METHOD, true); - if (header != null) { - result = Method.valueOf(header); - super.setAccessControlRequestMethod(result); - } - accessControlRequestMethodAdded = true; - } - return result; - } - - @Override - public List getCacheDirectives() { - List result = super.getCacheDirectives(); - - if (!cacheDirectivesAdded) { - for (Header header : getHttpCall().getRequestHeaders().subList(HeaderConstants.HEADER_CACHE_CONTROL)) { - CacheDirectiveReader.addValues(header, result); - } - - cacheDirectivesAdded = true; - } - - return result; - } - - @Override - public ChallengeResponse getChallengeResponse() { - ChallengeResponse result = super.getChallengeResponse(); - - if (!this.securityAdded) { - // Extract the header value - String authorization = getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_AUTHORIZATION); - - // Set the challenge response - result = AuthenticatorUtils.parseResponse(this, authorization, getHttpCall().getRequestHeaders()); - setChallengeResponse(result); - this.securityAdded = true; - } - - return result; - } - - /** - * Returns the client-specific information. - * - * @return The client-specific information. - */ - @Override - public ClientInfo getClientInfo() { - final ClientInfo result = super.getClientInfo(); - - if (!this.clientAdded) { - // Extract the header values - String acceptMediaType = getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_ACCEPT); - String acceptCharset = getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_ACCEPT_CHARSET); - String acceptEncoding = getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_ACCEPT_ENCODING); - String acceptLanguage = getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_ACCEPT_LANGUAGE); - String acceptPatch = getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_ACCEPT_PATCH); - String expect = getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_EXPECT); - - // Parse the headers and update the call preferences - - // Parse the Accept* headers. If an error occurs during the parsing - // of each header, the error is traced and we keep on with the other - // headers. - try { - PreferenceReader.addCharacterSets(acceptCharset, result); - } catch (Exception e) { - this.context.getLogger().log(Level.INFO, e.getMessage()); - } - - try { - PreferenceReader.addEncodings(acceptEncoding, result); - } catch (Exception e) { - this.context.getLogger().log(Level.INFO, e.getMessage()); - } - - try { - PreferenceReader.addLanguages(acceptLanguage, result); - } catch (Exception e) { - this.context.getLogger().log(Level.INFO, e.getMessage()); - } - - try { - PreferenceReader.addMediaTypes(acceptMediaType, result); - } catch (Exception e) { - this.context.getLogger().log(Level.INFO, e.getMessage()); - } - - try { - PreferenceReader.addPatches(acceptPatch, result); - } catch (Exception e) { - this.context.getLogger().log(Level.INFO, e.getMessage()); - } - - try { - ExpectationReader.addValues(expect, result); - } catch (Exception e) { - this.context.getLogger().log(Level.INFO, e.getMessage()); - } - - // Set other properties - result.setAgent(getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_USER_AGENT)); - result.setFrom(getHttpCall().getRequestHeaders().getFirstValue(HeaderConstants.HEADER_FROM, true)); - result.setAddress(getHttpCall().getClientAddress()); - result.setPort(getHttpCall().getClientPort()); - - if (getHttpCall().getUserPrincipal() != null) { - result.getPrincipals().add(getHttpCall().getUserPrincipal()); - } - - if (this.context != null) { - // Special handling for the non standard but common - // "X-Forwarded-For" header. - final boolean useForwardedForHeader = Boolean - .parseBoolean(this.context.getParameters().getFirstValue("useForwardedForHeader", false)); - if (useForwardedForHeader) { - // Lookup the "X-Forwarded-For" header supported by popular - // proxies and caches. - final String header = getHttpCall().getRequestHeaders() - .getValues(HeaderConstants.HEADER_X_FORWARDED_FOR); - if (header != null) { - final String[] addresses = header.split(","); + /** Indicates if the access control data for request headers was parsed and added */ + private volatile boolean accessControlRequestHeadersAdded; + + /** Indicates if the access control data for request methods was parsed and added */ + private volatile boolean accessControlRequestMethodAdded; + + /** Indicates if the cache control data was parsed and added. */ + private volatile boolean cacheDirectivesAdded; + + /** Indicates if the client data was parsed and added. */ + private volatile boolean clientAdded; + + /** Indicates if the conditions were parsed and added. */ + private volatile boolean conditionAdded; + + /** The context of the HTTP server connector that issued the call. */ + private volatile Context context; + + /** Indicates if the cookies were parsed and added. */ + private volatile boolean cookiesAdded; + + /** Indicates if the request entity was added. */ + private volatile boolean entityAdded; + + /** The low-level HTTP call. */ + private volatile ServerCall httpCall; + + /** Indicates if the proxy security data was parsed and added. */ + private volatile boolean proxySecurityAdded; + + /** Indicates if the ranges data was parsed and added. */ + private volatile boolean rangesAdded; + + /** Indicates if the recipients info was parsed and added. */ + private volatile boolean recipientsInfoAdded; + + /** Indicates if the referrer was parsed and added. */ + private volatile boolean referrerAdded; + + /** Indicates if the security data was parsed and added. */ + private volatile boolean securityAdded; + + /** Indicates if the warning data was parsed and added. */ + private volatile boolean warningsAdded; + + /** + * Constructor. + * + * @param context The context of the HTTP server connector that issued the call. + * @param httpCall The low-level HTTP server call. + */ + public HttpRequest(Context context, ServerCall httpCall) { + this.context = context; + this.clientAdded = false; + this.conditionAdded = false; + this.cookiesAdded = false; + this.entityAdded = false; + this.referrerAdded = false; + this.securityAdded = false; + this.proxySecurityAdded = false; + this.recipientsInfoAdded = false; + this.warningsAdded = false; + this.httpCall = httpCall; + + // Set the properties + setMethod(Method.valueOf(httpCall.getMethod())); + + // Set the host reference + StringBuilder sb = new StringBuilder(); + sb.append(httpCall.getProtocol().getSchemeName()).append("://"); + sb.append(httpCall.getHostDomain()); + if ((httpCall.getHostPort() != -1) + && (httpCall.getHostPort() != httpCall.getProtocol().getDefaultPort())) { + sb.append(':').append(httpCall.getHostPort()); + } + setHostRef(sb.toString()); + + // Set the resource reference + if (httpCall.getRequestUri() != null) { + setResourceRef(new Reference(getHostRef(), httpCall.getRequestUri())); + + if (getResourceRef().isRelative()) { + // Take care of the "/" between the host part and the segments. + if (!httpCall.getRequestUri().startsWith("/")) { + setResourceRef( + new Reference( + getHostRef().toString() + "/" + httpCall.getRequestUri())); + } else { + setResourceRef( + new Reference(getHostRef().toString() + httpCall.getRequestUri())); + } + } + + setOriginalRef( + ReferenceUtils.getOriginalRef(getResourceRef(), httpCall.getRequestHeaders())); + } + + // Set the request date + String dateHeader = + httpCall.getRequestHeaders().getFirstValue(HeaderConstants.HEADER_DATE, true); + Date date = null; + if (dateHeader != null) { + date = DateUtils.parse(dateHeader); + } + + if (date == null) { + date = new Date(); + } + + setDate(date); + } + + @Override + public boolean abort() { + return getHttpCall().abort(); + } + + @Override + public void flushBuffers() throws IOException { + getHttpCall().flushBuffers(); + } + + @Override + public Set getAccessControlRequestHeaders() { + Set result = super.getAccessControlRequestHeaders(); + if (!accessControlRequestHeadersAdded) { + for (String header : + getHttpCall() + .getRequestHeaders() + .getValuesArray( + HeaderConstants.HEADER_ACCESS_CONTROL_REQUEST_HEADERS, true)) { + new StringReader(header).addValues(result); + } + accessControlRequestHeadersAdded = true; + } + return result; + } + + @Override + public Method getAccessControlRequestMethod() { + Method result = super.getAccessControlRequestMethod(); + if (!accessControlRequestMethodAdded) { + String header = + getHttpCall() + .getRequestHeaders() + .getFirstValue( + HeaderConstants.HEADER_ACCESS_CONTROL_REQUEST_METHOD, true); + if (header != null) { + result = Method.valueOf(header); + super.setAccessControlRequestMethod(result); + } + accessControlRequestMethodAdded = true; + } + return result; + } + + @Override + public List getCacheDirectives() { + List result = super.getCacheDirectives(); + + if (!cacheDirectivesAdded) { + for (Header header : + getHttpCall() + .getRequestHeaders() + .subList(HeaderConstants.HEADER_CACHE_CONTROL)) { + CacheDirectiveReader.addValues(header, result); + } + + cacheDirectivesAdded = true; + } + + return result; + } + + @Override + public ChallengeResponse getChallengeResponse() { + ChallengeResponse result = super.getChallengeResponse(); + + if (!this.securityAdded) { + // Extract the header value + String authorization = + getHttpCall() + .getRequestHeaders() + .getValues(HeaderConstants.HEADER_AUTHORIZATION); + + // Set the challenge response + result = + AuthenticatorUtils.parseResponse( + this, authorization, getHttpCall().getRequestHeaders()); + setChallengeResponse(result); + this.securityAdded = true; + } + + return result; + } + + /** + * Returns the client-specific information. + * + * @return The client-specific information. + */ + @Override + public ClientInfo getClientInfo() { + final ClientInfo result = super.getClientInfo(); + + if (!this.clientAdded) { + // Extract the header values + String acceptMediaType = + getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_ACCEPT); + String acceptCharset = + getHttpCall() + .getRequestHeaders() + .getValues(HeaderConstants.HEADER_ACCEPT_CHARSET); + String acceptEncoding = + getHttpCall() + .getRequestHeaders() + .getValues(HeaderConstants.HEADER_ACCEPT_ENCODING); + String acceptLanguage = + getHttpCall() + .getRequestHeaders() + .getValues(HeaderConstants.HEADER_ACCEPT_LANGUAGE); + String acceptPatch = + getHttpCall() + .getRequestHeaders() + .getValues(HeaderConstants.HEADER_ACCEPT_PATCH); + String expect = + getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_EXPECT); + + // Parse the headers and update the call preferences + + // Parse the Accept* headers. If an error occurs during the parsing + // of each header, the error is traced and we keep on with the other + // headers. + try { + PreferenceReader.addCharacterSets(acceptCharset, result); + } catch (Exception e) { + this.context.getLogger().log(Level.INFO, e.getMessage()); + } + + try { + PreferenceReader.addEncodings(acceptEncoding, result); + } catch (Exception e) { + this.context.getLogger().log(Level.INFO, e.getMessage()); + } + + try { + PreferenceReader.addLanguages(acceptLanguage, result); + } catch (Exception e) { + this.context.getLogger().log(Level.INFO, e.getMessage()); + } + + try { + PreferenceReader.addMediaTypes(acceptMediaType, result); + } catch (Exception e) { + this.context.getLogger().log(Level.INFO, e.getMessage()); + } + + try { + PreferenceReader.addPatches(acceptPatch, result); + } catch (Exception e) { + this.context.getLogger().log(Level.INFO, e.getMessage()); + } + + try { + ExpectationReader.addValues(expect, result); + } catch (Exception e) { + this.context.getLogger().log(Level.INFO, e.getMessage()); + } + + // Set other properties + result.setAgent( + getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_USER_AGENT)); + result.setFrom( + getHttpCall() + .getRequestHeaders() + .getFirstValue(HeaderConstants.HEADER_FROM, true)); + result.setAddress(getHttpCall().getClientAddress()); + result.setPort(getHttpCall().getClientPort()); + + if (getHttpCall().getUserPrincipal() != null) { + result.getPrincipals().add(getHttpCall().getUserPrincipal()); + } + + if (this.context != null) { + // Special handling for the non-standard but common + // "X-Forwarded-For" header. + final boolean useForwardedForHeader = + Boolean.parseBoolean( + this.context + .getParameters() + .getFirstValue("useForwardedForHeader", false)); + if (useForwardedForHeader) { + // Lookup the "X-Forwarded-For" header supported by popular + // proxies and caches. + final String header = + getHttpCall() + .getRequestHeaders() + .getValues(HeaderConstants.HEADER_X_FORWARDED_FOR); + if (header != null) { + final String[] addresses = header.split(","); for (String s : addresses) { String address = s.trim(); result.getForwardedAddresses().add(address); } - } - } - } - - this.clientAdded = true; - } - - return result; - } - - /** - * Returns the condition data applying to this call. - * - * @return The condition data applying to this call. - */ - @Override - public Conditions getConditions() { - final Conditions result = super.getConditions(); - - if (!this.conditionAdded) { - // Extract the header values - String ifMatchHeader = getHttpCall().getRequestHeaders().getValues(HEADER_IF_MATCH); - String ifNoneMatchHeader = getHttpCall().getRequestHeaders().getValues(HEADER_IF_NONE_MATCH); - Date ifModifiedSince = null; - Date ifUnmodifiedSince = null; - String ifRangeHeader = getHttpCall().getRequestHeaders().getFirstValue(HEADER_IF_RANGE, true); - - for (Header header : getHttpCall().getRequestHeaders()) { - if (header.getName().equalsIgnoreCase(HEADER_IF_MODIFIED_SINCE)) { - ifModifiedSince = HeaderReader.readDate(header.getValue(), false); - } else if (header.getName().equalsIgnoreCase(HEADER_IF_UNMODIFIED_SINCE)) { - ifUnmodifiedSince = HeaderReader.readDate(header.getValue(), false); - } - } - - // Set the If-Modified-Since date - if ((ifModifiedSince != null) && (ifModifiedSince.getTime() != -1)) { - result.setModifiedSince(ifModifiedSince); - } - - // Set the If-Unmodified-Since date - if ((ifUnmodifiedSince != null) && (ifUnmodifiedSince.getTime() != -1)) { - result.setUnmodifiedSince(ifUnmodifiedSince); - } - - // Set the If-Match tags - if (ifMatchHeader != null) { - try { - new TagReader(ifMatchHeader).addValues(result.getMatch()); - } catch (Exception e) { - this.context.getLogger().log(Level.INFO, "Unable to process the if-match header: " + ifMatchHeader); - } - } - - // Set the If-None-Match tags - if (ifNoneMatchHeader != null) { - try { - new TagReader(ifNoneMatchHeader).addValues(result.getNoneMatch()); - } catch (Exception e) { - this.context.getLogger().log(Level.INFO, - "Unable to process the if-none-match header: " + ifNoneMatchHeader); - } - } - - if (!StringUtils.isNullOrEmpty(ifRangeHeader)) { - Tag tag = Tag.parse(ifRangeHeader); - - if (tag != null) { - result.setRangeTag(tag); - } else { - Date date = HeaderReader.readDate(ifRangeHeader, false); - result.setRangeDate(date); - } - } - - this.conditionAdded = true; - } - - return result; - } - - /** - * Returns the cookies provided by the client. - * - * @return The cookies provided by the client. - */ - @Override - public Series getCookies() { - Series result = super.getCookies(); - - if (!this.cookiesAdded) { - String cookieValues = getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_COOKIE); - - if (cookieValues != null) { - new CookieReader(cookieValues).addValues(result); - } - - this.cookiesAdded = true; - } - - return result; - } - - /** - * Returns the representation provided by the client. - * - * @return The representation provided by the client. - */ - @Override - public Representation getEntity() { - if (!this.entityAdded) { - setEntity(getHttpCall().getRequestEntity()); - this.entityAdded = true; - } - - return super.getEntity(); - } - - /** - * Returns the low-level HTTP call. - * - * @return The low-level HTTP call. - */ - public ServerCall getHttpCall() { - return this.httpCall; - } - - @Override - public ChallengeResponse getProxyChallengeResponse() { - ChallengeResponse result = super.getProxyChallengeResponse(); - - if (!this.proxySecurityAdded) { - // Extract the header value - final String authorization = getHttpCall().getRequestHeaders() - .getValues(HeaderConstants.HEADER_PROXY_AUTHORIZATION); - - // Set the challenge response - result = AuthenticatorUtils.parseResponse(this, authorization, getHttpCall().getRequestHeaders()); - setProxyChallengeResponse(result); - this.proxySecurityAdded = true; - } - - return result; - } - - @Override - public List getRanges() { - final List result = super.getRanges(); - - if (!this.rangesAdded) { - // Extract the header value - final String ranges = getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_RANGE); - result.addAll(RangeReader.read(ranges)); - - this.rangesAdded = true; - } - - return result; - } - - @Override - public List getRecipientsInfo() { - List result = super.getRecipientsInfo(); - if (!recipientsInfoAdded) { - for (String header : getHttpCall().getRequestHeaders().getValuesArray(HeaderConstants.HEADER_VIA, true)) { - new RecipientInfoReader(header).addValues(result); - } - recipientsInfoAdded = true; - } - return result; - } - - /** - * Returns the referrer reference if available. - * - * @return The referrer reference. - */ - @Override - public Reference getReferrerRef() { - if (!this.referrerAdded) { - final String referrerValue = getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_REFERRER); - if (referrerValue != null) { - setReferrerRef(new Reference(referrerValue)); - } - - this.referrerAdded = true; - } - - return super.getReferrerRef(); - } - - @Override - public List getWarnings() { - List result = super.getWarnings(); - if (!warningsAdded) { - for (String header : getHttpCall().getRequestHeaders().getValuesArray(HeaderConstants.HEADER_WARNING, - true)) { - new WarningReader(header).addValues(result); - } - warningsAdded = true; - } - return result; - } - - @Override - public void setAccessControlRequestHeaders(Set accessControlRequestHeaders) { - super.setAccessControlRequestHeaders(accessControlRequestHeaders); - this.accessControlRequestHeadersAdded = true; - } - - @Override - public void setAccessControlRequestMethod(Method accessControlRequestMethod) { - super.setAccessControlRequestMethod(accessControlRequestMethod); - this.accessControlRequestMethodAdded = true; - } - - @Override - public void setChallengeResponse(ChallengeResponse response) { - super.setChallengeResponse(response); - this.securityAdded = true; - } - - @Override - public void setEntity(Representation entity) { - super.setEntity(entity); - this.entityAdded = true; - } - - @Override - public void setProxyChallengeResponse(ChallengeResponse response) { - super.setProxyChallengeResponse(response); - this.proxySecurityAdded = true; - } - - @Override - public void setRecipientsInfo(List recipientsInfo) { - super.setRecipientsInfo(recipientsInfo); - this.recipientsInfoAdded = true; - } - - @Override - public void setWarnings(List warnings) { - super.setWarnings(warnings); - this.warningsAdded = true; - } + } + } + } + + this.clientAdded = true; + } + + return result; + } + + /** + * Returns the condition data applying to this call. + * + * @return The condition data applying to this call. + */ + @Override + public Conditions getConditions() { + final Conditions result = super.getConditions(); + + if (!this.conditionAdded) { + // Extract the header values + String ifMatchHeader = getHttpCall().getRequestHeaders().getValues(HEADER_IF_MATCH); + String ifNoneMatchHeader = + getHttpCall().getRequestHeaders().getValues(HEADER_IF_NONE_MATCH); + Date ifModifiedSince = null; + Date ifUnmodifiedSince = null; + String ifRangeHeader = + getHttpCall().getRequestHeaders().getFirstValue(HEADER_IF_RANGE, true); + + for (Header header : getHttpCall().getRequestHeaders()) { + if (header.getName().equalsIgnoreCase(HEADER_IF_MODIFIED_SINCE)) { + ifModifiedSince = HeaderReader.readDate(header.getValue(), false); + } else if (header.getName().equalsIgnoreCase(HEADER_IF_UNMODIFIED_SINCE)) { + ifUnmodifiedSince = HeaderReader.readDate(header.getValue(), false); + } + } + + // Set the If-Modified-Since date + if ((ifModifiedSince != null) && (ifModifiedSince.getTime() != -1)) { + result.setModifiedSince(ifModifiedSince); + } + + // Set the If-Unmodified-Since date + if ((ifUnmodifiedSince != null) && (ifUnmodifiedSince.getTime() != -1)) { + result.setUnmodifiedSince(ifUnmodifiedSince); + } + + // Set the If-Match tags + if (ifMatchHeader != null) { + try { + new TagReader(ifMatchHeader).addValues(result.getMatch()); + } catch (Exception e) { + this.context + .getLogger() + .log( + Level.INFO, + "Unable to process the if-match header: {0}", + ifMatchHeader); + } + } + + // Set the If-None-Match tags + if (ifNoneMatchHeader != null) { + try { + new TagReader(ifNoneMatchHeader).addValues(result.getNoneMatch()); + } catch (Exception e) { + this.context + .getLogger() + .log( + Level.INFO, + "Unable to process the if-none-match header: {0}", + ifNoneMatchHeader); + } + } + + if (!StringUtils.isNullOrEmpty(ifRangeHeader)) { + Tag tag = Tag.parse(ifRangeHeader); + + if (tag != null) { + result.setRangeTag(tag); + } else { + Date date = HeaderReader.readDate(ifRangeHeader, false); + result.setRangeDate(date); + } + } + + this.conditionAdded = true; + } + + return result; + } + + /** + * Returns the cookies provided by the client. + * + * @return The cookies provided by the client. + */ + @Override + public Series getCookies() { + Series result = super.getCookies(); + + if (!this.cookiesAdded) { + String cookieValues = + getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_COOKIE); + + if (cookieValues != null) { + new CookieReader(cookieValues).addValues(result); + } + + this.cookiesAdded = true; + } + + return result; + } + + /** + * Returns the representation provided by the client. + * + * @return The representation provided by the client. + */ + @Override + public Representation getEntity() { + if (!this.entityAdded) { + setEntity(getHttpCall().getRequestEntity()); + this.entityAdded = true; + } + + return super.getEntity(); + } + + /** + * Returns the low-level HTTP call. + * + * @return The low-level HTTP call. + */ + public ServerCall getHttpCall() { + return this.httpCall; + } + + @Override + public ChallengeResponse getProxyChallengeResponse() { + ChallengeResponse result = super.getProxyChallengeResponse(); + + if (!this.proxySecurityAdded) { + // Extract the header value + final String authorization = + getHttpCall() + .getRequestHeaders() + .getValues(HeaderConstants.HEADER_PROXY_AUTHORIZATION); + + // Set the challenge response + result = + AuthenticatorUtils.parseResponse( + this, authorization, getHttpCall().getRequestHeaders()); + setProxyChallengeResponse(result); + this.proxySecurityAdded = true; + } + + return result; + } + + @Override + public List getRanges() { + final List result = super.getRanges(); + + if (!this.rangesAdded) { + // Extract the header value + final String ranges = + getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_RANGE); + result.addAll(RangeReader.read(ranges)); + + this.rangesAdded = true; + } + + return result; + } + + @Override + public List getRecipientsInfo() { + List result = super.getRecipientsInfo(); + if (!recipientsInfoAdded) { + for (String header : + getHttpCall() + .getRequestHeaders() + .getValuesArray(HeaderConstants.HEADER_VIA, true)) { + new RecipientInfoReader(header).addValues(result); + } + recipientsInfoAdded = true; + } + return result; + } + + /** + * Returns the referrer reference if available. + * + * @return The referrer reference. + */ + @Override + public Reference getReferrerRef() { + if (!this.referrerAdded) { + final String referrerValue = + getHttpCall().getRequestHeaders().getValues(HeaderConstants.HEADER_REFERRER); + if (referrerValue != null) { + setReferrerRef(new Reference(referrerValue)); + } + + this.referrerAdded = true; + } + + return super.getReferrerRef(); + } + + @Override + public List getWarnings() { + List result = super.getWarnings(); + if (!warningsAdded) { + for (String header : + getHttpCall() + .getRequestHeaders() + .getValuesArray(HeaderConstants.HEADER_WARNING, true)) { + new WarningReader(header).addValues(result); + } + warningsAdded = true; + } + return result; + } + + @Override + public void setAccessControlRequestHeaders(Set accessControlRequestHeaders) { + super.setAccessControlRequestHeaders(accessControlRequestHeaders); + this.accessControlRequestHeadersAdded = true; + } + + @Override + public void setAccessControlRequestMethod(Method accessControlRequestMethod) { + super.setAccessControlRequestMethod(accessControlRequestMethod); + this.accessControlRequestMethodAdded = true; + } + + @Override + public void setChallengeResponse(ChallengeResponse response) { + super.setChallengeResponse(response); + this.securityAdded = true; + } + + @Override + public void setEntity(Representation entity) { + super.setEntity(entity); + this.entityAdded = true; + } + + @Override + public void setProxyChallengeResponse(ChallengeResponse response) { + super.setProxyChallengeResponse(response); + this.proxySecurityAdded = true; + } + + @Override + public void setRecipientsInfo(List recipientsInfo) { + super.setRecipientsInfo(recipientsInfo); + this.recipientsInfoAdded = true; + } + + @Override + public void setWarnings(List warnings) { + super.setWarnings(warnings); + this.warningsAdded = true; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/HttpResponse.java b/org.restlet/src/main/java/org/restlet/engine/adapter/HttpResponse.java index 6f933eae40..2b088a2f76 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/HttpResponse.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/HttpResponse.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; import org.restlet.Request; @@ -17,70 +16,69 @@ /** * Response wrapper for server HTTP calls. - * + * * @author Jerome Louvel */ public class HttpResponse extends Response { - /** - * Adds a new header to the given request. - * - * @param response The response to update. - * @param headerName The header name to add. - * @param headerValue The header value to add. - */ - public static void addHeader(Response response, String headerName, String headerValue) { - if (response instanceof HttpResponse) { - response.getHeaders().add(headerName, headerValue); - } - } - - /** The low-level HTTP call. */ - private volatile ServerCall httpCall; + /** + * Adds a new header to the given request. + * + * @param response The response to update. + * @param headerName The header name to add. + * @param headerValue The header value to add. + */ + public static void addHeader(Response response, String headerName, String headerValue) { + if (response instanceof HttpResponse) { + response.getHeaders().add(headerName, headerValue); + } + } - /** Indicates if the server data was parsed and added. */ - private volatile boolean serverAdded; + /** The low-level HTTP call. */ + private volatile ServerCall httpCall; - /** - * Constructor. - * - * @param httpCall The low-level HTTP server call. - * @param request The associated high-level request. - */ - public HttpResponse(ServerCall httpCall, Request request) { - super(request); - this.serverAdded = false; - this.httpCall = httpCall; + /** Indicates if the server data was parsed and added. */ + private volatile boolean serverAdded; - // Set the properties - setStatus(Status.SUCCESS_OK); - } + /** + * Constructor. + * + * @param httpCall The low-level HTTP server call. + * @param request The associated high-level request. + */ + public HttpResponse(ServerCall httpCall, Request request) { + super(request); + this.serverAdded = false; + this.httpCall = httpCall; - /** - * Returns the low-level HTTP call. - * - * @return The low-level HTTP call. - */ - public ServerCall getHttpCall() { - return this.httpCall; - } + // Set the properties + setStatus(Status.SUCCESS_OK); + } - /** - * Returns the server-specific information. - * - * @return The server-specific information. - */ - @Override - public ServerInfo getServerInfo() { - final ServerInfo result = super.getServerInfo(); + /** + * Returns the low-level HTTP call. + * + * @return The low-level HTTP call. + */ + public ServerCall getHttpCall() { + return this.httpCall; + } - if (!this.serverAdded) { - result.setAddress(this.httpCall.getServerAddress()); - result.setAgent(Engine.VERSION_HEADER); - result.setPort(this.httpCall.getServerPort()); - this.serverAdded = true; - } + /** + * Returns the server-specific information. + * + * @return The server-specific information. + */ + @Override + public ServerInfo getServerInfo() { + final ServerInfo result = super.getServerInfo(); - return result; - } + if (!this.serverAdded) { + result.setAddress(this.httpCall.getServerAddress()); + result.setAgent(Engine.VERSION_HEADER); + result.setPort(this.httpCall.getServerPort()); + this.serverAdded = true; + } + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/HttpServerHelper.java b/org.restlet/src/main/java/org/restlet/engine/adapter/HttpServerHelper.java index 70f94db627..bb2a032d9c 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/HttpServerHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/HttpServerHelper.java @@ -1,25 +1,24 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; +import java.lang.reflect.InvocationTargetException; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Server; import org.restlet.engine.Engine; import org.restlet.engine.connector.ServerHelper; -import java.lang.reflect.InvocationTargetException; -import java.util.logging.Level; - /** - * Base HTTP server connector. Here is the list of parameters that are - * supported. They should be set in the Server's context before it is started: + * Base HTTP server connector. Here is the list of parameters that are supported. They should be set + * in the Server's context before it is started: + * * * * @@ -46,84 +45,89 @@ * requests and responses. * *
list of supported parameters
- * + * * @author Jerome Louvel */ public class HttpServerHelper extends ServerHelper { - /** The adapter from HTTP calls to uniform calls. */ - private volatile ServerAdapter adapter; + /** The adapter from HTTP calls to uniform calls. */ + private volatile ServerAdapter adapter; - /** - * Default constructor. Note that many methods assume that a non-null server is - * set to work properly. You can use the setHelped(Server) method for this - * purpose or better rely on the other constructor. - */ - public HttpServerHelper() { - this(null); - } + /** + * Default constructor. Note that many methods assume that a non-null server is set to work + * properly. You can use the setHelped(Server) method for this purpose or better rely on the + * other constructor. + */ + public HttpServerHelper() { + this(null); + } - /** - * Constructor. - * - * @param server The server to help. - */ - public HttpServerHelper(Server server) { - super(server); - this.adapter = null; - } + /** + * Constructor. + * + * @param server The server to help. + */ + public HttpServerHelper(Server server) { + super(server); + this.adapter = null; + } - /** - * Returns the adapter from HTTP calls to uniform calls. - * - * @return the adapter from HTTP calls to uniform calls. - */ - public ServerAdapter getAdapter() { - if (this.adapter == null) { - try { - final String adapterClass = getHelpedParameters().getFirstValue("adapter", - "org.restlet.engine.adapter.ServerAdapter"); - this.adapter = (ServerAdapter) Engine.loadClass(adapterClass).getConstructor(Context.class) - .newInstance(getContext()); - } catch (IllegalArgumentException - | SecurityException - | InstantiationException - | IllegalAccessException - | InvocationTargetException - | NoSuchMethodException - | ClassNotFoundException e) { - getLogger().log(Level.SEVERE, "Unable to create the HTTP server adapter", e); - } + /** + * Returns the adapter from HTTP calls to uniform calls. + * + * @return the adapter from HTTP calls to uniform calls. + */ + public ServerAdapter getAdapter() { + if (this.adapter == null) { + try { + final String adapterClass = + getHelpedParameters() + .getFirstValue( + "adapter", "org.restlet.engine.adapter.ServerAdapter"); + this.adapter = + (ServerAdapter) + Engine.loadClass(adapterClass) + .getConstructor(Context.class) + .newInstance(getContext()); + } catch (IllegalArgumentException + | SecurityException + | InstantiationException + | IllegalAccessException + | InvocationTargetException + | NoSuchMethodException + | ClassNotFoundException e) { + getLogger().log(Level.SEVERE, "Unable to create the HTTP server adapter", e); + } } - return this.adapter; - } + return this.adapter; + } - /** - * Handles the connector call. The default behavior is to create an REST call - * and delegate it to the attached Restlet. - * - * @param httpCall The HTTP server call. - */ - public void handle(ServerCall httpCall) { - try { - HttpRequest request = getAdapter().toRequest(httpCall); - HttpResponse response = new HttpResponse(httpCall, request); - handle(request, response); - getAdapter().commit(response); - } catch (Exception e) { - getLogger().log(Level.WARNING, "Error while handling an HTTP server call", e); - } finally { - Engine.clearThreadLocalVariables(); - } - } + /** + * Handles the connector call. The default behavior is to create an REST call and delegate it to + * the attached Restlet. + * + * @param httpCall The HTTP server call. + */ + public void handle(ServerCall httpCall) { + try { + HttpRequest request = getAdapter().toRequest(httpCall); + HttpResponse response = new HttpResponse(httpCall, request); + handle(request, response); + getAdapter().commit(response); + } catch (Exception e) { + getLogger().log(Level.WARNING, "Error while handling an HTTP server call", e); + } finally { + Engine.clearThreadLocalVariables(); + } + } - /** - * Sets the adapter from HTTP calls to uniform calls. - * - * @param adapter The converter to set. - */ - public void setAdapter(ServerAdapter adapter) { - this.adapter = adapter; - } + /** + * Sets the adapter from HTTP calls to uniform calls. + * + * @param adapter The converter to set. + */ + public void setAdapter(ServerAdapter adapter) { + this.adapter = adapter; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/JettyClientCall.java b/org.restlet/src/main/java/org/restlet/engine/adapter/JettyClientCall.java index 0d563ff75c..2eea2fec83 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/JettyClientCall.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/JettyClientCall.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; import java.io.IOException; @@ -16,8 +15,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; - -import org.eclipse.jetty.client.*; +import org.eclipse.jetty.client.InputStreamRequestContent; +import org.eclipse.jetty.client.InputStreamResponseListener; +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.client.Response; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields.Mutable; @@ -32,47 +33,35 @@ /** * HTTP client connector call based on Jetty's HttpRequest class. - * + * * @author Jerome Louvel * @author Tal Liron */ public class JettyClientCall extends ClientCall { - /** - * The associated HTTP client. - */ + /** The associated HTTP client. */ private final HttpClientHelper clientHelper; - /** - * The wrapped HTTP request. - */ + /** The wrapped HTTP request. */ private final Request request; - /** - * The wrapped HTTP response. - */ + /** The wrapped HTTP response. */ private volatile Response response; - /** - * The wrapped input stream response listener. - */ + /** The wrapped input stream response listener. */ private volatile InputStreamResponseListener inputStreamResponseListener; - /** - * Indicates if the response headers were added. - */ + /** Indicates if the response headers were added. */ private volatile boolean responseHeadersAdded; /** * Constructor. - * - * @param helper The parent HTTP client helper. - * @param method The method name. + * + * @param helper The parent HTTP client helper. + * @param method The method name. * @param requestUri The request URI. - * @throws IOException */ - public JettyClientCall(HttpClientHelper helper, final String method, - final String requestUri) throws IOException { + public JettyClientCall(HttpClientHelper helper, final String method, final String requestUri) { super(helper, method, requestUri); this.clientHelper = helper; @@ -80,17 +69,19 @@ public JettyClientCall(HttpClientHelper helper, final String method, this.request = helper.getHttpClient().newRequest(requestUri); this.request.method(method); - setConfidential(this.request.getURI().getScheme() - .equalsIgnoreCase(Protocol.HTTPS.getSchemeName())); + setConfidential( + this.request + .getURI() + .getScheme() + .equalsIgnoreCase(Protocol.HTTPS.getSchemeName())); } else { - throw new IllegalArgumentException( - "Only HTTP or HTTPS resource URIs are allowed here"); + throw new IllegalArgumentException("Only HTTP or HTTPS resource URIs are allowed here"); } } /** * Returns the HTTP request. - * + * * @return The HTTP request. */ public Request getRequest() { @@ -99,7 +90,7 @@ public Request getRequest() { /** * Returns the HTTP response. - * + * * @return The HTTP response. */ public Response getResponse() { @@ -108,7 +99,7 @@ public Response getResponse() { /** * Returns the input stream response listener. - * + * * @return The input stream response listener. */ public InputStreamResponseListener getInputStreamResponseListener() { @@ -133,27 +124,25 @@ public OutputStream getRequestHeadStream() { @Override public InputStream getResponseEntityStream(long size) { - final InputStreamResponseListener inputStreamResponseListener = getInputStreamResponseListener(); - return inputStreamResponseListener == null ? null - : inputStreamResponseListener.getInputStream(); + final InputStreamResponseListener isrl = getInputStreamResponseListener(); + return isrl == null ? null : isrl.getInputStream(); } /** - * Returns the response entity if available. Note that no metadata is - * associated by default, you have to manually set them from your headers. - * - * As a jetty client decodes the input stream on the fly, we have to clear the - * {@link org.restlet.representation.Representation#getEncodings()} to avoid - * decoding the input stream another time. - * + * Returns the response entity if available. Note that no metadata is associated by default, you + * have to manually set them from your headers. + * + *

As a jetty client decodes the input stream on the fly, we have to clear the {@link + * org.restlet.representation.Representation#getEncodings()} to avoid decoding the input stream + * another time. + * * @param response the Response to get the entity from * @return The response entity if available. */ @Override public Representation getResponseEntity(org.restlet.Response response) { Representation responseEntity = super.getResponseEntity(response); - if (responseEntity != null - && !responseEntity.getEncodings().isEmpty()) { + if (responseEntity != null && !responseEntity.getEncodings().isEmpty()) { responseEntity.getEncodings().clear(); // Entity size is reset accordingly. responseEntity.setSize(Representation.UNKNOWN_SIZE); @@ -163,7 +152,7 @@ public Representation getResponseEntity(org.restlet.Response response) { /** * Returns the modifiable list of response headers. - * + * * @return The modifiable list of response headers. */ @Override @@ -189,7 +178,7 @@ public Series

getResponseHeaders() { /** * Returns the response address.
* Corresponds to the IP address of the responding server. - * + * * @return The response address. */ @Override @@ -199,7 +188,7 @@ public String getServerAddress() { /** * Returns the response status code. - * + * * @return The response status code. */ @Override @@ -208,9 +197,9 @@ public int getStatusCode() { } /** - * Sends the request to the client. Commits the request line, headers, and - * optional entity and send them over the network. - * + * Sends the request to the client. Commits the request line, headers, and optional entity and + * send them over the network. + * * @param request The high-level request. * @return The result status. */ @@ -229,49 +218,52 @@ public Status sendRequest(org.restlet.Request request) { for (Header header : getRequestHeaders()) { final String name = header.getName(); switch (name) { - case HeaderConstants.HEADER_CONTENT_LENGTH: - // skip this header - break; - case HeaderConstants.HEADER_USER_AGENT: - this.request.agent(header.getValue()); - break; - default: - ((Mutable)this.request.getHeaders()).add(name, header.getValue()); - break; + case HeaderConstants.HEADER_CONTENT_LENGTH: + // skip this header + break; + case HeaderConstants.HEADER_USER_AGENT: + this.request.agent(header.getValue()); + break; + default: + ((Mutable) this.request.getHeaders()).add(name, header.getValue()); + break; } } // Ensure that the connection is active this.inputStreamResponseListener = new InputStreamResponseListener(); this.request.send(this.inputStreamResponseListener); - this.response = this.inputStreamResponseListener - .get(clientHelper.getIdleTimeout(), TimeUnit.MILLISECONDS); + this.response = + this.inputStreamResponseListener.get( + clientHelper.getIdleTimeout(), TimeUnit.MILLISECONDS); result = new Status(getStatusCode(), getReasonPhrase()); } catch (IOException e) { - this.clientHelper.getLogger().log(Level.WARNING, - "An error occurred while reading the request entity.", e); + this.clientHelper + .getLogger() + .log(Level.WARNING, "An error occurred while reading the request entity.", e); result = new Status(Status.CONNECTOR_ERROR_INTERNAL, e); // Release the connection getRequest().abort(e); } catch (TimeoutException e) { - this.clientHelper.getLogger().log(Level.WARNING, - "The HTTP request timed out.", e); + this.clientHelper.getLogger().log(Level.WARNING, "The HTTP request timed out.", e); result = new Status(Status.CONNECTOR_ERROR_COMMUNICATION, e); // Release the connection getRequest().abort(e); } catch (InterruptedException e) { - this.clientHelper.getLogger().log(Level.WARNING, - "The HTTP request thread was interrupted.", e); + this.clientHelper + .getLogger() + .log(Level.WARNING, "The HTTP request thread was interrupted.", e); result = new Status(Status.CONNECTOR_ERROR_COMMUNICATION, e); // Release the connection getRequest().abort(e); } catch (ExecutionException e) { - this.clientHelper.getLogger().log(Level.WARNING, - "An error occurred while processing the HTTP request.", e); + this.clientHelper + .getLogger() + .log(Level.WARNING, "An error occurred while processing the HTTP request.", e); result = new Status(Status.CONNECTOR_ERROR_COMMUNICATION, e); // Release the connection @@ -282,13 +274,12 @@ public Status sendRequest(org.restlet.Request request) { } @Override - public void sendRequest(org.restlet.Request request, - org.restlet.Response response, Uniform callback) throws Exception { + public void sendRequest( + org.restlet.Request request, org.restlet.Response response, Uniform callback) { sendRequest(request); final Uniform getOnSent = request.getOnSent(); - if (getOnSent != null) - getOnSent.handle(request, response); + if (getOnSent != null) getOnSent.handle(request, response); if (callback != null) // Transmit to the callback, if any diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/JettyHandler.java b/org.restlet/src/main/java/org/restlet/engine/adapter/JettyHandler.java index 7b46250f03..a0df627ab8 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/JettyHandler.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/JettyHandler.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; import org.eclipse.jetty.server.Handler; @@ -19,10 +18,10 @@ import org.restlet.engine.connector.JettyServerHelper; /** - * Jetty handler that knows how to convert Jetty calls into Restlet calls. This - * handler isn't a full server, if you use it, you need to manually set up the - * Jetty server connector and add this handler to a Jetty server. - * + * Jetty handler that knows how to convert Jetty calls into Restlet calls. This handler isn't a full + * server, if you use it, you need to manually set up the Jetty server connector and add this + * handler to a Jetty server. + * * @author Valdis Rigdon * @author Jerome Louvel * @author Tal Liron @@ -34,7 +33,7 @@ public class JettyHandler extends Handler.Abstract { /** * Constructor for HTTP server connectors. - * + * * @param server Restlet HTTP server connector. */ public JettyHandler(Server server) { @@ -43,7 +42,7 @@ public JettyHandler(Server server) { /** * Constructor for HTTP server connectors. - * + * * @param server Restlet server connector. * @param secure Indicates if the server supports HTTP or HTTPS. */ @@ -68,20 +67,18 @@ protected void doStop() throws Exception { } /** - * Handles a Jetty call by converting it to a Restlet call and giving it for - * processing to the Restlet server. - * - * @param request The Jetty request. + * Handles a Jetty call by converting it to a Restlet call and giving it for processing to the + * Restlet server. + * + * @param request The Jetty request. * @param response The Jetty response. * @param callback The Jetty callback. */ @Override - public boolean handle(Request request, Response response, Callback callback) - throws Exception { - JettyServerCall httpCall = new JettyServerCall(this.helper.getHelped(), - request, response, callback); + public boolean handle(Request request, Response response, Callback callback) throws Exception { + JettyServerCall httpCall = + new JettyServerCall(this.helper.getHelped(), request, response, callback); this.helper.handle(httpCall); return true; } - } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/JettyServerCall.java b/org.restlet/src/main/java/org/restlet/engine/adapter/JettyServerCall.java index 851256ab1d..9d2fdad5f8 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/JettyServerCall.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/JettyServerCall.java @@ -1,14 +1,19 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.cert.Certificate; +import java.util.Arrays; +import java.util.List; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; @@ -19,19 +24,11 @@ import org.eclipse.jetty.util.Callback; import org.restlet.Server; import org.restlet.data.Header; -import org.restlet.engine.header.HeaderConstants; import org.restlet.util.Series; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.cert.Certificate; -import java.util.Arrays; -import java.util.List; - /** * Call that is used by the Jetty HTTP server connectors. - * + * * @author Jerome Louvel * @author Tal Liron */ @@ -51,14 +48,13 @@ public class JettyServerCall extends ServerCall { /** * Constructor. - * - * @param server The parent server. + * + * @param server The parent server. * @param request The wrapped Jetty HTTP request. * @param response The wrapped Jetty HTTP response. * @param callback The wrapped Jetty HTTP callback. */ - public JettyServerCall(Server server, Request request, Response response, - Callback callback) { + public JettyServerCall(Server server, Request request, Response response, Callback callback) { super(server); this.request = request; this.response = response; @@ -84,7 +80,7 @@ public void flushBuffers() throws IOException { /** * Returns the wrapped Jetty HTTP callback. - * + * * @return The wrapped Jetty HTTP callback. */ public Callback getCallback() { @@ -102,7 +98,7 @@ public List getCertificates() { } else { result = null; } - } else { + } else { result = null; } @@ -111,7 +107,7 @@ public List getCertificates() { /** * Returns the wrapped Jetty HTTP request. - * + * * @return The wrapped Jetty HTTP request. */ public Request getRequest() { @@ -120,7 +116,7 @@ public Request getRequest() { /** * Returns the wrapped Jetty HTTP response. - * + * * @return The wrapped Jetty HTTP response. */ public Response getResponse() { @@ -148,7 +144,7 @@ public int getClientPort() { /** * Returns the underlying Jetty's connection. - * + * * @return The underlying Jetty's connection. */ protected Connection getConnection() { @@ -157,7 +153,7 @@ protected Connection getConnection() { /** * Returns the underlying Jetty's endpoint. - * + * * @return The underlying Jetty's endpoint. */ protected EndPoint getEndPoint() { @@ -245,8 +241,7 @@ public boolean isConfidential() { @Override public boolean isConnectionBroken(Throwable exception) { - return (exception instanceof EofException) - || super.isConnectionBroken(exception); + return (exception instanceof EofException) || super.isConnectionBroken(exception); } @Override diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/ServerAdapter.java b/org.restlet/src/main/java/org/restlet/engine/adapter/ServerAdapter.java index 2d6174190c..646918b348 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/ServerAdapter.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/ServerAdapter.java @@ -1,14 +1,22 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; +import static org.restlet.engine.header.HeaderConstants.ATTRIBUTE_HEADERS; +import static org.restlet.engine.header.HeaderConstants.ATTRIBUTE_HTTPS_KEY_SIZE; +import static org.restlet.engine.header.HeaderConstants.ATTRIBUTE_HTTPS_SSL_SESSION_ID; +import static org.restlet.engine.header.HeaderConstants.ATTRIBUTE_VERSION; + +import java.io.IOException; +import java.security.cert.Certificate; +import java.util.List; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.data.Header; import org.restlet.data.Method; @@ -17,196 +25,212 @@ import org.restlet.representation.Representation; import org.restlet.util.Series; -import java.io.IOException; -import java.security.cert.Certificate; -import java.util.List; -import java.util.logging.Level; - -import static org.restlet.engine.header.HeaderConstants.*; - /** * Converter of low-level HTTP server calls into high-level uniform calls. - * + * * @author Jerome Louvel */ public class ServerAdapter extends Adapter { - /** - * Constructor. - * - * @param context The client context. - */ - public ServerAdapter(Context context) { - super(context); - } - - /** - * Adds the entity headers for the handled uniform call. - * - * @param response The response returned. - */ - protected void addEntityHeaders(HttpResponse response) { - Series

responseHeaders = response.getHttpCall().getResponseHeaders(); - Representation entity = response.getEntity(); - HeaderUtils.addEntityHeaders(entity, responseHeaders); - } - - /** - * Adds the response headers for the handled uniform call. - * - * @param response The response returned. - */ - protected void addResponseHeaders(HttpResponse response) { - try { - // Add all the necessary headers - HeaderUtils.addGeneralHeaders(response, response.getHttpCall().getResponseHeaders()); - HeaderUtils.addResponseHeaders(response, response.getHttpCall().getResponseHeaders()); - - // Set the status code in the response - if (response.getStatus() != null) { - response.getHttpCall().setStatusCode(response.getStatus().getCode()); - response.getHttpCall().setReasonPhrase(response.getStatus().getReasonPhrase()); - } - } catch (Exception e) { - getLogger().log(Level.WARNING, "Exception intercepted while adding the response headers", e); - response.getHttpCall().setStatusCode(Status.SERVER_ERROR_INTERNAL.getCode()); - response.getHttpCall().setReasonPhrase(Status.SERVER_ERROR_INTERNAL.getReasonPhrase()); - } - } - - /** - * Commits the changes to a handled uniform call back into the original HTTP - * call. The default implementation first invokes the "addResponseHeaders" then - * asks the "htppCall" to send the response back to the client. - * - * @param response The high-level response. - */ - public void commit(HttpResponse response) { - try { - if ((response.getRequest().getMethod() != null) && response.getRequest().getMethod().equals(Method.HEAD)) { - addEntityHeaders(response); - response.setEntity(null); - } else if (Method.GET.equals(response.getRequest().getMethod()) - && Status.SUCCESS_OK.equals(response.getStatus()) && (!response.isEntityAvailable())) { - addEntityHeaders(response); - getLogger() - .warning("A response with a 200 (Ok) status should have an entity. Make sure that resource \"" - + response.getRequest().getResourceRef() - + "\" returns one or sets the status to 204 (No content)."); - } else if (response.getStatus().equals(Status.SUCCESS_NO_CONTENT)) { - addEntityHeaders(response); - - if (response.isEntityAvailable()) { - getLogger().fine( - "Responses with a 204 (No content) status generally don't have an entity. Only adding entity headers for resource \"" - + response.getRequest().getResourceRef() + "\"."); - response.setEntity(null); - } - } else if (response.getStatus().equals(Status.SUCCESS_RESET_CONTENT)) { - if (response.isEntityAvailable()) { - getLogger().warning( - "Responses with a 205 (Reset content) status can't have an entity. Ignoring the entity for resource \"" - + response.getRequest().getResourceRef() + "\"."); - response.setEntity(null); - } - } else if (response.getStatus().equals(Status.REDIRECTION_NOT_MODIFIED)) { - if (response.getEntity() != null) { - HeaderUtils.addNotModifiedEntityHeaders(response.getEntity(), - response.getHttpCall().getResponseHeaders()); - response.setEntity(null); - } - } else if (response.getStatus().isInformational()) { - if (response.isEntityAvailable()) { - getLogger().warning( - "Responses with an informational (1xx) status can't have an entity. Ignoring the entity for resource \"" - + response.getRequest().getResourceRef() + "\"."); - response.setEntity(null); - } - } else { - addEntityHeaders(response); - - if (!response.isEntityAvailable()) { - if ((response.getEntity() != null) && (response.getEntity().getSize() != 0)) { - getLogger().warning( - "A response with an unavailable and potentially non empty entity was returned. Ignoring the entity for resource \"" - + response.getRequest().getResourceRef() + "\"."); - } - - response.setEntity(null); - } - } - - // Add the response headers - addResponseHeaders(response); - - // Send the response to the client - response.getHttpCall().sendResponse(response); - } catch (Throwable t) { - if (response.getHttpCall().isConnectionBroken(t)) { - // output a single log line for this common case to avoid filling server logs - getLogger().log(Level.INFO, - "The connection was broken. It was probably closed by the client. Reason: " + t.getMessage()); - } else { - getLogger().log(Level.SEVERE, "An exception occurred writing the response entity", t); - response.getHttpCall().setStatusCode(Status.SERVER_ERROR_INTERNAL.getCode()); - response.getHttpCall().setReasonPhrase("An exception occurred writing the response entity"); - response.setEntity(null); - - try { - response.getHttpCall().sendResponse(response); - } catch (IOException ioe) { - getLogger().log(Level.WARNING, "Unable to send error response", ioe); - } - } - } finally { - response.getHttpCall().complete(); - - if (response.getOnSent() != null) { - response.getOnSent().handle(response.getRequest(), response); - } - } - } - - /** - * Converts a low-level HTTP call into a high-level uniform request. - * - * @param httpCall The low-level HTTP call. - * @return A new high-level uniform request. - */ - public HttpRequest toRequest(ServerCall httpCall) { - HttpRequest result = new HttpRequest(getContext(), httpCall); - result.getAttributes().put(ATTRIBUTE_HEADERS, httpCall.getRequestHeaders()); - - if (httpCall.getVersion() != null) { - result.getAttributes().put(ATTRIBUTE_VERSION, httpCall.getVersion()); - } - - if (httpCall.isConfidential()) { - List clientCertificates = httpCall.getCertificates(); - - if (clientCertificates != null) { - result.getClientInfo().setCertificates(clientCertificates); - } - - String cipherSuite = httpCall.getCipherSuite(); - - if (cipherSuite != null) { - result.getClientInfo().setCipherSuite(cipherSuite); - } - - Integer keySize = httpCall.getSslKeySize(); - - if (keySize != null) { - result.getAttributes().put(ATTRIBUTE_HTTPS_KEY_SIZE, keySize); - } - - String sslSessionId = httpCall.getSslSessionId(); - - if (sslSessionId != null) { - result.getAttributes().put(ATTRIBUTE_HTTPS_SSL_SESSION_ID, sslSessionId); - } - } - - return result; - } + /** + * Constructor. + * + * @param context The client context. + */ + public ServerAdapter(Context context) { + super(context); + } + + /** + * Adds the entity headers for the handled uniform call. + * + * @param response The response returned. + */ + protected void addEntityHeaders(HttpResponse response) { + Series
responseHeaders = response.getHttpCall().getResponseHeaders(); + Representation entity = response.getEntity(); + HeaderUtils.addEntityHeaders(entity, responseHeaders); + } + + /** + * Adds the response headers for the handled uniform call. + * + * @param response The response returned. + */ + protected void addResponseHeaders(HttpResponse response) { + try { + // Add all the necessary headers + HeaderUtils.addGeneralHeaders(response, response.getHttpCall().getResponseHeaders()); + HeaderUtils.addResponseHeaders(response, response.getHttpCall().getResponseHeaders()); + + // Set the status code in the response + if (response.getStatus() != null) { + response.getHttpCall().setStatusCode(response.getStatus().getCode()); + response.getHttpCall().setReasonPhrase(response.getStatus().getReasonPhrase()); + } + } catch (Exception e) { + getLogger() + .log( + Level.WARNING, + "Exception intercepted while adding the response headers", + e); + response.getHttpCall().setStatusCode(Status.SERVER_ERROR_INTERNAL.getCode()); + response.getHttpCall().setReasonPhrase(Status.SERVER_ERROR_INTERNAL.getReasonPhrase()); + } + } + + /** + * Commits the changes to a handled uniform call back into the original HTTP call. The default + * implementation first invokes the "addResponseHeaders" then asks the "htppCall" to send the + * response back to the client. + * + * @param response The high-level response. + */ + public void commit(HttpResponse response) { + try { + if ((response.getRequest().getMethod() != null) + && response.getRequest().getMethod().equals(Method.HEAD)) { + addEntityHeaders(response); + response.setEntity(null); + } else if (Method.GET.equals(response.getRequest().getMethod()) + && Status.SUCCESS_OK.equals(response.getStatus()) + && (!response.isEntityAvailable())) { + addEntityHeaders(response); + getLogger() + .warning( + "A response with a 200 (Ok) status should have an entity. Make sure that resource \"" + + response.getRequest().getResourceRef() + + "\" returns one or sets the status to 204 (No content)."); + } else if (response.getStatus().equals(Status.SUCCESS_NO_CONTENT)) { + addEntityHeaders(response); + + if (response.isEntityAvailable()) { + getLogger() + .fine( + "Responses with a 204 (No content) status generally don't have an entity. Only adding entity headers for resource \"" + + response.getRequest().getResourceRef() + + "\"."); + response.setEntity(null); + } + } else if (response.getStatus().equals(Status.SUCCESS_RESET_CONTENT)) { + if (response.isEntityAvailable()) { + getLogger() + .warning( + "Responses with a 205 (Reset content) status can't have an entity. Ignoring the entity for resource \"" + + response.getRequest().getResourceRef() + + "\"."); + response.setEntity(null); + } + } else if (response.getStatus().equals(Status.REDIRECTION_NOT_MODIFIED)) { + if (response.getEntity() != null) { + HeaderUtils.addNotModifiedEntityHeaders( + response.getEntity(), response.getHttpCall().getResponseHeaders()); + response.setEntity(null); + } + } else if (response.getStatus().isInformational()) { + if (response.isEntityAvailable()) { + getLogger() + .warning( + "Responses with an informational (1xx) status can't have an entity. Ignoring the entity for resource \"" + + response.getRequest().getResourceRef() + + "\"."); + response.setEntity(null); + } + } else { + addEntityHeaders(response); + + if (!response.isEntityAvailable()) { + if ((response.getEntity() != null) && (response.getEntity().getSize() != 0)) { + getLogger() + .warning( + "A response with an unavailable and potentially non empty entity was returned. Ignoring the entity for resource \"" + + response.getRequest().getResourceRef() + + "\"."); + } + + response.setEntity(null); + } + } + + // Add the response headers + addResponseHeaders(response); + + // Send the response to the client + response.getHttpCall().sendResponse(response); + } catch (Exception exception) { + if (response.getHttpCall().isConnectionBroken(exception)) { + // output a single log line for this common case to avoid filling server logs + getLogger() + .log( + Level.INFO, + "The connection was broken. It was probably closed by the client. Reason: {0}", + exception.getMessage()); + } else { + getLogger() + .log( + Level.SEVERE, + "An exception occurred writing the response entity", + exception); + response.getHttpCall().setStatusCode(Status.SERVER_ERROR_INTERNAL.getCode()); + response.getHttpCall() + .setReasonPhrase("An exception occurred writing the response entity"); + response.setEntity(null); + + try { + response.getHttpCall().sendResponse(response); + } catch (IOException ioe) { + getLogger().log(Level.WARNING, "Unable to send error response", ioe); + } + } + } finally { + response.getHttpCall().complete(); + + if (response.getOnSent() != null) { + response.getOnSent().handle(response.getRequest(), response); + } + } + } + + /** + * Converts a low-level HTTP call into a high-level uniform request. + * + * @param httpCall The low-level HTTP call. + * @return A new high-level uniform request. + */ + public HttpRequest toRequest(ServerCall httpCall) { + HttpRequest result = new HttpRequest(getContext(), httpCall); + result.getAttributes().put(ATTRIBUTE_HEADERS, httpCall.getRequestHeaders()); + + if (httpCall.getVersion() != null) { + result.getAttributes().put(ATTRIBUTE_VERSION, httpCall.getVersion()); + } + + if (httpCall.isConfidential()) { + List clientCertificates = httpCall.getCertificates(); + + if (clientCertificates != null) { + result.getClientInfo().setCertificates(clientCertificates); + } + + String cipherSuite = httpCall.getCipherSuite(); + + if (cipherSuite != null) { + result.getClientInfo().setCipherSuite(cipherSuite); + } + + Integer keySize = httpCall.getSslKeySize(); + + if (keySize != null) { + result.getAttributes().put(ATTRIBUTE_HTTPS_KEY_SIZE, keySize); + } + + String sslSessionId = httpCall.getSslSessionId(); + + if (sslSessionId != null) { + result.getAttributes().put(ATTRIBUTE_HTTPS_SSL_SESSION_ID, sslSessionId); + } + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/ServerCall.java b/org.restlet/src/main/java/org/restlet/engine/adapter/ServerCall.java index 5a73ff07cc..c3b1ed114a 100644 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/ServerCall.java +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/ServerCall.java @@ -1,21 +1,35 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.adapter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PushbackInputStream; +import java.security.cert.Certificate; +import java.util.Base64; +import java.util.List; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Response; import org.restlet.Server; import org.restlet.data.Digest; import org.restlet.data.Header; import org.restlet.engine.connector.ConnectorHelper; -import org.restlet.engine.header.*; +import org.restlet.engine.header.ContentType; +import org.restlet.engine.header.DispositionReader; +import org.restlet.engine.header.EncodingReader; +import org.restlet.engine.header.HeaderConstants; +import org.restlet.engine.header.HeaderReader; +import org.restlet.engine.header.HeaderUtils; +import org.restlet.engine.header.LanguageReader; +import org.restlet.engine.header.RangeReader; import org.restlet.engine.io.IoUtils; import org.restlet.engine.util.StringUtils; import org.restlet.representation.EmptyRepresentation; @@ -23,16 +37,6 @@ import org.restlet.representation.Representation; import org.restlet.service.ConnectorService; -import javax.net.ssl.SSLPeerUnverifiedException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PushbackInputStream; -import java.security.cert.Certificate; -import java.util.Base64; -import java.util.List; -import java.util.logging.Level; - /** * Abstract HTTP server connector call. * @@ -40,142 +44,140 @@ */ public abstract class ServerCall extends Call { - /** Indicates if the "host" header was already parsed. */ - private volatile boolean hostParsed; - - /** - * Constructor. - * - * @param server The parent server connector. - */ - public ServerCall(Server server) { - this((server == null) ? null : server.getAddress(), (server == null) ? 0 : server.getPort()); - } - - /** - * Constructor. - * - * @param serverAddress The server IP address. - * @param serverPort The server port. - */ - public ServerCall(String serverAddress, int serverPort) { - setServerAddress(serverAddress); - setServerPort(serverPort); - this.hostParsed = false; - } - - /** - * Ask the connector to abort the related network connection, for example - * immediately closing the socket. - * - * @return True if the connection was aborted. - */ - public abstract boolean abort(); - - /** - * Complete the response - */ - public void complete() { - - } - - /** - * Flushes the buffers onto the network so that for example you can force - * headers to be written before the entity is becoming available. - * - * @throws IOException - */ - public void flushBuffers() throws IOException { - } - - /** - * Returns the chain of client SSL certificates, if available and accessible. - * - * @return The chain of client SSL certificates, if available and accessible. - */ - public List getCertificates() { - return null; - } - - /** - * Returns the SSL Cipher Suite, if available and accessible. - * - * @return The SSL Cipher Suite, if available and accessible. - */ - public String getCipherSuite() { - return null; - } - - /** - * Returns the content length of the request entity if know, - * {@link Representation#UNKNOWN_SIZE} otherwise. - * - * @return The request content length. - */ - protected long getContentLength() { - return HeaderUtils.getContentLength(getRequestHeaders()); - } - - /** - * Returns the host domain name. - * - * @return The host domain name. - */ - @Override - public String getHostDomain() { - if (!this.hostParsed) { - parseHost(); - } - return super.getHostDomain(); - } - - /** - * Returns the host port. - * - * @return The host port. - */ - @Override - public int getHostPort() { - if (!this.hostParsed) { - parseHost(); - } - return super.getHostPort(); - } - - /** - * Returns the request entity if available. - * - * @return The request entity if available. - */ - public Representation getRequestEntity() { - Representation result; - long contentLength = getContentLength(); - boolean chunkedEncoding = HeaderUtils.isChunkedEncoding(getRequestHeaders()); - // In some cases, there is an entity without a content-length header - boolean connectionClosed = HeaderUtils.isConnectionClose(getRequestHeaders()); - - // Create the representation - if (((contentLength != Representation.UNKNOWN_SIZE) && (contentLength != 0)) || chunkedEncoding - || connectionClosed) { - // Create the result representation - InputStream requestStream = getRequestEntityStream(contentLength); - - if (connectionClosed) { - // We need to detect if there is really an entity or not as only - // the end of connection can let us know at this point - PushbackInputStream pbi = new PushbackInputStream(requestStream); - - try { - int next = pbi.read(); - - if (next != -1) { - pbi.unread(next); - requestStream = pbi; - } else { - requestStream = null; - } - } catch (IOException e) { - getLogger().fine("Unable to read request entity"); + /** Indicates if the "host" header was already parsed. */ + private volatile boolean hostParsed; + + /** + * Constructor. + * + * @param server The parent server connector. + */ + protected ServerCall(Server server) { + this( + (server == null) ? null : server.getAddress(), + (server == null) ? 0 : server.getPort()); + } + + /** + * Constructor. + * + * @param serverAddress The server IP address. + * @param serverPort The server port. + */ + protected ServerCall(String serverAddress, int serverPort) { + setServerAddress(serverAddress); + setServerPort(serverPort); + this.hostParsed = false; + } + + /** + * Ask the connector to abort the related network connection, for example immediately closing + * the socket. + * + * @return True if the connection was aborted. + */ + public abstract boolean abort(); + + /** Complete the response */ + public void complete() {} + + /** + * Flushes the buffers onto the network so that for example you can force headers to be written + * before the entity is becoming available. + * + * @throws IOException + */ + public void flushBuffers() throws IOException {} + + /** + * Returns the chain of client SSL certificates, if available and accessible. + * + * @return The chain of client SSL certificates, if available and accessible. + */ + public List getCertificates() { + return null; + } + + /** + * Returns the SSL Cipher Suite, if available and accessible. + * + * @return The SSL Cipher Suite, if available and accessible. + */ + public String getCipherSuite() { + return null; + } + + /** + * Returns the content length of the request entity if know, {@link Representation#UNKNOWN_SIZE} + * otherwise. + * + * @return The request content length. + */ + protected long getContentLength() { + return HeaderUtils.getContentLength(getRequestHeaders()); + } + + /** + * Returns the host domain name. + * + * @return The host domain name. + */ + @Override + public String getHostDomain() { + if (!this.hostParsed) { + parseHost(); + } + return super.getHostDomain(); + } + + /** + * Returns the host port. + * + * @return The host port. + */ + @Override + public int getHostPort() { + if (!this.hostParsed) { + parseHost(); + } + return super.getHostPort(); + } + + /** + * Returns the request entity if available. + * + * @return The request entity if available. + */ + public Representation getRequestEntity() { + Representation result; + long contentLength = getContentLength(); + boolean chunkedEncoding = HeaderUtils.isChunkedEncoding(getRequestHeaders()); + // In some cases, there is an entity without a content-length header + boolean connectionClosed = HeaderUtils.isConnectionClose(getRequestHeaders()); + + // Create the representation + if (((contentLength != Representation.UNKNOWN_SIZE) && (contentLength != 0)) + || chunkedEncoding + || connectionClosed) { + // Create the result representation + InputStream requestStream = getRequestEntityStream(contentLength); + + if (connectionClosed) { + // We need to detect if there is really an entity or not as only + // the end of the connection can let us know at this point + PushbackInputStream pbi = new PushbackInputStream(requestStream); + + try { + int next = pbi.read(); + + if (next != -1) { + pbi.unread(next); + requestStream = pbi; + } else { + requestStream = null; + } + } catch (IOException e) { + getLogger().fine("Unable to read request entity"); try { pbi.close(); @@ -183,332 +185,342 @@ public Representation getRequestEntity() { getLogger().fine("Unable to close request entity"); } } - } - - if (requestStream != null) { - result = new InputRepresentation(requestStream, null, contentLength); - } else { - result = new EmptyRepresentation(); - } - - result.setSize(contentLength); - } else { - result = new EmptyRepresentation(); - } - - // Extract some interesting header values - for (Header header : getRequestHeaders()) { - if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_ENCODING)) { - new EncodingReader(header.getValue()).addValues(result.getEncodings()); - } else if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_LANGUAGE)) { - new LanguageReader(header.getValue()).addValues(result.getLanguages()); - } else if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_TYPE)) { - ContentType contentType = new ContentType(header.getValue()); - result.setMediaType(contentType.getMediaType()); - result.setCharacterSet(contentType.getCharacterSet()); - } else if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_RANGE)) { - RangeReader.update(header.getValue(), result); - } else if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_MD5)) { - result.setDigest(new Digest(Digest.ALGORITHM_MD5, Base64.getDecoder().decode(header.getValue()))); - } else if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_DISPOSITION)) { - try { - result.setDisposition(new DispositionReader(header.getValue()).readValue()); - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.WARNING, - "Error during Content-Disposition header parsing. Header: " + header.getValue(), ioe); - } - } - } - - return result; - } - - /** - * Returns the request entity stream if it exists. - * - * @param size The expected entity size or -1 if unknown. - * @return The request entity stream if it exists. - */ - public abstract InputStream getRequestEntityStream(long size); - - /** - * Returns the request head stream if it exists. - * - * @return The request head stream if it exists. - */ - public abstract InputStream getRequestHeadStream(); - - /** - * Returns the response entity stream if it exists. - * - * @return The response entity stream if it exists. - */ - public abstract OutputStream getResponseEntityStream(); - - /** - * Returns the SSL key size, if available and accessible. - * - * @return The SSL key size, if available and accessible. - */ - public Integer getSslKeySize() { - return null; - } - - /** - * Returns the SSL session ID, in hexadecimal encoding, if available and - * accessible. - * - * @return The SSL session ID, in hexadecimal encoding, if available and - * accessible. - */ - public String getSslSessionId() { - byte[] byteArray = getSslSessionIdBytes(); - - if (byteArray != null) { - return IoUtils.toHexString(byteArray); - } else { - return null; - } - } - - /** - * Returns the SSL session ID, as a byte array, if available and accessible in - * that format (to be used by getSslSessionId). - * - * @return The SSL session ID, as a byte array, if available and accessible in - * that format. - */ - protected byte[] getSslSessionIdBytes() { - return null; - } - - @Override - protected boolean isClientKeepAlive() { - return !HeaderUtils.isConnectionClose(getRequestHeaders()); - } - - @Override - protected boolean isServerKeepAlive() { - return true; - } - - /** - * Parses the "host" header to set the server host and port properties. - */ - private void parseHost() { - String host = getRequestHeaders().getFirstValue(HeaderConstants.HEADER_HOST, true); - - if (host != null) { - // Take care of IPV6 addresses - int colonIndex = host.indexOf(':', host.indexOf(']')); - - if (colonIndex != -1) { - super.setHostDomain(host.substring(0, colonIndex)); - super.setHostPort(Integer.parseInt(host.substring(colonIndex + 1))); - } else { - super.setHostDomain(host); - super.setHostPort(getProtocol().getDefaultPort()); - } - } else { - getLogger().info("Couldn't find the mandatory \"Host\" HTTP header."); - } - - this.hostParsed = true; - } - - /** - * Reads the HTTP request head (request line and headers). - * - * @throws IOException - */ - protected void readRequestHead(InputStream headStream) throws IOException { - StringBuilder sb = new StringBuilder(); - - // Parse the request method - int next = headStream.read(); - while ((next != -1) && !HeaderUtils.isSpace(next)) { - sb.append((char) next); - next = headStream.read(); - } - - if (next == -1) { - throw new IOException("Unable to parse the request method. End of stream reached too early."); - } - - setMethod(sb.toString()); - sb.delete(0, sb.length()); - - // Parse the request URI - next = headStream.read(); - while ((next != -1) && !HeaderUtils.isSpace(next)) { - sb.append((char) next); - next = headStream.read(); - } - - if (next == -1) { - throw new IOException("Unable to parse the request URI. End of stream reached too early."); - } - setRequestUri(sb.toString()); - sb.delete(0, sb.length()); - - // Parse the HTTP version - next = headStream.read(); - while ((next != -1) && !HeaderUtils.isCarriageReturn(next)) { - sb.append((char) next); - next = headStream.read(); - } - - if (next == -1) { - throw new IOException("Unable to parse the HTTP version. End of stream reached too early."); - } - next = headStream.read(); - - if (HeaderUtils.isLineFeed(next)) { - setVersion(sb.toString()); - sb.delete(0, sb.length()); - - // Parse the headers - Header header = HeaderReader.readHeader(headStream, sb); - - while (header != null) { - getRequestHeaders().add(header); - header = HeaderReader.readHeader(headStream, sb); - } - } else { - throw new IOException( - "Unable to parse the HTTP version. The carriage return must be followed by a line feed."); - } - } - - /** - * Sends the response back to the client. Commits the status, headers and - * optional entity and send them over the network. The default implementation - * only writes the response entity on the response stream or channel. Subclasses - * will probably also copy the response headers and status. - * - * @param response The high-level response. - * @throws IOException if the Response could not be written to the network. - */ - public void sendResponse(Response response) throws IOException { - if (response != null) { - // Get the connector service to callback - Representation responseEntity = response.getEntity(); - ConnectorService connectorService = ConnectorHelper.getConnectorService(); - - if (connectorService != null) { - connectorService.beforeSend(responseEntity); - } - - OutputStream responseEntityStream = null; - try { - writeResponseHead(response); - - if (responseEntity != null) { - - responseEntityStream = getResponseEntityStream(); - writeResponseBody(responseEntity, responseEntityStream); - } - } finally { - if (responseEntityStream != null) { - try { - responseEntityStream.flush(); - responseEntityStream.close(); - } catch (IOException ioe) { - // The stream was probably already closed by the - // connector. Probably OK, low message priority. - getLogger().log(Level.FINE, "Exception while flushing and closing the entity stream.", ioe); - } - } - if (responseEntity != null) { - responseEntity.release(); - } - - if (connectorService != null) { - connectorService.afterSend(responseEntity); - } - } - } - } - - /** - * Indicates if the response should be chunked because its length is unknown. - * - * @param response The response to analyze. - * @return True if the response should be chunked. - */ - public boolean shouldResponseBeChunked(Response response) { - return (response.getEntity() != null) && !response.getEntity().hasKnownSize(); - } - - /** - * Effectively writes the response body. The entity to write is guaranteed to be - * non null. Attempts to write the entity on the response channel or response - * stream by default. - * - * @param entity The representation to write as entity of the - * body. - * @param responseEntityStream The response entity stream or null if a channel - * is used. - * @throws IOException - */ - protected void writeResponseBody(Representation entity, OutputStream responseEntityStream) throws IOException { - // Send the entity to the client - if (responseEntityStream != null) { - entity.write(responseEntityStream); - responseEntityStream.flush(); - } - } - - /** - * Writes the response status line and headers. Does nothing by default. - * - * @param response The response. - * @throws IOException - */ - protected void writeResponseHead(Response response) throws IOException { - // Do nothing by default - } - - /** - * Writes the response head to the given output stream. - * - * @param response The response. - * @param headStream The output stream to write to. - * @throws IOException - */ - protected void writeResponseHead(Response response, OutputStream headStream) throws IOException { - // Write the status line - String version = (getVersion() == null) ? "1.1" : getVersion(); - headStream.write(StringUtils.getAsciiBytes(version)); - headStream.write(' '); - headStream.write(StringUtils.getAsciiBytes(Integer.toString(getStatusCode()))); - headStream.write(' '); - - if (getReasonPhrase() != null) { - headStream.write(StringUtils.getLatin1Bytes(getReasonPhrase())); - } else { - headStream.write(StringUtils.getAsciiBytes(("Status " + getStatusCode()))); - } - - headStream.write(13); // CR - headStream.write(10); // LF - - // We don't support persistent connections yet - getResponseHeaders().set(HeaderConstants.HEADER_CONNECTION, "close", true); - - // Check if 'Transfer-Encoding' header should be set - if (shouldResponseBeChunked(response)) { - getResponseHeaders().add(HeaderConstants.HEADER_TRANSFER_ENCODING, "chunked"); - } - - // Write the response headers - for (Header header : getResponseHeaders()) { - HeaderUtils.writeHeaderLine(header, headStream); - } - - // Write the end of the headers section - headStream.write(13); // CR - headStream.write(10); // LF - headStream.flush(); - } + } + + if (requestStream != null) { + result = new InputRepresentation(requestStream, null, contentLength); + } else { + result = new EmptyRepresentation(); + } + + result.setSize(contentLength); + } else { + result = new EmptyRepresentation(); + } + + // Extract some interesting header values + for (Header header : getRequestHeaders()) { + if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_ENCODING)) { + new EncodingReader(header.getValue()).addValues(result.getEncodings()); + } else if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_LANGUAGE)) { + new LanguageReader(header.getValue()).addValues(result.getLanguages()); + } else if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_TYPE)) { + ContentType contentType = new ContentType(header.getValue()); + result.setMediaType(contentType.getMediaType()); + result.setCharacterSet(contentType.getCharacterSet()); + } else if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_RANGE)) { + RangeReader.update(header.getValue(), result); + } else if (header.getName().equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_MD5)) { + result.setDigest( + new Digest( + Digest.ALGORITHM_MD5, + Base64.getDecoder().decode(header.getValue()))); + } else if (header.getName() + .equalsIgnoreCase(HeaderConstants.HEADER_CONTENT_DISPOSITION)) { + try { + result.setDisposition(new DispositionReader(header.getValue()).readValue()); + } catch (IOException ioe) { + Context.getCurrentLogger() + .log( + Level.WARNING, + ioe, + () -> + "Error during Content-Disposition header parsing. Header: " + + header.getValue()); + } + } + } + + return result; + } + + /** + * Returns the request entity stream if it exists. + * + * @param size The expected entity size or -1 if unknown. + * @return The request entity stream if it exists. + */ + public abstract InputStream getRequestEntityStream(long size); + + /** + * Returns the request head stream if it exists. + * + * @return The request head stream if it exists. + */ + public abstract InputStream getRequestHeadStream(); + + /** + * Returns the response entity stream if it exists. + * + * @return The response entity stream if it exists. + */ + public abstract OutputStream getResponseEntityStream(); + + /** + * Returns the SSL key size, if available and accessible. + * + * @return The SSL key size, if available and accessible. + */ + public Integer getSslKeySize() { + return null; + } + + /** + * Returns the SSL session ID, in hexadecimal encoding, if available and accessible. + * + * @return The SSL session ID, in hexadecimal encoding, if available and accessible. + */ + public String getSslSessionId() { + byte[] byteArray = getSslSessionIdBytes(); + + if (byteArray != null) { + return IoUtils.toHexString(byteArray); + } else { + return null; + } + } + + /** + * Returns the SSL session ID, as a byte array, if available and accessible in that format (to + * be used by getSslSessionId). + * + * @return The SSL session ID, as a byte array, if available and accessible in that format. + */ + protected byte[] getSslSessionIdBytes() { + return null; + } + + @Override + protected boolean isClientKeepAlive() { + return !HeaderUtils.isConnectionClose(getRequestHeaders()); + } + + @Override + protected boolean isServerKeepAlive() { + return true; + } + + /** Parses the "host" header to set the server host and port properties. */ + private void parseHost() { + String host = getRequestHeaders().getFirstValue(HeaderConstants.HEADER_HOST, true); + + if (host != null) { + // Take care of IPV6 addresses + int colonIndex = host.indexOf(':', host.indexOf(']')); + + if (colonIndex != -1) { + super.setHostDomain(host.substring(0, colonIndex)); + super.setHostPort(Integer.parseInt(host.substring(colonIndex + 1))); + } else { + super.setHostDomain(host); + super.setHostPort(getProtocol().getDefaultPort()); + } + } else { + getLogger().info("Couldn't find the mandatory \"Host\" HTTP header."); + } + + this.hostParsed = true; + } + + /** + * Reads the HTTP request head (request line and headers). + * + * @throws IOException + */ + protected void readRequestHead(InputStream headStream) throws IOException { + StringBuilder sb = new StringBuilder(); + + // Parse the request method + int next = headStream.read(); + while ((next != -1) && !HeaderUtils.isSpace(next)) { + sb.append((char) next); + next = headStream.read(); + } + + if (next == -1) { + throw new IOException( + "Unable to parse the request method. End of stream reached too early."); + } + + setMethod(sb.toString()); + sb.delete(0, sb.length()); + + // Parse the request URI + next = headStream.read(); + while ((next != -1) && !HeaderUtils.isSpace(next)) { + sb.append((char) next); + next = headStream.read(); + } + + if (next == -1) { + throw new IOException( + "Unable to parse the request URI. End of stream reached too early."); + } + setRequestUri(sb.toString()); + sb.delete(0, sb.length()); + + // Parse the HTTP version + next = headStream.read(); + while ((next != -1) && !HeaderUtils.isCarriageReturn(next)) { + sb.append((char) next); + next = headStream.read(); + } + + if (next == -1) { + throw new IOException( + "Unable to parse the HTTP version. End of stream reached too early."); + } + next = headStream.read(); + + if (HeaderUtils.isLineFeed(next)) { + setVersion(sb.toString()); + sb.delete(0, sb.length()); + + // Parse the headers + Header header = HeaderReader.readHeader(headStream, sb); + + while (header != null) { + getRequestHeaders().add(header); + header = HeaderReader.readHeader(headStream, sb); + } + } else { + throw new IOException( + "Unable to parse the HTTP version. The carriage return must be followed by a line feed."); + } + } + + /** + * Sends the response back to the client. Commits the status, headers, and optional entity and + * send them over the network. The default implementation only writes the response entity on the + * response stream or channel. Subclasses will probably also copy the response headers and + * status. + * + * @param response The high-level response. + * @throws IOException if the Response could not be written to the network. + */ + public void sendResponse(Response response) throws IOException { + if (response != null) { + // Get the connector service to callback + Representation responseEntity = response.getEntity(); + ConnectorService connectorService = ConnectorHelper.getConnectorService(); + + if (connectorService != null) { + connectorService.beforeSend(responseEntity); + } + + OutputStream responseEntityStream = null; + try { + writeResponseHead(response); + + if (responseEntity != null) { + + responseEntityStream = getResponseEntityStream(); + writeResponseBody(responseEntity, responseEntityStream); + } + } finally { + if (responseEntityStream != null) { + try { + responseEntityStream.flush(); + responseEntityStream.close(); + } catch (IOException ioe) { + // The stream was probably already closed by the + // connector. Probably OK, low message priority. + getLogger() + .log( + Level.FINE, + "Exception while flushing and closing the entity stream.", + ioe); + } + } + if (responseEntity != null) { + responseEntity.release(); + } + + if (connectorService != null) { + connectorService.afterSend(responseEntity); + } + } + } + } + + /** + * Indicates if the response should be chunked because its length is unknown. + * + * @param response The response to analyze. + * @return True if the response should be chunked. + */ + public boolean shouldResponseBeChunked(Response response) { + return (response.getEntity() != null) && !response.getEntity().hasKnownSize(); + } + + /** + * Effectively writes the response body. The entity to write is guaranteed to be non-null. + * Attempts to write the entity on the response channel or response stream by default. + * + * @param entity The representation to write as entity of the body. + * @param responseEntityStream The response entity stream or null if a channel is used. + * @throws IOException + */ + protected void writeResponseBody(Representation entity, OutputStream responseEntityStream) + throws IOException { + // Send the entity to the client + if (responseEntityStream != null) { + entity.write(responseEntityStream); + responseEntityStream.flush(); + } + } + + /** + * Writes the response status line and headers. Does nothing by default. + * + * @param response The response. + * @throws IOException + */ + protected void writeResponseHead(Response response) throws IOException { + // Do nothing by default + } + + /** + * Writes the response head to the given output stream. + * + * @param response The response. + * @param headStream The output stream to write to. + * @throws IOException + */ + protected void writeResponseHead(Response response, OutputStream headStream) + throws IOException { + // Write the status line + String version = (getVersion() == null) ? "1.1" : getVersion(); + headStream.write(StringUtils.getAsciiBytes(version)); + headStream.write(' '); + headStream.write(StringUtils.getAsciiBytes(Integer.toString(getStatusCode()))); + headStream.write(' '); + + if (getReasonPhrase() != null) { + headStream.write(StringUtils.getLatin1Bytes(getReasonPhrase())); + } else { + headStream.write(StringUtils.getAsciiBytes(("Status " + getStatusCode()))); + } + + headStream.write(13); // CR + headStream.write(10); // LF + + // We don't support persistent connections yet + getResponseHeaders().set(HeaderConstants.HEADER_CONNECTION, "close", true); + + // Check if 'Transfer-Encoding' header should be set + if (shouldResponseBeChunked(response)) { + getResponseHeaders().add(HeaderConstants.HEADER_TRANSFER_ENCODING, "chunked"); + } + + // Write the response headers + for (Header header : getResponseHeaders()) { + HeaderUtils.writeHeaderLine(header, headStream); + } + + // Write the end of the headers section + headStream.write(13); // CR + headStream.write(10); // LF + headStream.flush(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/package-info.java b/org.restlet/src/main/java/org/restlet/engine/adapter/package-info.java new file mode 100644 index 0000000000..58e1e7c2c1 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/adapter/package-info.java @@ -0,0 +1,6 @@ +/** + * Adapters between low-level HTTP calls and high-level Restlet Request and Response objects. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.adapter; diff --git a/org.restlet/src/main/java/org/restlet/engine/adapter/package.html b/org.restlet/src/main/java/org/restlet/engine/adapter/package.html deleted file mode 100644 index 60d5c6dfeb..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/adapter/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Adapters between low-level HTTP calls and high-level Restlet Request and -Response objects. -

@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/application/ApplicationHelper.java b/org.restlet/src/main/java/org/restlet/engine/application/ApplicationHelper.java index f204bab732..2aa0e774b8 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/ApplicationHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/ApplicationHelper.java @@ -1,15 +1,21 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; -import org.restlet.*; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.data.Protocol; import org.restlet.data.Reference; import org.restlet.data.Status; @@ -17,136 +23,146 @@ import org.restlet.routing.Filter; import org.restlet.service.Service; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - /** * Application implementation. - * + * * @author Jerome Louvel */ public class ApplicationHelper extends CompositeHelper { - /** - * Constructor. - * - * @param application The application to help. - */ - public ApplicationHelper(Application application) { - super(application); - } - - /** - * In addition to the default behavior, it saves the current application - * instance into the current thread. - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - public void handle(Request request, Response response) { - // Save the current application - // Plan to move current application as attribute of the Response in incoming 2.5 - // release - final Application currentApplication = getHelped() != null ? getHelped() : Application.getCurrent(); - Application.setCurrent(currentApplication); - - // Actually handle call - try { - super.handle(request, response); - } finally { - // restore the current application - Application.setCurrent(currentApplication); - } - } - - /** - * Sets the context. - * - * @param context The context. - */ - public void setContext(Context context) { - if (context != null) { - setOutboundNext(context.getClientDispatcher()); - } - } - - /** Start hook. */ - @Override - public synchronized void start() throws Exception { - Filter filter = null; - - for (Service service : getHelped().getServices()) { - if (service.isEnabled()) { - // Attach the service inbound filters - filter = service.createInboundFilter((getContext() == null) ? null : getContext().createChildContext()); - - if (filter != null) { - addInboundFilter(filter); - } - - // Attach the service outbound filters - filter = service - .createOutboundFilter((getContext() == null) ? null : getContext().createChildContext()); - - if (filter != null) { - addOutboundFilter(filter); - } - } - } - - // Attach the Application's server root Restlet - setInboundNext(getHelped().getInboundRoot()); - - if (getOutboundNext() == null) { - // Warn about chaining problem - getLogger().fine( - "By default, an application should be attached to a parent component in order to let application's outbound root handle calls properly."); - setOutboundNext(new Restlet() { - final Map clients = new ConcurrentHashMap(); - - @Override - public void handle(Request request, Response response) { - Protocol rProtocol = request.getProtocol(); - Reference rReference = request.getResourceRef(); - Protocol protocol = (rProtocol != null) ? rProtocol - : (rReference != null) ? rReference.getSchemeProtocol() : null; - - if (protocol != null) { - Client c = clients.get(protocol); - - if (c == null) { - c = new Client(protocol); - clients.put(protocol, c); - getLogger().fine("Added runtime client for protocol: " + protocol.getName()); - } - - c.handle(request, response); - } else { - response.setStatus(Status.SERVER_ERROR_INTERNAL, - "The server isn't properly configured to handle client calls."); - getLogger() - .warning("There is no protocol detected for this request: " + request.getResourceRef()); - } - } - - @Override - public synchronized void stop() throws Exception { - super.stop(); - for (Client client : clients.values()) { - client.stop(); - } - } - }); - } - } - - @Override - public synchronized void stop() throws Exception { - clear(); - } - - @Override - public void update() throws Exception { - } - + /** + * Constructor. + * + * @param application The application to help. + */ + public ApplicationHelper(Application application) { + super(application); + } + + /** + * In addition to the default behavior, it saves the current application instance into the + * current thread. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public void handle(Request request, Response response) { + // Save the current application + // Plan to move the current application as an attribute of the Response in the incoming 2.5 + // release + final Application currentApplication = + getHelped() != null ? getHelped() : Application.getCurrent(); + Application.setCurrent(currentApplication); + + // Actually handle call + try { + super.handle(request, response); + } finally { + // restore the current application + Application.setCurrent(currentApplication); + } + } + + /** + * Sets the context. + * + * @param context The context. + */ + public void setContext(Context context) { + if (context != null) { + setOutboundNext(context.getClientDispatcher()); + } + } + + /** Start hook. */ + @Override + public synchronized void start() throws Exception { + Filter filter; + + for (Service service : getHelped().getServices()) { + if (service.isEnabled()) { + // Attach the service inbound filters + Context context = (getContext() == null) ? null : getContext().createChildContext(); + filter = service.createInboundFilter(context); + + if (filter != null) { + addInboundFilter(filter); + } + + // Attach the service outbound filters + context = (getContext() == null) ? null : getContext().createChildContext(); + filter = service.createOutboundFilter(context); + + if (filter != null) { + addOutboundFilter(filter); + } + } + } + + // Attach the Application's server root Restlet + setInboundNext(getHelped().getInboundRoot()); + + if (getOutboundNext() == null) { + // Warn about a chaining problem + getLogger() + .fine( + "By default, an application should be attached to a parent component to let application's outbound root handle calls properly."); + setOutboundNext( + new Restlet() { + final Map clients = new ConcurrentHashMap<>(); + + @Override + public void handle(Request request, Response response) { + Protocol rProtocol = request.getProtocol(); + Reference rReference = request.getResourceRef(); + Protocol protocol = + (rProtocol != null) + ? rProtocol + : (rReference != null) + ? rReference.getSchemeProtocol() + : null; + + if (protocol != null) { + Client c = + clients.computeIfAbsent( + protocol, + p -> { + getLogger() + .fine( + "Added runtime client for protocol: " + + p.getName()); + return new Client(p); + }); + c.handle(request, response); + } else { + response.setStatus( + Status.SERVER_ERROR_INTERNAL, + "The server isn't properly configured to handle client calls."); + getLogger() + .warning( + "There is no protocol detected for this request: " + + request.getResourceRef()); + } + } + + @Override + public synchronized void stop() throws Exception { + super.stop(); + for (Client client : clients.values()) { + client.stop(); + } + } + }); + } + } + + @Override + public synchronized void stop() throws Exception { + clear(); + } + + @Override + public void update() throws Exception { + // Nothing to do here + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/Conneg.java b/org.restlet/src/main/java/org/restlet/engine/application/Conneg.java index 4992b4da5b..c1c69dc787 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/Conneg.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/Conneg.java @@ -1,89 +1,83 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import java.util.List; import org.restlet.Request; import org.restlet.representation.Variant; import org.restlet.service.MetadataService; -import java.util.List; - /** * Content negotiation algorithm. - * + * * @author Jerome Louvel */ public abstract class Conneg { - /** The request including client preferences. */ - private final Request request; - - /** - * Constructor. - * - * @param request The request including client preferences. - * @param metadataService The metadata service used to get default metadata - * values. - */ - public Conneg(Request request, MetadataService metadataService) { - this.request = request; - } + /** The request including client preferences. */ + private final Request request; - /** - * Returns the request including client preferences. - * - * @return The request including client preferences. - */ - public Request getRequest() { - return request; - } + /** + * Constructor. + * + * @param request The request including client preferences. + * @param metadataService The metadata service used to get default metadata values. + */ + protected Conneg(Request request, MetadataService metadataService) { + this.request = request; + } - /** - * Returns the best variant representation for a given resource according the - * the client preferences.
- * A default language is provided in case the variants don't match the client - * preferences. - * - * @param variants The list of variants to compare. - * @return The preferred variant. - * @see Apache - * content negotiation algorithm - */ - public Variant getPreferredVariant(List variants) { - Variant result = null; + /** + * Returns the request including client preferences. + * + * @return The request including client preferences. + */ + public Request getRequest() { + return request; + } - if ((variants != null) && !variants.isEmpty()) { - float bestScore = -1.0F; - float current; + /** + * Returns the best variant representation for a given resource according the client + * preferences.
+ * A default language is provided in case the variants don't match the client preferences. + * + * @param variants The list of variants to compare. + * @return The preferred variant. + * @see Apache + * content negotiation algorithm + */ + public Variant getPreferredVariant(List variants) { + Variant result = null; - // Compute the score of each variant - for (Variant variant : variants) { - current = scoreVariant(variant); + if ((variants != null) && !variants.isEmpty()) { + float bestScore = -1.0F; + float current; - if (current > bestScore) { - bestScore = current; - result = variant; - } - } - } + // Compute the score of each variant + for (Variant variant : variants) { + current = scoreVariant(variant); - return result; - } + if (current > bestScore) { + bestScore = current; + result = variant; + } + } + } - /** - * Scores a variant relatively to enriched client preferences. - * - * @param variant The variant to score. - * @return The enriched client preferences. - */ - public abstract float scoreVariant(Variant variant); + return result; + } + /** + * Scores a variant relatively to enriched client preferences. + * + * @param variant The variant to score. + * @return The enriched client preferences. + */ + public abstract float scoreVariant(Variant variant); } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/CorsFilter.java b/org.restlet/src/main/java/org/restlet/engine/application/CorsFilter.java index 62ea3270a4..2b20acd9b4 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/CorsFilter.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/CorsFilter.java @@ -1,14 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -17,346 +19,325 @@ import org.restlet.engine.util.SetUtils; import org.restlet.routing.Filter; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - /** - * Filter that helps support CORS requests. This filter lets the target - * resources specify the allowed methods. - * - * Example: - * + * Filter that helps support CORS requests. This filter lets the target resources specify the + * allowed methods. + * + *

Example: + * *

  * Router router = new Router(getContext());
- * 
+ *
  * CorsFilter corsFilter = new CorsFilter(getContext(), router);
  * corsFilter.setAllowedOrigins(new HashSet(Arrays.asList("http://server.com")));
  * corsFilter.setAllowedCredentials(true);
  * 
- * + * * @author Manuel Boillod */ public class CorsFilter extends Filter { - /** - * If true, copies the value of 'Access-Control-Request-Headers' request header - * into the 'Access-Control-Allow-Headers' response header. If false, use - * {@link #allowedHeaders}. Default is true. - */ - public boolean allowAllRequestedHeaders = true; - - /** - * If true, add 'Access-Control-Allow-Credentials' header. Default is false. - */ - private boolean allowedCredentials = false; - - /** - * The value of 'Access-Control-Allow-Headers' response header. Used only if - * {@link #allowAllRequestedHeaders} is false. - */ - private Set allowedHeaders = null; - - /** The value of 'Access-Control-Allow-Origin' header. Default is '*'. */ - private Set allowedOrigins = SetUtils.newHashSet("*"); - - /** Helper for generating CORS response. */ - private CorsResponseHelper corsResponseHelper; - - /** - * The set of methods allowed by default, used when - * {@link #skippingResourceForCorsOptions} is turned on. By default: GET, PUT, - * POST, DELETE, PATCH. - */ - private Set defaultAllowedMethods = new HashSet<>( - Arrays.asList(Method.GET, Method.POST, Method.PUT, Method.DELETE, Method.PATCH)); - - /** The value of 'Access-Control-Expose-Headers' response header. */ - private Set exposedHeaders = null; - - /** - * The value of 'Access-Control-Max-Age' response header. Default is that the - * header is not set. - */ - private int maxAge = -1; - - /** - * If true, the filter does not call the server resource for OPTIONS method of - * CORS request and set Access-Control-Allow-Methods header with - * {@link #defaultAllowedMethods}. Default is false. - */ - private boolean skippingResourceForCorsOptions = false; - - /** - * Constructor. - */ - public CorsFilter() { - this(null); - } - - /** - * Constructor. - * - * @param context The context. - */ - public CorsFilter(Context context) { - super(context, null); - } - - /** - * Constructor. - * - * @param context The context. - * @param next The next Restlet. - */ - public CorsFilter(Context context, Restlet next) { - super(context, next); - } - - /** - * Add CORS headers to response - * - * @param request The request to handle. - * @param response The response - */ - @Override - protected void afterHandle(Request request, Response response) { - getCorsResponseHelper().addCorsResponseHeaders(request, response); - } - - /** - * Skip the call to the server resource if the - * {@link #skippingResourceForCorsOptions} is true and if the current request - * use the OPTIONS method and is a CORS request. - * - * @param request The request to handle. - * @param response The response to update. - * @return The continuation status. Either {@link #CONTINUE} or {@link #SKIP} or - * {@link #STOP}. - */ - @Override - protected int beforeHandle(Request request, Response response) { - if (skippingResourceForCorsOptions && Method.OPTIONS.equals(request.getMethod()) - && getCorsResponseHelper().isCorsRequest(request)) { - response.setAllowedMethods(getDefaultAllowedMethods()); - return Filter.SKIP; - } else { - return Filter.CONTINUE; - } - } - - /** - * Returns the modifiable set of headers allowed by the actual request on the - * current resource.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Headers" header. - * - * @return The set of headers allowed by the actual request on the current - * resource. - */ - public Set getAllowedHeaders() { - return allowedHeaders; - } - - /** - * Returns the URI an origin server allows for the requested resource. Use "*" - * as a wildcard character.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Origin" header. - * - * @return The origin allowed by the requested resource. - */ - public Set getAllowedOrigins() { - return allowedOrigins; - } - - /** - * Returns a lazy-initialized instance of - * {@link org.restlet.engine.application.CorsResponseHelper}. - */ - protected CorsResponseHelper getCorsResponseHelper() { - if (corsResponseHelper == null) { - corsResponseHelper = new CorsResponseHelper(); - corsResponseHelper.setAllowedCredentials(allowedCredentials); - corsResponseHelper.setAllowedOrigins(allowedOrigins); - corsResponseHelper.setAllowAllRequestedHeaders(allowAllRequestedHeaders); - corsResponseHelper.setAllowedHeaders(allowedHeaders); - corsResponseHelper.setExposedHeaders(exposedHeaders); - corsResponseHelper.setMaxAge(maxAge); - } - return corsResponseHelper; - } - - /** - * Returns the list of methods allowed by default, used when - * {@link #skippingResourceForCorsOptions} is turned on. - * - * @return The list of methods allowed by default, used when - * {@link #skippingResourceForCorsOptions} is turned on. - */ - public Set getDefaultAllowedMethods() { - return defaultAllowedMethods; - } - - /** - * Returns a modifiable whitelist of headers an origin server allows for the - * requested resource.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Expose-Headers" header. - * - * @return The set of headers an origin server allows for the requested - * resource. - */ - public Set getExposedHeaders() { - return exposedHeaders; - } - - /** - * Indicates how long (in seconds) the results of a preflight request can be - * cached in a preflight result cache.
- * In case of a negative value, the results of a preflight request is not meant - * to be cached.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Max-Age" header. - * - * @return Indicates how long the results of a preflight request can be cached - * in a preflight result cache. - */ - public int getMaxAge() { - return maxAge; - } - - /** - * If true, indicates that the value of 'Access-Control-Request-Headers' request - * header will be copied into the 'Access-Control-Allow-Headers' response - * header. If false, use {@link #allowedHeaders}. - */ - public boolean isAllowAllRequestedHeaders() { - return allowAllRequestedHeaders; - } - - /** - * If true, adds 'Access-Control-Allow-Credentials' header. - * - * @return True, if the 'Access-Control-Allow-Credentials' header will be added. - */ - public boolean isAllowedCredentials() { - return allowedCredentials; - } - - /** - * If true, the filter does not call the server resource for OPTIONS method of - * CORS request and set Access-Control-Allow-Methods header with - * {@link #defaultAllowedMethods}. Default is false. - * - * @return True if the filter does not call the server resource for OPTIONS - * method of CORS request. - */ - public boolean isSkippingResourceForCorsOptions() { - return skippingResourceForCorsOptions; - } - - /** - * If true, adds 'Access-Control-Allow-Credentials' header. - * - * @param allowedCredentials True to add the 'Access-Control-Allow-Credentials' - * header. - * @return Itself for chaining methods calls. - */ - public CorsFilter setAllowedCredentials(boolean allowedCredentials) { - this.allowedCredentials = allowedCredentials; - return this; - } - - /** - * Sets the value of the 'Access-Control-Allow-Headers' response header. Used - * only if {@link #allowAllRequestedHeaders} is false. - * - * @param allowedHeaders The value of 'Access-Control-Allow-Headers' response - * header. - * @return Itself for chaining methods calls. - */ - public CorsFilter setAllowedHeaders(Set allowedHeaders) { - this.allowedHeaders = allowedHeaders; - return this; - } - - /** - * Sets the value of 'Access-Control-Allow-Origin' header. - * - * @param allowedOrigins The value of 'Access-Control-Allow-Origin' header. - * @return Itself for chaining methods calls. - */ - public CorsFilter setAllowedOrigins(Set allowedOrigins) { - this.allowedOrigins = allowedOrigins; - return this; - } - - /** - * If true, copies the value of 'Access-Control-Request-Headers' request header - * into the 'Access-Control-Allow-Headers' response header. If false, use - * {@link #allowedHeaders}. - * - * @param allowingAllRequestedHeaders True to copy the value of - * 'Access-Control-Request-Headers' request - * header into the - * 'Access-Control-Allow-Headers' response - * header. If false, use - * {@link #allowedHeaders}. - * @return Itself for chaining methods calls. - */ - public CorsFilter setAllowingAllRequestedHeaders(boolean allowingAllRequestedHeaders) { - this.allowAllRequestedHeaders = allowingAllRequestedHeaders; - return this; - } - - /** - * Sets the list of methods allowed by default, used when - * {@link #skippingResourceForCorsOptions} is turned on. - * - * @param defaultAllowedMethods The list of methods allowed by default, used - * when {@link #skippingResourceForCorsOptions} is - * turned on. - * @return Itself for chaining methods calls. - */ - public CorsFilter setDefaultAllowedMethods(Set defaultAllowedMethods) { - this.defaultAllowedMethods = defaultAllowedMethods; - return this; - } - - /** - * Sets the value of 'Access-Control-Expose-Headers' response header. - * - * @param exposedHeaders The value of 'Access-Control-Expose-Headers' response - * header. - * @return Itself for chaining methods calls. - */ - public CorsFilter setExposedHeaders(Set exposedHeaders) { - this.exposedHeaders = exposedHeaders; - return this; - } - - /** - * Sets the value of 'Access-Control-Max-Age' response header.
- * In case of negative value, the header is not set. - * - * @param maxAge The value of 'Access-Control-Max-Age' response header. - */ - public CorsFilter setMaxAge(int maxAge) { - this.maxAge = maxAge; - return this; - } - - /** - * Sets the value of skipResourceForCorsOptions field. - * - * @param skipResourceForCorsOptions True if the filter does not call the server - * resource for OPTIONS method of CORS - * request. - * @return Itself for chaining methods calls. - */ - public CorsFilter setSkippingResourceForCorsOptions(boolean skipResourceForCorsOptions) { - this.skippingResourceForCorsOptions = skipResourceForCorsOptions; - return this; - } + /** + * If true, copies the value of 'Access-Control-Request-Headers' request header into the + * 'Access-Control-Allow-Headers' response header. If false, use {@link #allowedHeaders}. + * Default is true. + */ + private boolean allowAllRequestedHeaders = true; + + /** If true, add an 'Access-Control-Allow-Credentials' header. Default is false. */ + private boolean allowedCredentials = false; + + /** + * The value of 'Access-Control-Allow-Headers' response header. Used only if {@link + * #allowAllRequestedHeaders} is false. + */ + private Set allowedHeaders = null; + + /** The value of the 'Access-Control-Allow-Origin' header. Default is '*'. */ + private Set allowedOrigins = SetUtils.newHashSet("*"); + + /** Helper for generating CORS response. */ + private CorsResponseHelper corsResponseHelper; + + /** + * The set of methods allowed by default, used when {@link #skippingResourceForCorsOptions} is + * turned on. Default methods: GET, PUT, POST, DELETE, PATCH. + */ + private Set defaultAllowedMethods = + new HashSet<>( + List.of(Method.GET, Method.POST, Method.PUT, Method.DELETE, Method.PATCH)); + + /** The value of 'Access-Control-Expose-Headers' response header. */ + private Set exposedHeaders = null; + + /** + * The value of the 'Access-Control-Max-Age' response header. By default, the header is not set. + */ + private int maxAge = -1; + + /** + * If true, the filter does not call the server resource for OPTIONS method of CORS request and + * set Access-Control-Allow-Methods header with {@link #defaultAllowedMethods}. Default is + * false. + */ + private boolean skippingResourceForCorsOptions = false; + + /** Constructor. */ + public CorsFilter() { + this(null); + } + + /** + * Constructor. + * + * @param context The context. + */ + public CorsFilter(Context context) { + super(context, null); + } + + /** + * Constructor. + * + * @param context The context. + * @param next The next Restlet. + */ + public CorsFilter(Context context, Restlet next) { + super(context, next); + } + + /** + * Add CORS headers to response + * + * @param request The request to handle. + * @param response The response + */ + @Override + protected void afterHandle(Request request, Response response) { + getCorsResponseHelper().addCorsResponseHeaders(request, response); + } + + /** + * Skip the call to the server resource if the {@link #skippingResourceForCorsOptions} is true + * and if the current request use the OPTIONS method and is a CORS request. + * + * @param request The request to handle. + * @param response The response to update. + * @return The continuation status. Either {@link #CONTINUE} or {@link #SKIP} or {@link #STOP}. + */ + @Override + protected int beforeHandle(Request request, Response response) { + if (skippingResourceForCorsOptions + && Method.OPTIONS.equals(request.getMethod()) + && getCorsResponseHelper().isCorsRequest(request)) { + response.setAllowedMethods(getDefaultAllowedMethods()); + return Filter.SKIP; + } else { + return Filter.CONTINUE; + } + } + + /** + * Returns the modifiable set of headers allowed by the actual request on the current resource. + *
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Headers" header. + * + * @return The set of headers allowed by the actual request on the current resource. + */ + public Set getAllowedHeaders() { + return allowedHeaders; + } + + /** + * Returns the URI an origin server allows for the requested resource. Use "*" as a wildcard + * character.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Origin" header. + * + * @return The origin allowed by the requested resource. + */ + public Set getAllowedOrigins() { + return allowedOrigins; + } + + /** + * Returns a lazy-initialized instance of {@link + * org.restlet.engine.application.CorsResponseHelper}. + */ + protected CorsResponseHelper getCorsResponseHelper() { + if (corsResponseHelper == null) { + corsResponseHelper = new CorsResponseHelper(); + corsResponseHelper.setAllowedCredentials(allowedCredentials); + corsResponseHelper.setAllowedOrigins(allowedOrigins); + corsResponseHelper.setAllowAllRequestedHeaders(allowAllRequestedHeaders); + corsResponseHelper.setAllowedHeaders(allowedHeaders); + corsResponseHelper.setExposedHeaders(exposedHeaders); + corsResponseHelper.setMaxAge(maxAge); + } + return corsResponseHelper; + } + + /** + * Returns the list of methods allowed by default, used when {@link + * #skippingResourceForCorsOptions} is turned on. + * + * @return The list of methods allowed by default, used when {@link + * #skippingResourceForCorsOptions} is turned on. + */ + public Set getDefaultAllowedMethods() { + return defaultAllowedMethods; + } + + /** + * Returns a modifiable whitelist of headers an origin server allows for the requested resource. + *
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Expose-Headers" header. + * + * @return The set of headers an origin server allows for the requested resource. + */ + public Set getExposedHeaders() { + return exposedHeaders; + } + + /** + * Indicates how long (in seconds) the results of a preflight request can be cached in a + * preflight result cache.
+ * In case of a negative value, the results of a preflight request are not meant to be cached. + *
+ * Note that when used with HTTP connectors, this property maps to the "Access-Control-Max-Age" + * header. + * + * @return Indicates how long the results of a preflight request can be cached in a preflight + * result cache. + */ + public int getMaxAge() { + return maxAge; + } + + /** + * If true, indicates that the value of 'Access-Control-Request-Headers' request header will be + * copied into the 'Access-Control-Allow-Headers' response header. If false, use {@link + * #allowedHeaders}. + */ + public boolean isAllowAllRequestedHeaders() { + return allowAllRequestedHeaders; + } + + /** + * If true, adds 'Access-Control-Allow-Credentials' header. + * + * @return True, if the 'Access-Control-Allow-Credentials' header will be added. + */ + public boolean isAllowedCredentials() { + return allowedCredentials; + } + + /** + * If true, the filter does not call the server resource for OPTIONS method of CORS request and + * set Access-Control-Allow-Methods header with {@link #defaultAllowedMethods}. Default is + * false. + * + * @return True if the filter does not call the server resource for OPTIONS method of CORS + * request. + */ + public boolean isSkippingResourceForCorsOptions() { + return skippingResourceForCorsOptions; + } + + /** + * If true, adds 'Access-Control-Allow-Credentials' header. + * + * @param allowedCredentials True to add the 'Access-Control-Allow-Credentials' header. + * @return Itself for chaining methods calls. + */ + public CorsFilter setAllowedCredentials(boolean allowedCredentials) { + this.allowedCredentials = allowedCredentials; + return this; + } + + /** + * Sets the value of the 'Access-Control-Allow-Headers' response header. Used only if {@link + * #allowAllRequestedHeaders} is false. + * + * @param allowedHeaders The value of 'Access-Control-Allow-Headers' response header. + * @return Itself for chaining methods calls. + */ + public CorsFilter setAllowedHeaders(Set allowedHeaders) { + this.allowedHeaders = allowedHeaders; + return this; + } + + /** + * Sets the value of the 'Access-Control-Allow-Origin' header. + * + * @param allowedOrigins The value of the 'Access-Control-Allow-Origin' header. + * @return Itself for chaining methods calls. + */ + public CorsFilter setAllowedOrigins(Set allowedOrigins) { + this.allowedOrigins = allowedOrigins; + return this; + } + + /** + * If true, copies the value of 'Access-Control-Request-Headers' request header into the + * 'Access-Control-Allow-Headers' response header. If false, use {@link #allowedHeaders}. + * + * @param allowingAllRequestedHeaders True to copy the value of 'Access-Control-Request-Headers' + * request header into the 'Access-Control-Allow-Headers' response header. If false, use + * {@link #allowedHeaders}. + * @return Itself for chaining methods calls. + */ + public CorsFilter setAllowingAllRequestedHeaders(boolean allowingAllRequestedHeaders) { + this.allowAllRequestedHeaders = allowingAllRequestedHeaders; + return this; + } + + /** + * Sets the list of methods allowed by default, used when {@link + * #skippingResourceForCorsOptions} is turned on. + * + * @param defaultAllowedMethods The list of methods allowed by default, used when {@link + * #skippingResourceForCorsOptions} is turned on. + * @return Itself for chaining methods calls. + */ + public CorsFilter setDefaultAllowedMethods(Set defaultAllowedMethods) { + this.defaultAllowedMethods = defaultAllowedMethods; + return this; + } + + /** + * Sets the value of 'Access-Control-Expose-Headers' response header. + * + * @param exposedHeaders The value of 'Access-Control-Expose-Headers' response header. + * @return Itself for chaining methods calls. + */ + public CorsFilter setExposedHeaders(Set exposedHeaders) { + this.exposedHeaders = exposedHeaders; + return this; + } + + /** + * Sets the value of the 'Access-Control-Max-Age' response header.
+ * In the case of negative value, the header is not set. + * + * @param maxAge The value of the 'Access-Control-Max-Age' response header. + */ + public CorsFilter setMaxAge(int maxAge) { + this.maxAge = maxAge; + return this; + } + + /** + * Sets the value of the skipResourceForCorsOptions field. + * + * @param skipResourceForCorsOptions True if the filter does not call the server resource for + * OPTIONS method of CORS request. + * @return Itself for chaining methods calls. + */ + public CorsFilter setSkippingResourceForCorsOptions(boolean skipResourceForCorsOptions) { + this.skippingResourceForCorsOptions = skipResourceForCorsOptions; + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/CorsResponseHelper.java b/org.restlet/src/main/java/org/restlet/engine/application/CorsResponseHelper.java index e293396a8f..381c800b05 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/CorsResponseHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/CorsResponseHelper.java @@ -1,14 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -16,331 +18,314 @@ import org.restlet.data.Status; import org.restlet.engine.util.SetUtils; -import java.util.HashSet; -import java.util.Set; -import java.util.logging.Logger; - /** * Helps to generate response CORS headers.
- * The CORS specification defines a subset of methods qualified as simple HEAD, - * GET and POST. Any other methods should send a preflight request with the - * method OPTIONS. - * + * The CORS specification defines a subset of methods qualified as simple HEAD, GET and POST. Any + * other methods should send a preflight request with the method OPTIONS. + * * @see W3C CORS Specification * @see Simple methods - * * @author Manuel Boillod */ public class CorsResponseHelper { - private static Logger LOGGER = Context.getCurrentLogger(); - - /** - * If true, copies the value of 'Access-Control-Request-Headers' request header - * into the 'Access-Control-Allow-Headers' response header. If false, use - * {@link #allowedHeaders}. Default is true. - */ - public boolean allowAllRequestedHeaders = true; - - /** - * If true, add 'Access-Control-Allow-Credentials' header. Default is false. - */ - public boolean allowedCredentials = false; - - /** - * The value of 'Access-Control-Allow-Headers' response header. Used only if - * {@link #allowAllRequestedHeaders} is false. - */ - public Set allowedHeaders = null; - - /** The value of 'Access-Control-Allow-Origin' header. Default is '*'. */ - public Set allowedOrigins = SetUtils.newHashSet("*"); - - /** The value of 'Access-Control-Expose-Headers' response header. */ - public Set exposedHeaders = null; - - /** - * The value of 'Access-Control-Max-Age' response header. Default is that the - * header is not set. - */ - public int maxAge = -1; - - /** - * Adds CORS headers to the given response. - * - * @param request The current request. - * @param response The response. - */ - public void addCorsResponseHeaders(Request request, Response response) { - - String origin = request.getHeaders().getFirstValue("Origin", true); - - if (origin == null) { - // Not a CORS request - return; - } - - Set allowedMethods = new HashSet<>(response.getAllowedMethods()); - // Header 'Allow' is not relevant in CORS request. - response.getAllowedMethods().clear(); - - if (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin)) { - // Origin not allowed - LOGGER.fine("Origin " + origin + " not allowed for CORS request"); - return; - } - - boolean isPreflightRequest = Method.OPTIONS.equals(request.getMethod()); - - if (isPreflightRequest) { - - // Default OPTIONS method in a server resource returns a - // {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED} if the method is - // not implemented - // or a {@link Status#SUCCESS_NO_CONTENT} or a {@link - // Status#SUCCESS_NO_CONTENT} if - // the method is implemented and the call succeed. - // Other status are considered as error. - - // Preflight request returns a 200 status except if server resource - // method . - // If the preflight request is not allowed, CORS response headers - // will not be added. - if (Status.SUCCESS_OK.equals(response.getStatus()) || Status.SUCCESS_NO_CONTENT.equals(response.getStatus()) - || Status.CLIENT_ERROR_METHOD_NOT_ALLOWED.equals(response.getStatus())) { - response.setStatus(Status.SUCCESS_OK); - } else { - LOGGER.fine("The CORS preflight request failed in server resource."); - return; - } - - Method requestedMethod = request.getAccessControlRequestMethod(); - if (requestedMethod == null) { - // Requested Method is required - LOGGER.fine("A CORS preflight request should specified header 'Access-Control-Request-Method'"); - return; - } - - if (!allowedMethods.contains(requestedMethod)) { - // Method not allowed - LOGGER.fine( - "The CORS preflight request ask for methods not allowed in header 'Access-Control-Request-Method'"); - return; - } - - Set requestedHeaders = request.getAccessControlRequestHeaders(); - if (requestedHeaders == null) { - requestedHeaders = SetUtils.newHashSet(); - } - - if (!allowAllRequestedHeaders - && (allowedHeaders == null || !isAllHeadersAllowed(allowedHeaders, requestedHeaders))) { - // Headers not allowed - LOGGER.fine( - "The CORS preflight request ask for headers not allowed in header 'Access-Control-Request-Headers'"); - return; - } - - // Header 'Access-Control-Allow-Methods' - response.setAccessControlAllowMethods(allowedMethods); - - // Header 'Access-Control-Allow-Headers' - response.setAccessControlAllowHeaders(requestedHeaders); - - if (getMaxAge() > 0) { - response.setAccessControlMaxAge(getMaxAge()); - } - } else { - // simple request - - // Header 'Access-Control-Expose-Headers' - if (exposedHeaders != null && !exposedHeaders.isEmpty()) { - response.setAccessControlExposeHeaders(exposedHeaders); - } - } - - // Header 'Access-Control-Allow-Credentials' - if (allowedCredentials) { - response.setAccessControlAllowCredentials(true); - } - - // Header 'Access-Control-Allow-Origin' - if (!allowedCredentials && allowedOrigins.contains("*")) { - response.setAccessControlAllowOrigin("*"); - } else { - response.setAccessControlAllowOrigin(origin); - } - } - - /** - * Returns the modifiable set of headers allowed by the actual request on the - * current resource.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Headers" header. - * - * @return The set of headers allowed by the actual request on the current - * resource. - */ - public Set getAllowedHeaders() { - return allowedHeaders; - } - - /** - * Returns the URI an origin server allows for the requested resource. Use "*" - * as a wildcard character.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Origin" header. - * - * @return The origin allowed by the requested resource. - */ - public Set getAllowedOrigins() { - return allowedOrigins; - } - - /** - * Returns a modifiable whitelist of headers an origin server allows for the - * requested resource.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Expose-Headers" header. - * - * @return The set of headers an origin server allows for the requested - * resource. - */ - public Set getExposedHeaders() { - return exposedHeaders; - } - - /** - * Indicates how long (in seconds) the results of a preflight request can be - * cached in a preflight result cache.
- * In case of a negative value, the results of a preflight request is not meant - * to be cached.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Max-Age" header. - * - * @return Indicates how long the results of a preflight request can be cached - * in a preflight result cache. - */ - public int getMaxAge() { - return maxAge; - } - - /** - * Returns true if all requested headers are allowed (case-insensitive). - * - * @param allowHeaders The allowed headers. - * @param requestedHeaders The requested headers. - * @return True if all requested headers are allowed (case-insensitive). - */ - private boolean isAllHeadersAllowed(Set allowHeaders, Set requestedHeaders) { - for (String requestedHeader : requestedHeaders) { - boolean headerAllowed = false; - for (String allowHeader : allowHeaders) { - if (allowHeader.equalsIgnoreCase(requestedHeader)) { - headerAllowed = true; - break; - } - } - if (!headerAllowed) { - return false; - } - } - return true; - } - - /** - * If true, indicates that the value of 'Access-Control-Request-Headers' request - * header will be copied into the 'Access-Control-Allow-Headers' response - * header. If false, use {@link #allowedHeaders}. - */ - public boolean isAllowAllRequestedHeaders() { - return allowAllRequestedHeaders; - } - - /** - * If true, adds 'Access-Control-Allow-Credentials' header. - * - * @return True, if the 'Access-Control-Allow-Credentials' header will be added. - */ - public boolean isAllowedCredentials() { - return allowedCredentials; - } - - /** - * Returns true if the request is a CORS request. - * - * @param request The current request. - * @return true if the request is a CORS request. - */ - public boolean isCorsRequest(Request request) { - return request.getHeaders().getFirstValue("Origin", true) != null; - } - - /** - * If true, copies the value of 'Access-Control-Request-Headers' request header - * into the 'Access-Control-Allow-Headers' response header. If false, use - * {@link #allowedHeaders}. - * - * @param allowAllRequestedHeaders True to copy the value of - * 'Access-Control-Request-Headers' request - * header into the - * 'Access-Control-Allow-Headers' response - * header. If false, use - * {@link #allowedHeaders}. - */ - public void setAllowAllRequestedHeaders(boolean allowAllRequestedHeaders) { - this.allowAllRequestedHeaders = allowAllRequestedHeaders; - } - - /** - * If true, adds 'Access-Control-Allow-Credentials' header. - * - * @param allowedCredentials True to add the 'Access-Control-Allow-Credentials' - * header. - */ - public void setAllowedCredentials(boolean allowedCredentials) { - this.allowedCredentials = allowedCredentials; - } - - /** - * Sets the value of the 'Access-Control-Allow-Headers' response header. Used - * only if {@link #allowAllRequestedHeaders} is false. - * - * @param allowedHeaders The value of 'Access-Control-Allow-Headers' response - * header. - */ - public void setAllowedHeaders(Set allowedHeaders) { - this.allowedHeaders = allowedHeaders; - } - - /** - * Sets the value of 'Access-Control-Allow-Origin' header. - * - * @param allowedOrigins The value of 'Access-Control-Allow-Origin' header. - */ - public void setAllowedOrigins(Set allowedOrigins) { - this.allowedOrigins = allowedOrigins; - } - - /** - * Sets the value of 'Access-Control-Expose-Headers' response header. - * - * @param exposedHeaders The value of 'Access-Control-Expose-Headers' response - * header. - */ - public void setExposedHeaders(Set exposedHeaders) { - this.exposedHeaders = exposedHeaders; - } - - /** - * Sets the value of 'Access-Control-Max-Age' response header.
- * In case of negative value, the header is not set. - * - * @param maxAge The value of 'Access-Control-Max-Age' response header. - */ - public void setMaxAge(int maxAge) { - this.maxAge = maxAge; - - } - + private static final Logger LOGGER = Context.getCurrentLogger(); + + /** + * If true, copies the value of 'Access-Control-Request-Headers' request header into the + * 'Access-Control-Allow-Headers' response header. If false, use {@link #allowedHeaders}. + * Default is true. + */ + private boolean allowAllRequestedHeaders = true; + + /** If true, add an 'Access-Control-Allow-Credentials' header. Default is false. */ + private boolean allowedCredentials = false; + + /** + * The value of 'Access-Control-Allow-Headers' response header. Used only if {@link + * #allowAllRequestedHeaders} is false. + */ + private Set allowedHeaders = null; + + /** The value of the 'Access-Control-Allow-Origin' header. Default is '*'. */ + private Set allowedOrigins = SetUtils.newHashSet("*"); + + /** The value of 'Access-Control-Expose-Headers' response header. */ + private Set exposedHeaders = null; + + /** + * The value of the 'Access-Control-Max-Age' response header. By default, the header is not set. + */ + private int maxAge = -1; + + /** + * Adds CORS headers to the given response. + * + * @param request The current request. + * @param response The response. + */ + public void addCorsResponseHeaders(Request request, Response response) { + + String origin = request.getHeaders().getFirstValue("Origin", true); + + if (origin == null) { + // Not a CORS request + return; + } + + Set allowedMethods = new HashSet<>(response.getAllowedMethods()); + // Header 'Allow' is not relevant in a CORS request. + response.getAllowedMethods().clear(); + + if (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin)) { + // Origin isn't allowed + LOGGER.fine(() -> "Origin " + origin + " not allowed for CORS request"); + return; + } + + boolean isPreflightRequest = Method.OPTIONS.equals(request.getMethod()); + + if (isPreflightRequest) { + + // Default OPTIONS method in a server resource returns a + // {@link Status#CLIENT_ERROR_METHOD_NOT_ALLOWED} if the method is + // not implemented + // or a {@link Status#SUCCESS_NO_CONTENT} or a {@link + // Status#SUCCESS_NO_CONTENT} if + // the method is implemented and the call succeeds. + // Other status is considered as an error. + + // Preflight request returns a 200 status except if the server resource + // method is not allowed. + // If the preflight request is not allowed, CORS response headers + // will not be added. + if (Status.SUCCESS_OK.equals(response.getStatus()) + || Status.SUCCESS_NO_CONTENT.equals(response.getStatus()) + || Status.CLIENT_ERROR_METHOD_NOT_ALLOWED.equals(response.getStatus())) { + response.setStatus(Status.SUCCESS_OK); + } else { + LOGGER.fine("The CORS preflight request failed in server resource."); + return; + } + + Method requestedMethod = request.getAccessControlRequestMethod(); + if (requestedMethod == null) { + // Requested Method is required + LOGGER.fine( + "A CORS preflight request should specified header 'Access-Control-Request-Method'"); + return; + } + + if (!allowedMethods.contains(requestedMethod)) { + // Method isn't allowed + LOGGER.fine( + "The CORS preflight request ask for methods not allowed in header 'Access-Control-Request-Method'"); + return; + } + + Set requestedHeaders = request.getAccessControlRequestHeaders(); + if (requestedHeaders == null) { + requestedHeaders = SetUtils.newHashSet(); + } + + if (!allowAllRequestedHeaders + && (allowedHeaders == null + || !isAllHeadersAllowed(allowedHeaders, requestedHeaders))) { + // Headers aren't allowed + LOGGER.fine( + "The CORS preflight request ask for headers not allowed in header 'Access-Control-Request-Headers'"); + return; + } + + // Header 'Access-Control-Allow-Methods' + response.setAccessControlAllowMethods(allowedMethods); + + // Header 'Access-Control-Allow-Headers' + response.setAccessControlAllowHeaders(requestedHeaders); + + if (getMaxAge() > 0) { + response.setAccessControlMaxAge(getMaxAge()); + } + } else { + // simple request + + // Header 'Access-Control-Expose-Headers' + if (exposedHeaders != null && !exposedHeaders.isEmpty()) { + response.setAccessControlExposeHeaders(exposedHeaders); + } + } + + // Header 'Access-Control-Allow-Credentials' + if (allowedCredentials) { + response.setAccessControlAllowCredentials(true); + } + + // Header 'Access-Control-Allow-Origin' + if (!allowedCredentials && allowedOrigins.contains("*")) { + response.setAccessControlAllowOrigin("*"); + } else { + response.setAccessControlAllowOrigin(origin); + } + } + + /** + * Returns the modifiable set of headers allowed by the actual request on the current resource. + *
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Headers" header. + * + * @return The set of headers allowed by the actual request on the current resource. + */ + public Set getAllowedHeaders() { + return allowedHeaders; + } + + /** + * Returns the URI an origin server allows for the requested resource. Use "*" as a wildcard + * character.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Origin" header. + * + * @return The origin allowed by the requested resource. + */ + public Set getAllowedOrigins() { + return allowedOrigins; + } + + /** + * Returns a modifiable whitelist of headers an origin server allows for the requested resource. + *
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Expose-Headers" header. + * + * @return The set of headers an origin server allows for the requested resource. + */ + public Set getExposedHeaders() { + return exposedHeaders; + } + + /** + * Indicates how long (in seconds) the results of a preflight request can be cached in a + * preflight result cache.
+ * In case of a negative value, the results of a preflight request are not meant to be cached. + *
+ * Note that when used with HTTP connectors, this property maps to the "Access-Control-Max-Age" + * header. + * + * @return Indicates how long the results of a preflight request can be cached in a preflight + * result cache. + */ + public int getMaxAge() { + return maxAge; + } + + /** + * Returns true if all requested headers are allowed (case-insensitive). + * + * @param allowHeaders The allowed headers. + * @param requestedHeaders The requested headers. + * @return True if all requested headers are allowed (case-insensitive). + */ + private boolean isAllHeadersAllowed(Set allowHeaders, Set requestedHeaders) { + for (String requestedHeader : requestedHeaders) { + boolean headerAllowed = false; + for (String allowHeader : allowHeaders) { + if (allowHeader.equalsIgnoreCase(requestedHeader)) { + headerAllowed = true; + break; + } + } + if (!headerAllowed) { + return false; + } + } + return true; + } + + /** + * If true, indicates that the value of 'Access-Control-Request-Headers' request header will be + * copied into the 'Access-Control-Allow-Headers' response header. If false, use {@link + * #allowedHeaders}. + */ + public boolean isAllowAllRequestedHeaders() { + return allowAllRequestedHeaders; + } + + /** + * If true, adds 'Access-Control-Allow-Credentials' header. + * + * @return True, if the 'Access-Control-Allow-Credentials' header will be added. + */ + public boolean isAllowedCredentials() { + return allowedCredentials; + } + + /** + * Returns true if the request is a CORS request. + * + * @param request The current request. + * @return true if the request is a CORS request. + */ + public boolean isCorsRequest(Request request) { + return request.getHeaders().getFirstValue("Origin", true) != null; + } + + /** + * If true, copies the value of 'Access-Control-Request-Headers' request header into the + * 'Access-Control-Allow-Headers' response header. If false, use {@link #allowedHeaders}. + * + * @param allowAllRequestedHeaders True to copy the value of 'Access-Control-Request-Headers' + * request header into the 'Access-Control-Allow-Headers' response header. If false, use + * {@link #allowedHeaders}. + */ + public void setAllowAllRequestedHeaders(boolean allowAllRequestedHeaders) { + this.allowAllRequestedHeaders = allowAllRequestedHeaders; + } + + /** + * If true, adds 'Access-Control-Allow-Credentials' header. + * + * @param allowedCredentials True to add the 'Access-Control-Allow-Credentials' header. + */ + public void setAllowedCredentials(boolean allowedCredentials) { + this.allowedCredentials = allowedCredentials; + } + + /** + * Sets the value of the 'Access-Control-Allow-Headers' response header. Used only if {@link + * #allowAllRequestedHeaders} is false. + * + * @param allowedHeaders The value of 'Access-Control-Allow-Headers' response header. + */ + public void setAllowedHeaders(Set allowedHeaders) { + this.allowedHeaders = allowedHeaders; + } + + /** + * Sets the value of the 'Access-Control-Allow-Origin' header. + * + * @param allowedOrigins The value of the 'Access-Control-Allow-Origin' header. + */ + public void setAllowedOrigins(Set allowedOrigins) { + this.allowedOrigins = allowedOrigins; + } + + /** + * Sets the value of 'Access-Control-Expose-Headers' response header. + * + * @param exposedHeaders The value of 'Access-Control-Expose-Headers' response header. + */ + public void setExposedHeaders(Set exposedHeaders) { + this.exposedHeaders = exposedHeaders; + } + + /** + * Sets the value of the 'Access-Control-Max-Age' response header.
+ * In the case of negative value, the header is not set. + * + * @param maxAge The value of the 'Access-Control-Max-Age' response header. + */ + public void setMaxAge(int maxAge) { + this.maxAge = maxAge; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/DecodeRepresentation.java b/org.restlet/src/main/java/org/restlet/engine/application/DecodeRepresentation.java index 7bd3384559..3b8e5cad6b 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/DecodeRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/DecodeRepresentation.java @@ -1,25 +1,20 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; -import org.restlet.data.Encoding; -import org.restlet.engine.io.IoUtils; -import org.restlet.representation.Representation; -import org.restlet.util.WrapperRepresentation; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -27,182 +22,199 @@ import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; import java.util.zip.ZipInputStream; +import org.restlet.data.Encoding; +import org.restlet.engine.io.IoUtils; +import org.restlet.representation.Representation; +import org.restlet.util.WrapperRepresentation; /** - * Representation that decodes a wrapped representation if its encoding is - * supported. If at least one encoding of the wrapped representation is not - * supported, then the wrapped representation is not decoded. - * + * Representation that decodes a wrapped representation if its encoding is supported. If at least + * one encoding of the wrapped representation is not supported, then the wrapped representation is + * not decoded. + * * @author Jerome Louvel */ public class DecodeRepresentation extends WrapperRepresentation { - /** - * Returns the list of supported encodings. - * - * @return The list of supported encodings. - */ - public static List getSupportedEncodings() { - return Arrays.asList(Encoding.GZIP, Encoding.DEFLATE, Encoding.DEFLATE_NOWRAP, Encoding.ZIP, - Encoding.IDENTITY); - } - - /** Indicates if the decoding can happen. */ - private volatile boolean decoding; - - /** List of encodings still applied to the decodeRepresentation */ - private final List wrappedEncodings; - - /** - * Constructor. - * - * @param wrappedRepresentation The wrapped representation. - */ - public DecodeRepresentation(Representation wrappedRepresentation) { - super(wrappedRepresentation); - this.decoding = getSupportedEncodings().containsAll(wrappedRepresentation.getEncodings()); - this.wrappedEncodings = new CopyOnWriteArrayList(wrappedRepresentation.getEncodings()); - } - - @Override - public long getAvailableSize() { - return IoUtils.getAvailableSize(this); - } - - /** - * Returns a decoded stream for a given encoding and coded stream. - * - * @param encoding The encoding to use. - * @param encodedStream The encoded stream. - * @return The decoded stream. - * @throws IOException - */ - private InputStream getDecodedStream(Encoding encoding, InputStream encodedStream) throws IOException { - InputStream result = null; - - if (encodedStream != null) { - if (encoding.equals(Encoding.GZIP)) { - result = new GZIPInputStream(encodedStream); - } else if (encoding.equals(Encoding.DEFLATE)) { - result = new InflaterInputStream(encodedStream); - } else if (encoding.equals(Encoding.DEFLATE_NOWRAP)) { - result = new InflaterInputStream(encodedStream, new Inflater(true)); - } else if (encoding.equals(Encoding.ZIP)) { - final ZipInputStream stream = new ZipInputStream(encodedStream); - if (stream.getNextEntry() != null) { - result = stream; - } - } else if (encoding.equals(Encoding.IDENTITY)) { - throw new IOException("Decoder unecessary for identity decoding"); - } - } - - return result; - } - - /** - * Returns the encodings applied to the entity. - * - * @return The encodings applied to the entity. - */ - @Override - public List getEncodings() { - if (isDecoding()) { - return new ArrayList(); - } else { - return this.wrappedEncodings; - } - } - - @Override - public Reader getReader() throws IOException { - if (isDecoding()) { - return IoUtils.getReader(getStream(), getCharacterSet()); - } else { - return getWrappedRepresentation().getReader(); - } - } - - /** - * Returns the size in bytes of the decoded representation if known, - * UNKNOWN_SIZE (-1) otherwise. - * - * @return The size in bytes if known, UNKNOWN_SIZE (-1) otherwise. - */ - @Override - public long getSize() { - long result = UNKNOWN_SIZE; - - if (isDecoding()) { - boolean identity = true; - for (final Iterator iter = this.wrappedEncodings.iterator(); identity && iter.hasNext();) { - identity = (iter.next().equals(Encoding.IDENTITY)); - } - if (identity) { - result = getWrappedRepresentation().getSize(); - } - } else { - result = getWrappedRepresentation().getSize(); - } - - return result; - } - - /** - * Returns a stream with the representation's content. - * - * @return A stream with the representation's content. - */ - @Override - public InputStream getStream() throws IOException { - InputStream result = getWrappedRepresentation().getStream(); - - if (isDecoding()) { - for (int i = this.wrappedEncodings.size() - 1; i >= 0; i--) { - if (!this.wrappedEncodings.get(i).equals(Encoding.IDENTITY)) { - result = getDecodedStream(this.wrappedEncodings.get(i), result); - } - } - } - - return result; - } - - /** - * Converts the representation to a string value. Be careful when using this - * method as the conversion of large content to a string fully stored in memory - * can result in OutOfMemoryErrors being thrown. - * - * @return The representation as a string value. - */ - @Override - public String getText() throws IOException { - if (isDecoding()) { - return IoUtils.toString(getStream(), getCharacterSet()); - } else { - return getWrappedRepresentation().getText(); - } - } - - /** - * Indicates if the decoding can happen. - * - * @return True if the decoding can happen. - */ - public boolean isDecoding() { - return this.decoding; - } - - /** - * Writes the representation to a byte stream. - * - * @param outputStream The output stream. - */ - @Override - public void write(OutputStream outputStream) throws IOException { - if (isDecoding()) { - IoUtils.copy(getStream(), outputStream); - } else { - getWrappedRepresentation().write(outputStream); - } - } + /** + * Returns the list of supported encodings. + * + * @return The list of supported encodings. + */ + public static List getSupportedEncodings() { + return Arrays.asList( + Encoding.GZIP, + Encoding.DEFLATE, + Encoding.DEFLATE_NOWRAP, + Encoding.ZIP, + Encoding.IDENTITY); + } + + /** Indicates if the decoding can happen. */ + private volatile boolean decoding; + + /** List of encodings still applied to the decodeRepresentation */ + private final List wrappedEncodings; + + /** + * Constructor. + * + * @param wrappedRepresentation The wrapped representation. + */ + public DecodeRepresentation(Representation wrappedRepresentation) { + super(wrappedRepresentation); + this.decoding = + new HashSet<>(getSupportedEncodings()) + .containsAll(wrappedRepresentation.getEncodings()); + this.wrappedEncodings = new CopyOnWriteArrayList<>(wrappedRepresentation.getEncodings()); + } + + @Override + public boolean equals(final Object obj) { + return super.equals(obj); + } + + @Override + public long getAvailableSize() { + return IoUtils.getAvailableSize(this); + } + + /** + * Returns a decoded stream for a given encoding and coded stream. + * + * @param encoding The encoding to use. + * @param encodedStream The encoded stream. + * @return The decoded stream. + * @throws IOException + */ + private InputStream getDecodedStream(Encoding encoding, InputStream encodedStream) + throws IOException { + InputStream result = null; + + if (encodedStream != null) { + if (encoding.equals(Encoding.GZIP)) { + result = new GZIPInputStream(encodedStream); + } else if (encoding.equals(Encoding.DEFLATE)) { + result = new InflaterInputStream(encodedStream); + } else if (encoding.equals(Encoding.DEFLATE_NOWRAP)) { + result = new InflaterInputStream(encodedStream, new Inflater(true)); + } else if (encoding.equals(Encoding.ZIP)) { + final ZipInputStream stream = new ZipInputStream(encodedStream); + if (stream.getNextEntry() != null) { + result = stream; + } + } else if (encoding.equals(Encoding.IDENTITY)) { + throw new IOException("Decoder unnecessary for identity decoding"); + } + } + + return result; + } + + /** + * Returns the encodings applied to the entity. + * + * @return The encodings applied to the entity. + */ + @Override + public List getEncodings() { + if (isDecoding()) { + return new ArrayList<>(); + } else { + return this.wrappedEncodings; + } + } + + @Override + public Reader getReader() throws IOException { + if (isDecoding()) { + return IoUtils.getReader(getStream(), getCharacterSet()); + } else { + return getWrappedRepresentation().getReader(); + } + } + + /** + * Returns the size in bytes of the decoded representation if known, UNKNOWN_SIZE (-1) + * otherwise. + * + * @return The size in bytes if known, UNKNOWN_SIZE (-1) otherwise. + */ + @Override + public long getSize() { + long result = UNKNOWN_SIZE; + + if (isDecoding()) { + boolean identity = true; + for (final Iterator iter = this.wrappedEncodings.iterator(); + identity && iter.hasNext(); ) { + identity = (iter.next().equals(Encoding.IDENTITY)); + } + if (identity) { + result = getWrappedRepresentation().getSize(); + } + } else { + result = getWrappedRepresentation().getSize(); + } + + return result; + } + + /** + * Returns a stream with the representation's content. + * + * @return A stream with the representation's content. + */ + @Override + public InputStream getStream() throws IOException { + InputStream result = getWrappedRepresentation().getStream(); + + if (isDecoding()) { + for (int i = this.wrappedEncodings.size() - 1; i >= 0; i--) { + if (!this.wrappedEncodings.get(i).equals(Encoding.IDENTITY)) { + result = getDecodedStream(this.wrappedEncodings.get(i), result); + } + } + } + + return result; + } + + /** + * Converts the representation to a string value. Be careful when using this method as the + * conversion of large content to a string fully stored in memory can result in + * OutOfMemoryErrors being thrown. + * + * @return The representation as a string value. + */ + @Override + public String getText() throws IOException { + if (isDecoding()) { + return IoUtils.toString(getStream(), getCharacterSet()); + } else { + return getWrappedRepresentation().getText(); + } + } + + /** + * Indicates if the decoding can happen. + * + * @return True if the decoding can happen. + */ + public boolean isDecoding() { + return this.decoding; + } + + /** + * Writes the representation to a byte stream. + * + * @param outputStream The output stream. + */ + @Override + public void write(OutputStream outputStream) throws IOException { + if (isDecoding()) { + IoUtils.copy(getStream(), outputStream); + } else { + getWrappedRepresentation().write(outputStream); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/Decoder.java b/org.restlet/src/main/java/org/restlet/engine/application/Decoder.java index 126d7251e1..9cc04dec9d 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/Decoder.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/Decoder.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import java.util.Iterator; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -16,152 +16,146 @@ import org.restlet.representation.Representation; import org.restlet.routing.Filter; -import java.util.Iterator; - /** * Filter uncompressing entities. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class Decoder extends Filter { - /** - * Indicates if the request entity should be decoded. - */ - private final boolean decodingRequest; - - /** - * Indicates if the response entity should be decoded. - */ - private final boolean decodingResponse; - - /** - * Constructor to only decode request entities before handling. - * - * @param context The context. - */ - public Decoder(Context context) { - this(context, true, false); - } - - /** - * Constructor. - * - * @param context The context. - * @param decodingRequest Indicates if the request entity should be decoded. - * @param decodingResponse Indicates if the response entity should be decoded. - */ - public Decoder(Context context, boolean decodingRequest, boolean decodingResponse) { - super(context); - this.decodingRequest = decodingRequest; - this.decodingResponse = decodingResponse; - } - - /** - * Allows filtering after its handling by the target Restlet. Does nothing by - * default. - * - * @param request The request to filter. - * @param response The response to filter. - */ - @Override - public void afterHandle(Request request, Response response) { - // Check if decoding of the response entity is needed - if (isDecodingResponse() && canDecode(response.getEntity())) { - response.setEntity(decode(response.getEntity())); - } - } - - /** - * Allows filtering before its handling by the target Restlet. Does nothing by - * default. - * - * @param request The request to filter. - * @param response The response to filter. - * @return The continuation status. - */ - @Override - public int beforeHandle(Request request, Response response) { - // Check if decoding of the request entity is needed - if (isDecodingRequest() && canDecode(request.getEntity())) { - request.setEntity(decode(request.getEntity())); - } - - return CONTINUE; - } - - /** - * Indicates if a representation can be decoded. - * - * @param representation The representation to test. - * @return True if the call can be decoded. - */ - public boolean canDecode(Representation representation) { - // Test the existence of the representation and that at least an - // encoding applies. - boolean result = (representation != null) && (!representation.getEncodings().isEmpty()); - - if (result) { - boolean found = false; - - for (final Iterator iter = representation.getEncodings().iterator(); !found && iter.hasNext();) { - found = (!iter.next().equals(Encoding.IDENTITY)); - } - - result = found; - } - return result; - } - - /** - * Decodes a given representation if its encodings are supported by NRE. - * - * @param representation The representation to encode. - * @return The decoded representation or the original one if the encoding isn't - * supported by NRE. - */ - public Representation decode(Representation representation) { - Representation result = representation; - - // Check if all encodings of the representation are supported in order - // to avoid the creation of a useless decodeRepresentation object. - // False if an encoding is not supported - boolean supported = true; - // True if all representation's encodings are IDENTITY - boolean identityEncodings = true; - for (final Iterator iter = representation.getEncodings().iterator(); supported && iter.hasNext();) { - final Encoding encoding = iter.next(); - supported = DecodeRepresentation.getSupportedEncodings().contains(encoding); - identityEncodings &= encoding.equals(Encoding.IDENTITY); - } - - if (supported && !identityEncodings) { - result = new DecodeRepresentation(representation); - } - - return result; - } - - /** - * Indicates if the request entity should be decoded. - * - * @return True if the request entity should be decoded. - */ - public boolean isDecodingRequest() { - return this.decodingRequest; - } - - /** - * Indicates if the response entity should be decoded. - * - * @return True if the response entity should be decoded. - */ - public boolean isDecodingResponse() { - return this.decodingResponse; - } - + /** Indicates if the request entity should be decoded. */ + private final boolean decodingRequest; + + /** Indicates if the response entity should be decoded. */ + private final boolean decodingResponse; + + /** + * Constructor to only decode request entities before handling. + * + * @param context The context. + */ + public Decoder(Context context) { + this(context, true, false); + } + + /** + * Constructor. + * + * @param context The context. + * @param decodingRequest Indicates if the request entity should be decoded. + * @param decodingResponse Indicates if the response entity should be decoded. + */ + public Decoder(Context context, boolean decodingRequest, boolean decodingResponse) { + super(context); + this.decodingRequest = decodingRequest; + this.decodingResponse = decodingResponse; + } + + /** + * Allows filtering of a request and a response after the target Restlet handled the request. + * Does nothing by default. + * + * @param request The request to filter. + * @param response The response to filter. + */ + @Override + public void afterHandle(Request request, Response response) { + // Check if decoding of the response entity is needed + if (isDecodingResponse() && canDecode(response.getEntity())) { + response.setEntity(decode(response.getEntity())); + } + } + + /** + * Allows filtering before its handling by the target Restlet. Does nothing by default. + * + * @param request The request to filter. + * @param response The response to filter. + * @return The continuation status. + */ + @Override + public int beforeHandle(Request request, Response response) { + // Check if decoding of the request entity is needed + if (isDecodingRequest() && canDecode(request.getEntity())) { + request.setEntity(decode(request.getEntity())); + } + + return CONTINUE; + } + + /** + * Indicates if a representation can be decoded. + * + * @param representation The representation to test. + * @return True if the call can be decoded. + */ + public boolean canDecode(Representation representation) { + // Test the existence of the representation and that at least an + // encoding applies. + boolean result = (representation != null) && (!representation.getEncodings().isEmpty()); + + if (result) { + boolean found = false; + + for (final Iterator iter = representation.getEncodings().iterator(); + !found && iter.hasNext(); ) { + found = (!iter.next().equals(Encoding.IDENTITY)); + } + + result = found; + } + return result; + } + + /** + * Decodes a given representation if its encodings are supported by NRE. + * + * @param representation The representation to encode. + * @return The decoded representation or the original one if the encoding isn't supported by + * NRE. + */ + public Representation decode(Representation representation) { + Representation result = representation; + + // Check if all encodings of the representation are supported in order + // to avoid the creation of a useless decodeRepresentation object. + // False if an encoding is not supported + boolean supported = true; + // True if all representation's encodings are IDENTITY + boolean identityEncodings = true; + for (final Iterator iter = representation.getEncodings().iterator(); + supported && iter.hasNext(); ) { + final Encoding encoding = iter.next(); + supported = DecodeRepresentation.getSupportedEncodings().contains(encoding); + identityEncodings &= encoding.equals(Encoding.IDENTITY); + } + + if (supported && !identityEncodings) { + result = new DecodeRepresentation(representation); + } + + return result; + } + + /** + * Indicates if the request entity should be decoded. + * + * @return True if the request entity should be decoded. + */ + public boolean isDecodingRequest() { + return this.decodingRequest; + } + + /** + * Indicates if the response entity should be decoded. + * + * @return True if the response entity should be decoded. + */ + public boolean isDecodingResponse() { + return this.decodingResponse; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/EncodeRepresentation.java b/org.restlet/src/main/java/org/restlet/engine/application/EncodeRepresentation.java index 6789147d26..c8ecd54c3e 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/EncodeRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/EncodeRepresentation.java @@ -1,263 +1,217 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; -import org.restlet.data.Disposition; -import org.restlet.data.Encoding; -import org.restlet.engine.io.IoUtils; -import org.restlet.representation.Representation; -import org.restlet.util.WrapperList; -import org.restlet.util.WrapperRepresentation; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; import java.util.List; -import java.util.zip.*; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.restlet.data.Disposition; +import org.restlet.data.Encoding; +import org.restlet.engine.io.IoUtils; +import org.restlet.representation.Representation; +import org.restlet.util.NonNullItemsList; +import org.restlet.util.WrapperRepresentation; /** * Content that encodes a wrapped content. Allows to apply only one encoding. - * + * * @author Jerome Louvel */ public class EncodeRepresentation extends WrapperRepresentation { - /** - * Returns the list of supported encodings. - * - * @return The list of supported encodings. - */ - public static List getSupportedEncodings() { - return Arrays.asList(Encoding.GZIP, Encoding.DEFLATE, Encoding.DEFLATE_NOWRAP, Encoding.ZIP, - Encoding.IDENTITY); - } - - /** Indicates if the encoding can happen. */ - private volatile boolean canEncode; - - /** The encoding to apply. */ - private volatile Encoding encoding; - - /** The applied encodings. */ - private volatile List encodings; - - /** - * Constructor. - * - * @param encoding Encoder algorithm. - * @param wrappedRepresentation The wrapped representation. - */ - public EncodeRepresentation(Encoding encoding, Representation wrappedRepresentation) { - super(wrappedRepresentation); - this.canEncode = getSupportedEncodings().contains(encoding); - this.encodings = null; - this.encoding = encoding; - } - - /** - * Indicates if the encoding can happen. - * - * @return True if the encoding can happen. - */ - public boolean canEncode() { - return this.canEncode; - } - - /** - * Returns the available size in bytes of the encoded representation if known, - * UNKNOWN_SIZE (-1) otherwise. - * - * @return The available size in bytes if known, UNKNOWN_SIZE (-1) otherwise. - */ - @Override - public long getAvailableSize() { - long result = UNKNOWN_SIZE; - - if (canEncode()) { - if (this.encoding.equals(Encoding.IDENTITY)) { - result = getWrappedRepresentation().getAvailableSize(); - } - } else { - result = getWrappedRepresentation().getAvailableSize(); - } - - return result; - } - - /** - * Returns the applied encodings. - * - * @return The applied encodings. - */ - @Override - public List getEncodings() { - if (this.encodings == null) { - this.encodings = new WrapperList() { - - @Override - public boolean add(Encoding element) { - if (element == null) { - throw new IllegalArgumentException("Cannot add a null encoding."); - } - - return super.add(element); - } - - @Override - public void add(int index, Encoding element) { - if (element == null) { - throw new IllegalArgumentException("Cannot add a null encoding."); - } - - super.add(index, element); - } - - @Override - public boolean addAll(Collection elements) { - boolean addNull = (elements == null); - if (!addNull) { - for (final Iterator iterator = elements.iterator(); !addNull - && iterator.hasNext();) { - addNull = (iterator.next() == null); - } - } - if (addNull) { - throw new IllegalArgumentException("Cannot add a null encoding."); - } - - return super.addAll(elements); - } - - @Override - public boolean addAll(int index, Collection elements) { - boolean addNull = (elements == null); - if (!addNull) { - for (final Iterator iterator = elements.iterator(); !addNull - && iterator.hasNext();) { - addNull = (iterator.next() == null); - } - } - if (addNull) { - throw new IllegalArgumentException("Cannot add a null encoding."); - } - - return super.addAll(index, elements); - } - }; - this.encodings.addAll(getWrappedRepresentation().getEncodings()); - if (canEncode()) { - this.encodings.add(this.encoding); - } - } - return this.encodings; - } - - @Override - public Reader getReader() throws IOException { - if (canEncode()) { - return IoUtils.getReader(getStream(), getCharacterSet()); - } else { - return getWrappedRepresentation().getReader(); - } - } - - /** - * Returns the size in bytes of the encoded representation if known, - * UNKNOWN_SIZE (-1) otherwise. - * - * @return The size in bytes if known, UNKNOWN_SIZE (-1) otherwise. - */ - @Override - public long getSize() { - long result = UNKNOWN_SIZE; - - if (canEncode()) { - if (this.encoding.equals(Encoding.IDENTITY)) { - result = getWrappedRepresentation().getSize(); - } - } else { - result = getWrappedRepresentation().getSize(); - } - - return result; - } - - @Override - public InputStream getStream() throws IOException { - if (canEncode()) { - return IoUtils.getStream(this); - } else { - return getWrappedRepresentation().getStream(); - } - } - - @Override - public String getText() throws IOException { - if (canEncode()) { - return IoUtils.toString(getStream(), getCharacterSet()); - } else { - return getWrappedRepresentation().getText(); - } - } - - @Override - public void write(OutputStream outputStream) throws IOException { - if (canEncode()) { - DeflaterOutputStream encoderOutputStream = null; - - if (this.encoding.equals(Encoding.GZIP)) { - encoderOutputStream = new GZIPOutputStream(outputStream); - } else if (this.encoding.equals(Encoding.DEFLATE)) { - encoderOutputStream = new DeflaterOutputStream(outputStream); - } else if (this.encoding.equals(Encoding.DEFLATE_NOWRAP)) { - encoderOutputStream = new DeflaterOutputStream(outputStream, - new Deflater(Deflater.DEFAULT_COMPRESSION, true)); - } else if (this.encoding.equals(Encoding.ZIP)) { - final ZipOutputStream stream = new ZipOutputStream(outputStream); - String name = "entry"; - - if (getWrappedRepresentation().getDisposition() != null) { - name = getWrappedRepresentation().getDisposition().getParameters() - .getFirstValue(Disposition.NAME_FILENAME, true, name); - } - - stream.putNextEntry(new ZipEntry(name)); - encoderOutputStream = stream; - } else if (this.encoding.equals(Encoding.IDENTITY)) { - // Encoder unnecessary for identity encoding - } - - if (encoderOutputStream != null) { - getWrappedRepresentation().write(encoderOutputStream); - encoderOutputStream.flush(); - encoderOutputStream.finish(); - } else { - getWrappedRepresentation().write(outputStream); - } - } else { - getWrappedRepresentation().write(outputStream); - } - } - - @Override - public void write(java.io.Writer writer) throws IOException { - if (canEncode()) { - OutputStream os = IoUtils.getStream(writer, getCharacterSet()); - write(os); - os.flush(); - } else { - getWrappedRepresentation().write(writer); - } - } - + /** + * Returns the list of supported encodings. + * + * @return The list of supported encodings. + */ + public static List getSupportedEncodings() { + return Arrays.asList( + Encoding.GZIP, + Encoding.DEFLATE, + Encoding.DEFLATE_NOWRAP, + Encoding.ZIP, + Encoding.IDENTITY); + } + + /** Indicates if the encoding can happen. */ + private final boolean canEncode; + + /** The encoding to apply. */ + private volatile Encoding encoding; + + /** The applied encodings. */ + private volatile List encodings; + + /** + * Constructor. + * + * @param encoding Encoder algorithm. + * @param wrappedRepresentation The wrapped representation. + */ + public EncodeRepresentation(Encoding encoding, Representation wrappedRepresentation) { + super(wrappedRepresentation); + this.canEncode = getSupportedEncodings().contains(encoding); + this.encodings = null; + this.encoding = encoding; + } + + /** + * Indicates if the encoding can happen. + * + * @return True if the encoding can happen. + */ + public boolean canEncode() { + return this.canEncode; + } + + /** + * Returns the available size in bytes of the encoded representation if known, UNKNOWN_SIZE (-1) + * otherwise. + * + * @return The available size in bytes if known, UNKNOWN_SIZE (-1) otherwise. + */ + @Override + public long getAvailableSize() { + long result = UNKNOWN_SIZE; + + if (canEncode()) { + if (this.encoding.equals(Encoding.IDENTITY)) { + result = getWrappedRepresentation().getAvailableSize(); + } + } else { + result = getWrappedRepresentation().getAvailableSize(); + } + + return result; + } + + /** + * Returns the applied encodings. + * + * @return The applied encodings. + */ + @Override + public List getEncodings() { + if (this.encodings == null) { + this.encodings = new NonNullItemsList<>("Cannot add a null encoding."); + this.encodings.addAll(getWrappedRepresentation().getEncodings()); + if (canEncode()) { + this.encodings.add(this.encoding); + } + } + return this.encodings; + } + + @Override + public Reader getReader() throws IOException { + if (canEncode()) { + return IoUtils.getReader(getStream(), getCharacterSet()); + } else { + return getWrappedRepresentation().getReader(); + } + } + + /** + * Returns the size in bytes of the encoded representation if known, UNKNOWN_SIZE (-1) + * otherwise. + * + * @return The size in bytes if known, UNKNOWN_SIZE (-1) otherwise. + */ + @Override + public long getSize() { + long result = UNKNOWN_SIZE; + + if (canEncode()) { + if (this.encoding.equals(Encoding.IDENTITY)) { + result = getWrappedRepresentation().getSize(); + } + } else { + result = getWrappedRepresentation().getSize(); + } + + return result; + } + + @Override + public InputStream getStream() throws IOException { + if (canEncode()) { + return IoUtils.getStream(this); + } else { + return getWrappedRepresentation().getStream(); + } + } + + @Override + public String getText() throws IOException { + if (canEncode()) { + return IoUtils.toString(getStream(), getCharacterSet()); + } else { + return getWrappedRepresentation().getText(); + } + } + + @Override + public void write(OutputStream outputStream) throws IOException { + if (canEncode()) { + DeflaterOutputStream encoderOutputStream = null; + + if (this.encoding.equals(Encoding.GZIP)) { + encoderOutputStream = new GZIPOutputStream(outputStream); + } else if (this.encoding.equals(Encoding.DEFLATE)) { + encoderOutputStream = new DeflaterOutputStream(outputStream); + } else if (this.encoding.equals(Encoding.DEFLATE_NOWRAP)) { + encoderOutputStream = + new DeflaterOutputStream( + outputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); + } else if (this.encoding.equals(Encoding.ZIP)) { + final ZipOutputStream stream = new ZipOutputStream(outputStream); + String name = "entry"; + + if (getWrappedRepresentation().getDisposition() != null) { + name = + getWrappedRepresentation() + .getDisposition() + .getParameters() + .getFirstValue(Disposition.NAME_FILENAME, true, name); + } + + stream.putNextEntry(new ZipEntry(name)); + encoderOutputStream = stream; + } + + if (encoderOutputStream != null) { + getWrappedRepresentation().write(encoderOutputStream); + encoderOutputStream.flush(); + encoderOutputStream.finish(); + } else { + getWrappedRepresentation().write(outputStream); + } + } else { + getWrappedRepresentation().write(outputStream); + } + } + + @Override + public void write(java.io.Writer writer) throws IOException { + if (canEncode()) { + OutputStream os = IoUtils.getStream(writer, getCharacterSet()); + write(os); + os.flush(); + } else { + getWrappedRepresentation().write(writer); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/Encoder.java b/org.restlet/src/main/java/org/restlet/engine/application/Encoder.java index a4736ba441..c2821d9529 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/Encoder.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/Encoder.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import java.util.List; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -19,113 +19,112 @@ import org.restlet.routing.Filter; import org.restlet.service.EncoderService; -import java.util.Iterator; -import java.util.List; - /** - * Filter compressing entities. The best encoding is automatically selected - * based on the preferences of the client and on the encoding supported by NRE: - * GZip, Zip and Deflate.
- * If the {@link org.restlet.representation.Representation} has an unknown size, - * it will always be a candidate for encoding. Candidate representations need to - * respect media type criteria by the lists of accepted and ignored media types. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Filter compressing entities. The best encoding is automatically selected based on the preferences + * of the client and on the encoding supported by NRE: GZip, Zip, and Deflate.
+ * If the {@link org.restlet.representation.Representation} has an unknown size, it will always be a + * candidate for encoding. Candidate representations need to respect media type criteria by the + * lists of accepted and ignored media types. + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Lars Heuer * @author Jerome Louvel */ public class Encoder extends Filter { - /** Indicates if the request entity should be encoded. */ - private final boolean encodingRequest; - - /** Indicates if the response entity should be encoded. */ - private final boolean encodingResponse; - - /** The parent encoder service. */ - private final EncoderService encoderService; - - /** - * Constructor. - * - * @param context The context. - * @param encodingRequest Indicates if the request entities should be encoded. - * @param encodingResponse Indicates if the response entities should be encoded. - * @param encoderService The parent encoder service. - */ - public Encoder(Context context, boolean encodingRequest, boolean encodingResponse, EncoderService encoderService) { - super(context); - this.encodingRequest = encodingRequest; - this.encodingResponse = encodingResponse; - this.encoderService = encoderService; - } - - /** - * Allows filtering after its handling by the target Restlet. Does nothing by - * default. - * - * @param request The request to filter. - * @param response The response to filter. - */ - @Override - public void afterHandle(Request request, Response response) { - // Check if encoding of the response entity is needed - if (isEncodingResponse() && getEncoderService().canEncode(response.getEntity())) { - response.setEntity(encode(request.getClientInfo(), response.getEntity())); - } - } - - /** - * Allows filtering before its handling by the target Restlet. Does nothing by - * default. - * - * @param request The request to filter. - * @param response The response to filter. - * @return The continuation status. - */ - @Override - public int beforeHandle(Request request, Response response) { - // Check if encoding of the request entity is needed - if (isEncodingRequest() && getEncoderService().canEncode(request.getEntity())) { - request.setEntity(encode(request.getClientInfo(), request.getEntity())); - } - - return CONTINUE; - } - - /** - * Encodes a given representation if an encoding is supported by the client. - * - * @param client The client preferences to use. - * @param representation The representation to encode. - * @return The encoded representation or the original one if no encoding - * supported by the client. - */ - public Representation encode(ClientInfo client, Representation representation) { - Representation result = representation; - Encoding bestEncoding = getBestEncoding(client); - - if (bestEncoding != null) { - result = new EncodeRepresentation(bestEncoding, representation); - } - - return result; - } - - /** - * Returns the best supported encoding for a given client. - * - * @param client The client preferences to use. - * @return The best supported encoding for the given call. - */ - public Encoding getBestEncoding(ClientInfo client) { - Encoding bestEncoding = null; - Encoding currentEncoding = null; - Preference currentPref = null; - float bestScore = 0F; + /** Indicates if the request entity should be encoded. */ + private final boolean encodingRequest; + + /** Indicates if the response entity should be encoded. */ + private final boolean encodingResponse; + + /** The parent encoder service. */ + private final EncoderService encoderService; + + /** + * Constructor. + * + * @param context The context. + * @param encodingRequest Indicates if the request entities should be encoded. + * @param encodingResponse Indicates if the response entities should be encoded. + * @param encoderService The parent encoder service. + */ + public Encoder( + Context context, + boolean encodingRequest, + boolean encodingResponse, + EncoderService encoderService) { + super(context); + this.encodingRequest = encodingRequest; + this.encodingResponse = encodingResponse; + this.encoderService = encoderService; + } + + /** + * Allows filtering of a request and a response after the target Restlet handled the request. + * Does nothing by default. + * + * @param request The request to filter. + * @param response The response to filter. + */ + @Override + public void afterHandle(Request request, Response response) { + // Check if encoding of the response entity is needed + if (isEncodingResponse() && getEncoderService().canEncode(response.getEntity())) { + response.setEntity(encode(request.getClientInfo(), response.getEntity())); + } + } + + /** + * Allows filtering before its handling by the target Restlet. Does nothing by default. + * + * @param request The request to filter. + * @param response The response to filter. + * @return The continuation status. + */ + @Override + public int beforeHandle(Request request, Response response) { + // Check if encoding of the request entity is needed + if (isEncodingRequest() && getEncoderService().canEncode(request.getEntity())) { + request.setEntity(encode(request.getClientInfo(), request.getEntity())); + } + + return CONTINUE; + } + + /** + * Encodes a given representation if the client supports an encoding. + * + * @param client The client preferences to use. + * @param representation The representation to encode. + * @return The encoded representation or the original one if no encoding supported by the + * client. + */ + public Representation encode(ClientInfo client, Representation representation) { + Representation result = representation; + Encoding bestEncoding = getBestEncoding(client); + + if (bestEncoding != null) { + result = new EncodeRepresentation(bestEncoding, representation); + } + + return result; + } + + /** + * Returns the best supported encoding for a given client. + * + * @param client The client preferences to use. + * @return The best supported encoding for the given call. + */ + public Encoding getBestEncoding(ClientInfo client) { + Encoding bestEncoding = null; + Encoding currentEncoding = null; + Preference currentPref = null; + float bestScore = 0F; for (Encoding encoding : getSupportedEncodings()) { currentEncoding = encoding; @@ -133,55 +132,52 @@ public Encoding getBestEncoding(ClientInfo client) { for (Preference encodingPreference : client.getAcceptedEncodings()) { currentPref = encodingPreference; - if (currentPref.getMetadata().equals(Encoding.ALL) - || currentPref.getMetadata().equals(currentEncoding)) { - // A match was found, compute its score - if (currentPref.getQuality() > bestScore) { - bestScore = currentPref.getQuality(); - bestEncoding = currentEncoding; - } + if ((currentPref.getMetadata().equals(Encoding.ALL) + || currentPref.getMetadata().equals(currentEncoding)) + && currentPref.getQuality() > bestScore) { + bestScore = currentPref.getQuality(); + bestEncoding = currentEncoding; } } } - return bestEncoding; - } - - /** - * Returns the parent encoder service. - * - * @return The parent encoder service. - */ - public EncoderService getEncoderService() { - return encoderService; - } - - /** - * Returns the list of supported encodings. By default it calls - * {@link EncodeRepresentation#getSupportedEncodings()} static method. - * - * @return The list of supported encodings. - */ - public List getSupportedEncodings() { - return EncodeRepresentation.getSupportedEncodings(); - } - - /** - * Indicates if the request entity should be encoded. - * - * @return True if the request entity should be encoded. - */ - public boolean isEncodingRequest() { - return this.encodingRequest; - } - - /** - * Indicates if the response entity should be encoded. - * - * @return True if the response entity should be encoded. - */ - public boolean isEncodingResponse() { - return this.encodingResponse; - } - + return bestEncoding; + } + + /** + * Returns the parent encoder service. + * + * @return The parent encoder service. + */ + public EncoderService getEncoderService() { + return encoderService; + } + + /** + * Returns the list of supported encodings. By default, it calls {@link + * EncodeRepresentation#getSupportedEncodings()} static method. + * + * @return The list of supported encodings. + */ + public List getSupportedEncodings() { + return EncodeRepresentation.getSupportedEncodings(); + } + + /** + * Indicates if the request entity should be encoded. + * + * @return True if the request entity should be encoded. + */ + public boolean isEncodingRequest() { + return this.encodingRequest; + } + + /** + * Indicates if the response entity should be encoded. + * + * @return True if the response entity should be encoded. + */ + public boolean isEncodingResponse() { + return this.encodingResponse; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/FlexibleConneg.java b/org.restlet/src/main/java/org/restlet/engine/application/FlexibleConneg.java index 49451e3e52..dc7c0dd79e 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/FlexibleConneg.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/FlexibleConneg.java @@ -1,193 +1,212 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; -import org.restlet.Request; -import org.restlet.data.*; -import org.restlet.service.MetadataService; - import java.util.ArrayList; import java.util.List; +import org.restlet.Request; +import org.restlet.data.CharacterSet; +import org.restlet.data.ClientInfo; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; +import org.restlet.data.Preference; +import org.restlet.service.MetadataService; /** - * Content negotiation algorithm that flexibly interprets the content - * negotiation preferences to try to always return a variant even if the client - * preferences don't exactly match. - * + * Content negotiation algorithm that flexibly interprets the content negotiation preferences to try + * to always return a variant even if the client preferences don't exactly match. + * * @author Jerome Louvel */ public class FlexibleConneg extends StrictConneg { - /** The enriched list of character set preferences. */ - private volatile List> characterSetPrefs; - - /** The enriched list of encoding preferences. */ - private volatile List> encodingPrefs; - - /** The enriched list of language preferences. */ - private volatile List> languagePrefs; - - /** The enriched list of media type preferences. */ - private volatile List> mediaTypePrefs; - - /** - * Constructor. - * - * @param request The request including client preferences. - * @param metadataService The metadata service used to get default metadata - * values. - */ - public FlexibleConneg(Request request, MetadataService metadataService) { - super(request, metadataService); - ClientInfo clientInfo = request.getClientInfo(); - - if (clientInfo != null) { - // Get the enriched user preferences - this.languagePrefs = getEnrichedPreferences(clientInfo.getAcceptedLanguages(), - (metadataService == null) ? null : metadataService.getDefaultLanguage(), Language.ALL); - this.mediaTypePrefs = getEnrichedPreferences(clientInfo.getAcceptedMediaTypes(), - (metadataService == null) ? null : metadataService.getDefaultMediaType(), MediaType.ALL); - this.characterSetPrefs = getEnrichedPreferences(clientInfo.getAcceptedCharacterSets(), - (metadataService == null) ? null : metadataService.getDefaultCharacterSet(), CharacterSet.ALL); - this.encodingPrefs = getEnrichedPreferences(clientInfo.getAcceptedEncodings(), - (metadataService == null) ? null : metadataService.getDefaultEncoding(), Encoding.ALL); - } - } - - /** - * Returns true if the metadata can be added. - * - * @param - * @param metadata The metadata to add. - * @param undesired The list of prohibited metadata. - * @return True if the metadata can be added. - */ - protected boolean canAdd(T metadata, List undesired) { - boolean add = true; - if (undesired != null) { - for (T u : undesired) { - if (u.equals(metadata)) { - add = false; - break; - } - } - } - - return add; - } - - /** - * Returns the enriched list of character set preferences. - * - * @return The enriched list of character set preferences. - */ - protected List> getCharacterSetPrefs() { - return characterSetPrefs; - } - - /** - * Returns the enriched list of encoding preferences. - * - * @return The enriched list of encoding preferences. - */ - protected List> getEncodingPrefs() { - return encodingPrefs; - } - - /** - * Returns an enriched list of preferences. Contains the user preferences, - * implied user parent preferences (quality between 0.005 and 0.006), default - * preference (quality of 0.003), default parent preference (quality of 0.002), - * all preference (quality of 0.001).
- *
- * This necessary to compensate the misconfiguration of many browsers which - * don't expose all the metadata actually understood by end users. - * - * @param - * @param userPreferences The user preferences to enrich. - * @param defaultValue The default value. - * @param allValue The ALL value. - * @return The enriched user preferences. - */ - @SuppressWarnings("unchecked") - protected List> getEnrichedPreferences(List> userPreferences, - T defaultValue, T allValue) { - // 0) Add the user preferences - List> result = new ArrayList<>(userPreferences); + /** The enriched list of character set preferences. */ + private volatile List> characterSetPrefs; + + /** The enriched list of encoding preferences. */ + private volatile List> encodingPrefs; + + /** The enriched list of language preferences. */ + private volatile List> languagePrefs; + + /** The enriched list of media type preferences. */ + private volatile List> mediaTypePrefs; + + /** + * Constructor. + * + * @param request The request including client preferences. + * @param metadataService The metadata service used to get default metadata values. + */ + public FlexibleConneg(Request request, MetadataService metadataService) { + super(request, metadataService); + ClientInfo clientInfo = request.getClientInfo(); + + if (clientInfo != null) { + // Get the enriched user preferences + this.languagePrefs = + getEnrichedPreferences( + clientInfo.getAcceptedLanguages(), + (metadataService == null) ? null : metadataService.getDefaultLanguage(), + Language.ALL); + this.mediaTypePrefs = + getEnrichedPreferences( + clientInfo.getAcceptedMediaTypes(), + (metadataService == null) + ? null + : metadataService.getDefaultMediaType(), + MediaType.ALL); + this.characterSetPrefs = + getEnrichedPreferences( + clientInfo.getAcceptedCharacterSets(), + (metadataService == null) + ? null + : metadataService.getDefaultCharacterSet(), + CharacterSet.ALL); + this.encodingPrefs = + getEnrichedPreferences( + clientInfo.getAcceptedEncodings(), + (metadataService == null) ? null : metadataService.getDefaultEncoding(), + Encoding.ALL); + } + } + + /** + * Returns true if the metadata can be added. + * + * @param + * @param metadata The metadata to add. + * @param undesired The list of prohibited metadata. + * @return True if the metadata can be added. + */ + protected boolean canAdd(T metadata, List undesired) { + boolean add = true; + if (undesired != null) { + for (T u : undesired) { + if (u.equals(metadata)) { + add = false; + break; + } + } + } + + return add; + } + + /** + * Returns the enriched list of character set preferences. + * + * @return The enriched list of character set preferences. + */ + @Override + protected List> getCharacterSetPrefs() { + return characterSetPrefs; + } + + /** + * Returns the enriched list of encoding preferences. + * + * @return The enriched list of encoding preferences. + */ + @Override + protected List> getEncodingPrefs() { + return encodingPrefs; + } + + /** + * Returns an enriched list of preferences. Contains the user preferences, implied user parent + * preferences (quality between 0.005 and 0.006), default preference (quality of 0.003), default + * parent preference (quality of 0.002), all preference (quality of 0.001).
+ *
+ * This necessary to compensate the misconfiguration of many browsers which don't expose all the + * metadata actually understood by end users. + * + * @param + * @param userPreferences The user preferences to enrich. + * @param defaultValue The default value. + * @param allValue The 'ALL' value. + * @return The enriched user preferences. + */ + @SuppressWarnings("unchecked") + protected List> getEnrichedPreferences( + List> userPreferences, T defaultValue, T allValue) { + // 0) Add the user preferences + List> result = new ArrayList<>(userPreferences); // 1) List all undesired metadata - List undesired = null; - for (Preference pref : userPreferences) { - if (pref.getQuality() == 0) { - if (undesired == null) { - undesired = new ArrayList(); - } - undesired.add(pref.getMetadata()); - } - } - - // 2) Add the user parent preferences - T parent; - for (int i = 0; i < result.size(); i++) { - Preference userPref = result.get(i); - parent = (T) userPref.getMetadata().getParent(); - - // Add the parent if it is not proscribed. - if (parent != null) { - if (canAdd(parent, undesired)) { - result.add(new Preference(parent, 0.005f + (0.001f * userPref.getQuality()))); - } - } - } - - // 3) Add the default preference - if (defaultValue != null && canAdd(defaultValue, undesired)) { - Preference defaultPref = new Preference(defaultValue, 0.003f); - result.add(defaultPref); - T defaultParent = (T) defaultValue.getParent(); - - if (defaultParent != null && canAdd(defaultParent, undesired)) { - result.add(new Preference(defaultParent, 0.002f)); - } - } - - // 5) Add "all" preference - for (int i = result.size() - 1; i >= 0; i--) { - // Remove any existing preference - if (result.get(i).getMetadata().equals(allValue)) { - result.remove(i); - } - } - - result.add(new Preference(allValue, 0.001f)); - - // 6) Return the enriched preferences - return result; - } - - /** - * Returns the enriched list of language preferences. - * - * @return The enriched list of language preferences. - */ - protected List> getLanguagePrefs() { - return languagePrefs; - } - - /** - * Returns the enriched list of media type preferences. - * - * @return The enriched list of media type preferences. - */ - protected List> getMediaTypePrefs() { - return mediaTypePrefs; - } - + List undesired = null; + for (Preference pref : userPreferences) { + if (pref.getQuality() == 0) { + if (undesired == null) { + undesired = new ArrayList<>(); + } + undesired.add(pref.getMetadata()); + } + } + + // 2) Add the user parent preferences + T parent; + for (int i = 0; i < result.size(); i++) { + Preference userPref = result.get(i); + parent = (T) userPref.getMetadata().getParent(); + + // Add the parent if it is not proscribed. + if (parent != null && canAdd(parent, undesired)) { + final float quality = 0.005f + (0.001f * userPref.getQuality()); + result.add(new Preference<>(parent, quality)); + } + } + + // 3) Add the default preference + if (defaultValue != null && canAdd(defaultValue, undesired)) { + Preference defaultPref = new Preference<>(defaultValue, 0.003f); + result.add(defaultPref); + T defaultParent = (T) defaultValue.getParent(); + + if (defaultParent != null && canAdd(defaultParent, undesired)) { + result.add(new Preference<>(defaultParent, 0.002f)); + } + } + + // 5) Add "all" preference + for (int i = result.size() - 1; i >= 0; i--) { + // Remove any existing preference + if (result.get(i).getMetadata().equals(allValue)) { + result.remove(i); + } + } + + result.add(new Preference<>(allValue, 0.001f)); + + // 6) Return the enriched preferences + return result; + } + + /** + * Returns the enriched list of language preferences. + * + * @return The enriched list of language preferences. + */ + @Override + protected List> getLanguagePrefs() { + return languagePrefs; + } + + /** + * Returns the enriched list of media type preferences. + * + * @return The enriched list of media type preferences. + */ + @Override + protected List> getMediaTypePrefs() { + return mediaTypePrefs; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/MetadataExtension.java b/org.restlet/src/main/java/org/restlet/engine/application/MetadataExtension.java index 6ca1189c15..8f621ee592 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/MetadataExtension.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/MetadataExtension.java @@ -1,93 +1,95 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; -import org.restlet.data.*; +import org.restlet.data.CharacterSet; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; /** * Associates an extension name and a metadata. - * + * * @author Alex Milowski (alexml@milowski.org) * @author Thierry Boileau */ public class MetadataExtension { - /** The mapped metadata. */ - private final Metadata metadata; - - /** The name of the extension. */ - private final String name; + /** The mapped metadata. */ + private final Metadata metadata; - /** - * Constructor. - * - * @param name The extension name. - * @param metadata The metadata. - */ - public MetadataExtension(String name, Metadata metadata) { - this.name = name; - this.metadata = metadata; - } + /** The name of the extension. */ + private final String name; - /** - * Returns the character set. - * - * @return the character set. - */ - public CharacterSet getCharacterSet() { - return (CharacterSet) getMetadata(); - } + /** + * Constructor. + * + * @param name The extension name. + * @param metadata The metadata. + */ + public MetadataExtension(String name, Metadata metadata) { + this.name = name; + this.metadata = metadata; + } - /** - * Returns the encoding. - * - * @return the encoding. - */ - public Encoding getEncoding() { - return (Encoding) getMetadata(); - } + /** + * Returns the character set. + * + * @return the character set. + */ + public CharacterSet getCharacterSet() { + return (CharacterSet) getMetadata(); + } - /** - * Returns the language. - * - * @return the language. - */ - public Language getLanguage() { - return (Language) getMetadata(); - } + /** + * Returns the encoding. + * + * @return the encoding. + */ + public Encoding getEncoding() { + return (Encoding) getMetadata(); + } - /** - * Returns the media type. - * - * @return the media type. - */ - public MediaType getMediaType() { - return (MediaType) getMetadata(); - } + /** + * Returns the language. + * + * @return the language. + */ + public Language getLanguage() { + return (Language) getMetadata(); + } - /** - * Returns the metadata. - * - * @return the metadata. - */ - public Metadata getMetadata() { - return this.metadata; - } + /** + * Returns the media type. + * + * @return the media type. + */ + public MediaType getMediaType() { + return (MediaType) getMetadata(); + } - /** - * Returns the extension name. - * - * @return The extension name. - */ - public String getName() { - return this.name; - } + /** + * Returns the metadata. + * + * @return the metadata. + */ + public Metadata getMetadata() { + return this.metadata; + } + /** + * Returns the extension name. + * + * @return The extension name. + */ + public String getName() { + return this.name; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/RangeFilter.java b/org.restlet/src/main/java/org/restlet/engine/application/RangeFilter.java index 12abd5857c..4be55ad554 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/RangeFilter.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/RangeFilter.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import static org.restlet.data.Range.isBytesRange; + import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -17,11 +18,8 @@ import org.restlet.routing.Filter; import org.restlet.service.RangeService; -import static org.restlet.data.Range.isBytesRange; - /** - * Filter that is in charge to check the responses to requests for partial - * content. + * Filter that is in charge to check the responses to requests for partial content. * * @author Thierry Boileau */ @@ -38,59 +36,61 @@ public RangeFilter(Context context) { @Override protected void afterHandle(Request request, Response response) { - if (getRangeService().isEnabled()) { - response.getServerInfo().setAcceptingRanges(true); + response.getServerInfo().setAcceptingRanges(getRangeService().isEnabled()); - if (request.getMethod().isSafe() && response.isEntityAvailable()) { - Range responseRange = response.getEntity().getRange(); - boolean rangedEntity = responseRange != null && isBytesRange(responseRange); + if (!getRangeService().isEnabled()) { + return; + } + if (!request.getMethod().isSafe() || !response.isEntityAvailable()) { + return; + } + if (!response.getStatus().isSuccess()) { + return; + } - if (response.getStatus().isSuccess()) { - if (Status.SUCCESS_PARTIAL_CONTENT.equals(response.getStatus())) { - if (!rangedEntity) { - getLogger().warning( - "When returning a \"206 Partial content\" status, your response entity must be properly ranged."); - } else { - // We assume that the response entity has been properly ranged. - } - } else if (request.getRanges().size() > 1) { // At this time, lists of ranges are not supported. - // Return a server error as this feature isn't supported yet - response.setStatus(Status.SERVER_ERROR_NOT_IMPLEMENTED); - getLogger().warning("Multiple ranges are not supported at this time."); - response.setEntity(null); - } else if (request.getRanges().size() == 1 && - (!request.getConditions().hasSomeRange() - || request.getConditions().getRangeStatus(response.getEntity()).isSuccess())) { - Range requestedRange = request.getRanges().get(0); + Range responseRange = response.getEntity().getRange(); + boolean rangedEntity = responseRange != null && isBytesRange(responseRange); - if ((!response.getEntity().hasKnownSize()) - && ((requestedRange.getIndex() == Range.INDEX_LAST - || requestedRange.getSize() == Range.SIZE_MAX) - && !(requestedRange.getIndex() == Range.INDEX_LAST - && requestedRange.getSize() == Range.SIZE_MAX))) { - // The end index cannot be properly computed - response.setStatus(Status.SERVER_ERROR_INTERNAL); - getLogger().warning( - "Unable to serve this range since at least the end index of the range cannot be computed."); - response.setEntity(null); - } else if (!requestedRange.equals(responseRange)) { - if (rangedEntity) { - getLogger().info( - "The range of the response entity is not equal to the requested one."); - } + if (Status.SUCCESS_PARTIAL_CONTENT.equals(response.getStatus())) { + if (!rangedEntity) { + getLogger() + .warning( + "When returning a \"206 Partial content\" status, your response entity must be properly ranged."); + } + } else if (request.getRanges().size() > 1) { + multipleRangesNotSupported(response); + } else if (request.getRanges().size() == 1 + && (!request.getConditions().hasSomeRange() + || request.getConditions() + .getRangeStatus(response.getEntity()) + .isSuccess())) { + Range requestedRange = request.getRanges().getFirst(); - if (response.getEntity().hasKnownSize() - && requestedRange.getSize() > response.getEntity().getAvailableSize()) { - requestedRange.setSize(Range.SIZE_MAX); - } + if ((!response.getEntity().hasKnownSize()) + && ((requestedRange.getIndex() == Range.INDEX_LAST + || requestedRange.getSize() == Range.SIZE_MAX) + && !(requestedRange.getIndex() == Range.INDEX_LAST + && requestedRange.getSize() == Range.SIZE_MAX))) { + // The end index cannot be properly computed + response.setStatus(Status.SERVER_ERROR_INTERNAL); + getLogger() + .warning( + "Unable to serve this range since at least the end index of the range cannot be computed."); + response.setEntity(null); + } else if (!requestedRange.equals(responseRange)) { + if (rangedEntity) { + getLogger() + .info( + "The range of the response entity is not equal to the requested one."); + } - response.setEntity(new RangeRepresentation(response.getEntity(), requestedRange)); - response.setStatus(Status.SUCCESS_PARTIAL_CONTENT); - } - } - } else { - // Ignore error responses + if (response.getEntity().hasKnownSize() + && requestedRange.getSize() > response.getEntity().getAvailableSize()) { + requestedRange.setSize(Range.SIZE_MAX); } + + response.setEntity(new RangeRepresentation(response.getEntity(), requestedRange)); + response.setStatus(Status.SUCCESS_PARTIAL_CONTENT); } } } @@ -104,4 +104,11 @@ public RangeService getRangeService() { return getApplication().getRangeService(); } + private void multipleRangesNotSupported(final Response response) { + // At this time, lists of ranges are not supported. + // Return a server error as this feature isn't supported yet + getLogger().warning("Multiple ranges are not supported at this time."); + response.setStatus(Status.SERVER_ERROR_NOT_IMPLEMENTED); + response.setEntity(null); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/RangeRepresentation.java b/org.restlet/src/main/java/org/restlet/engine/application/RangeRepresentation.java index c0923b6226..b9bb54bce9 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/RangeRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/RangeRepresentation.java @@ -1,113 +1,119 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.util.Objects; import org.restlet.data.Range; import org.restlet.engine.io.IoUtils; import org.restlet.engine.io.RangeInputStream; import org.restlet.representation.Representation; import org.restlet.util.WrapperRepresentation; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; - /** - * Representation that exposes only a range of the content of a wrapped - * representation. - * + * Representation that exposes only a range of the content of a wrapped representation. + * * @author Jerome Louvel */ public class RangeRepresentation extends WrapperRepresentation { - /** The range specific to this wrapper. */ - private volatile Range range; - - /** - * Constructor. - * - * @param wrappedRepresentation The wrapped representation with a complete - * content. - */ - public RangeRepresentation(Representation wrappedRepresentation) { - this(wrappedRepresentation, null); - } - - /** - * Constructor. - * - * @param wrappedRepresentation The wrapped representation with a complete - * content. - * @param range The range to expose. - */ - public RangeRepresentation(Representation wrappedRepresentation, Range range) { - super(wrappedRepresentation); - if (wrappedRepresentation.getRange() != null) { - throw new IllegalArgumentException("The wrapped representation must not have a range set."); - } - setRange(range); - } - - @Override - public long getAvailableSize() { - return IoUtils.getAvailableSize(this); - } - - /** - * Returns the range specific to this wrapper. The wrapped representation must - * not have a range set itself. - * - * @return The range specific to this wrapper. - */ - @Override - public Range getRange() { - return this.range; - } - - @Override - public Reader getReader() throws IOException { - return IoUtils.getReader(getStream(), getCharacterSet()); - } - - @Override - public InputStream getStream() throws IOException { - return new RangeInputStream(super.getStream(), getSize(), getRange()); - } - - @Override - public String getText() throws IOException { - return IoUtils.getText(this); - } - - /** - * Sets the range specific to this wrapper. This will not affect the wrapped - * representation. - * - * @param range The range specific to this wrapper. - */ - @Override - public void setRange(Range range) { - this.range = range; - } - - @Override - public void write(java.io.Writer writer) throws IOException { - OutputStream os = IoUtils.getStream(writer, getCharacterSet()); - write(os); - os.flush(); - } - - @Override - public void write(OutputStream outputStream) throws IOException { - IoUtils.copy(getStream(), outputStream); - } - + /** The range specific to this wrapper. */ + private volatile Range range; + + /** + * Constructor. + * + * @param wrappedRepresentation The wrapped representation with a complete content. + */ + public RangeRepresentation(Representation wrappedRepresentation) { + this(wrappedRepresentation, null); + } + + /** + * Constructor. + * + * @param wrappedRepresentation The wrapped representation with a complete content. + * @param range The range to expose. + */ + public RangeRepresentation(Representation wrappedRepresentation, Range range) { + super(wrappedRepresentation); + if (wrappedRepresentation.getRange() != null) { + throw new IllegalArgumentException( + "The wrapped representation must not have a range set."); + } + setRange(range); + } + + @Override + public boolean equals(final Object obj) { + return super.equals(obj) + && Objects.equals(getRange(), ((RangeRepresentation) obj).getRange()); + } + + @Override + public long getAvailableSize() { + return IoUtils.getAvailableSize(this); + } + + /** + * Returns the range specific to this wrapper. The wrapped representation must not have a range + * set itself. + * + * @return The range specific to this wrapper. + */ + @Override + public Range getRange() { + return this.range; + } + + @Override + public Reader getReader() throws IOException { + return IoUtils.getReader(getStream(), getCharacterSet()); + } + + @Override + public InputStream getStream() throws IOException { + return new RangeInputStream(super.getStream(), getSize(), getRange()); + } + + @Override + public String getText() throws IOException { + return IoUtils.getText(this); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + /** + * Sets the range specific to this wrapper. This will not affect the wrapped representation. + * + * @param range The range specific to this wrapper. + */ + @Override + public void setRange(Range range) { + this.range = range; + } + + @Override + public void write(java.io.Writer writer) throws IOException { + OutputStream os = IoUtils.getStream(writer, getCharacterSet()); + write(os); + os.flush(); + } + + @Override + public void write(OutputStream outputStream) throws IOException { + IoUtils.copy(getStream(), outputStream); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/StatusFilter.java b/org.restlet/src/main/java/org/restlet/engine/application/StatusFilter.java index 1b8254939d..6cb7531a82 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/StatusFilter.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/StatusFilter.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -16,155 +16,149 @@ import org.restlet.routing.Filter; import org.restlet.service.StatusService; -import java.util.logging.Level; - /** - * Filter associating a response entity based on the status. In order to - * customize the default representation, just subclass this class and override - * the "getRepresentation" method.
- * If any exception occurs during the call handling, a "server internal error" - * status is automatically associated to the call. Of course, you can - * personalize the representation of this error. Also, if no status is set - * (null), then the "success OK" status is assumed. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Filter associating a response entity based on the status. To customize the default + * representation, just subclass this class and override the "getRepresentation" method.
+ * If any exception occurs during the call handling, a "server internal error" status is + * automatically associated with the call. Of course, you can personalize the representation of this + * error. Also, if no status is set (null), then the "success OK" status is assumed. + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class StatusFilter extends Filter { - /** Indicates if existing representations should be overwritten. */ - private volatile boolean overwriting; - - /** The helped status service. */ - private volatile StatusService statusService; - - /** - * Constructor. - * - * @param context The context. - * @param overwriting Indicates whether an existing representation should be - * overwritten. - */ - public StatusFilter(Context context, boolean overwriting) { - super(context); - this.overwriting = overwriting; - this.statusService = null; - } - - /** - * Constructor from a status service. - * - * @param context The context. - * @param statusService The helped status service. - */ - public StatusFilter(Context context, StatusService statusService) { - this(context, statusService.isOverwriting()); - this.statusService = statusService; - } - - /** - * Allows filtering after its handling by the target Restlet. If the status is - * not set, set {@link org.restlet.data.Status#SUCCESS_OK} by default. - * - * If this is an error status, try to get a representation of it with - * {@link org.restlet.service.StatusService#toRepresentation(Status, Request, Response)} - * . - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - public void afterHandle(Request request, Response response) { - // If no status is set, then the "success ok" status is assumed. - if (response.getStatus() == null) { - response.setStatus(Status.SUCCESS_OK); - } - - // Do we need to get a representation for the current status? - try { - if (response.getStatus().isError() && ((response.getEntity() == null) || isOverwriting())) { - response.setEntity(getStatusService().toRepresentation(response.getStatus(), request, response)); - } - } catch (Exception e) { - getLogger().log(Level.WARNING, "Unable to get the custom status representation", e); - } - } - - /** - * Handles the call by distributing it to the next Restlet. If a throwable is - * caught, the - * {@link org.restlet.service.StatusService#toStatus(Throwable, Request, Response)} - * method is invoked. - * - * @param request The request to handle. - * @param response The response to update. - * @return The continuation status. - */ - @Override - protected int doHandle(Request request, Response response) { - // Normally handle the call - try { - super.doHandle(request, response); - } catch (Throwable throwable) { - Status status = getStatusService().toStatus(throwable, request, response); - - final Level level; - if (status.isServerError()) { - level = Level.WARNING; - } else if (status.isConnectorError()) { - level = Level.INFO; - } else if (status.isClientError()) { - level = Level.FINE; - } else { - level = Level.FINE; - } - getLogger().log(level, "Exception or error caught by status service", throwable); - - if (response != null) { - response.setStatus(status); - } - } - - return CONTINUE; - } - - /** - * Returns the helped status service. - * - * @return The helped status service. - */ - public StatusService getStatusService() { - return statusService; - } - - /** - * Indicates if existing representations should be overwritten. - * - * @return True if existing representations should be overwritten. - */ - public boolean isOverwriting() { - return overwriting; - } - - /** - * Indicates if existing representations should be overwritten. - * - * @param overwriting True if existing representations should be overwritten. - */ - public void setOverwriting(boolean overwriting) { - this.overwriting = overwriting; - } - - /** - * Sets the helped status service. - * - * @param statusService The helped status service. - */ - public void setStatusService(StatusService statusService) { - this.statusService = statusService; - } - + /** Indicates if existing representations should be overwritten. */ + private volatile boolean overwriting; + + /** The helped status service. */ + private volatile StatusService statusService; + + /** + * Constructor. + * + * @param context The context. + * @param overwriting Indicates whether an existing representation should be overwritten. + */ + public StatusFilter(Context context, boolean overwriting) { + super(context); + this.overwriting = overwriting; + this.statusService = null; + } + + /** + * Constructor from a status service. + * + * @param context The context. + * @param statusService The helped status service. + */ + public StatusFilter(Context context, StatusService statusService) { + this(context, statusService.isOverwriting()); + this.statusService = statusService; + } + + /** + * Allows filtering after its handling by the target Restlet. If the status is not set, set + * {@link org.restlet.data.Status#SUCCESS_OK} by default. + * + *

If this is an error status, try to get a representation of it with {@link + * org.restlet.service.StatusService#toRepresentation(Status, Request, Response)} . + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public void afterHandle(Request request, Response response) { + // If no status is set, then the "success ok" status is assumed. + if (response.getStatus() == null) { + response.setStatus(Status.SUCCESS_OK); + } + + // Do we need to get a representation for the current status? + try { + if (response.getStatus().isError() + && ((response.getEntity() == null) || isOverwriting())) { + response.setEntity( + getStatusService() + .toRepresentation(response.getStatus(), request, response)); + } + } catch (Exception e) { + getLogger().log(Level.WARNING, "Unable to get the custom status representation", e); + } + } + + /** + * Handles the call by distributing it to the next Restlet. If a throwable is caught, the {@link + * org.restlet.service.StatusService#toStatus(Throwable, Request, Response)} method is invoked. + * + * @param request The request to handle. + * @param response The response to update. + * @return The continuation status. + */ + @Override + protected int doHandle(Request request, Response response) { + // Normally handle the call + try { + super.doHandle(request, response); + } catch (Exception exception) { + Status status = getStatusService().toStatus(exception, request, response); + + final Level level; + if (status.isServerError()) { + level = Level.WARNING; + } else if (status.isConnectorError()) { + level = Level.INFO; + } else if (status.isClientError()) { + level = Level.FINE; + } else { + level = Level.FINE; + } + getLogger().log(level, "Exception or error caught by status service", exception); + + if (response != null) { + response.setStatus(status); + } + } + + return CONTINUE; + } + + /** + * Returns the helped status service. + * + * @return The helped status service. + */ + public StatusService getStatusService() { + return statusService; + } + + /** + * Indicates if existing representations should be overwritten. + * + * @return True if existing representations should be overwritten. + */ + public boolean isOverwriting() { + return overwriting; + } + + /** + * Indicates if existing representations should be overwritten. + * + * @param overwriting True if existing representations should be overwritten. + */ + public void setOverwriting(boolean overwriting) { + this.overwriting = overwriting; + } + + /** + * Sets the helped status service. + * + * @param statusService The helped status service. + */ + public void setStatusService(StatusService statusService) { + this.statusService = statusService; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/StatusInfo.java b/org.restlet/src/main/java/org/restlet/engine/application/StatusInfo.java index 42d6f1762a..8c2dc59dd7 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/StatusInfo.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/StatusInfo.java @@ -1,202 +1,205 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; -import org.restlet.data.Status; - import java.io.Serializable; +import org.restlet.data.Status; /** * Representation of a {@link Status}. - * + * * @author Manuel Boillod */ public class StatusInfo implements Serializable { - private static final long serialVersionUID = 1L; - - /** The specification code. */ - private volatile int code; - - /** The email address of the administrator to contact in case of error. */ - private volatile String contactEmail; - - /** The longer description. */ - private volatile String description; - - /** The home URI to propose in case of error. */ - private volatile String homeRef; - - /** The short reason phrase. */ - private volatile String reasonPhrase; - - /** The URI of the specification describing the method. */ - private volatile String uri; - - /** - * Empty Constructor - */ - public StatusInfo() { - } - - /** - * Constructor. - * - * @param code The specification code. - * @param description The longer description. - * @param reasonPhrase The short reason phrase. - */ - public StatusInfo(int code, String description, String reasonPhrase) { - this(code, description, reasonPhrase, null, null, null); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param description The longer description. - * @param reasonPhrase The short reason phrase. - * @param uri The URI of the specification describing the method. - * @param contactEmail The email address of the administrator to contact in case - * of error. - * @param homeRef The home URI to propose in case of error. - */ - public StatusInfo(int code, String description, String reasonPhrase, String uri, String contactEmail, - String homeRef) { - super(); - this.code = code; - this.description = description; - this.reasonPhrase = reasonPhrase; - this.uri = uri; - this.contactEmail = contactEmail; - this.homeRef = homeRef; - } - - /** - * Constructor. - * - * @param status The represented status. - */ - public StatusInfo(Status status) { - this(status, null, null); - } - - /** - * Constructor. - * - * @param status The represented status. - * @param contactEmail The email address of the administrator to contact in case - * of error. - * @param homeRef The home URI to propose in case of error. - */ - public StatusInfo(Status status, String contactEmail, String homeRef) { - this(status.getCode(), status.getDescription(), status.getReasonPhrase(), status.getUri(), contactEmail, - homeRef); - } - - /** - * Returns the code of the status. - * - * @return The code of the status. - */ - public int getCode() { - return code; - } - - /** - * Returns the email address of the administrator to contact in case of error. - * - * @return The email address. - */ - public String getContactEmail() { - return contactEmail; - } - - /** - * Returns the description of the status. - * - * @return The description of the status. - */ - public String getDescription() { - return description; - } - - /** - * Returns the home URI to propose in case of error. - * - * @return The home URI. - */ - public String getHomeRef() { - return homeRef; - } - - /** - * Returns the short description of the status. - * - * @return The short description of the status. - */ - public String getReasonPhrase() { - return reasonPhrase; - } - - /** - * Returns the URI of the specification describing the status. - * - * @return The URI of the specification describing the status. - */ - public String getUri() { - return this.uri; - } - - /** - * Sets the code of the status. - * - * @param code The code of the status. - */ - public void setCode(int code) { - this.code = code; - } - - /** - * Sets the email address of the administrator to contact in case of error. - * - * @param email The email address. - */ - public void setContactEmail(String email) { - this.contactEmail = email; - } - - /** - * Sets the description of the status. - * - * @param description The description of the status. - */ - public void setDescription(String description) { - this.description = description; - } - - /** - * Sets the home URI to propose in case of error. - * - * @param homeRef The home URI. - */ - public void setHomeRef(String homeRef) { - this.homeRef = homeRef; - } - - /** - * Sets the short description of the status. - * - * @param reasonPhrase The short description of the status. - */ - public void setReasonPhrase(String reasonPhrase) { - this.reasonPhrase = reasonPhrase; - } + private static final long serialVersionUID = 1L; + + /** The specification code. */ + private volatile int code; + + /** The email address of the administrator to contact in case of error. */ + private volatile String contactEmail; + + /** The longer description. */ + private volatile String description; + + /** The home URI to propose in case of error. */ + private volatile String homeRef; + + /** The short reason phrase. */ + private volatile String reasonPhrase; + + /** The URI of the specification describing the method. */ + private volatile String uri; + + /** Empty Constructor */ + public StatusInfo() {} + + /** + * Constructor. + * + * @param code The specification code. + * @param description The longer description. + * @param reasonPhrase The short reason phrase. + */ + public StatusInfo(int code, String description, String reasonPhrase) { + this(code, description, reasonPhrase, null, null, null); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param description The longer description. + * @param reasonPhrase The short reason phrase. + * @param uri The URI of the specification describing the method. + * @param contactEmail The email address of the administrator to contact in case of error. + * @param homeRef The home URI to propose in case of error. + */ + public StatusInfo( + int code, + String description, + String reasonPhrase, + String uri, + String contactEmail, + String homeRef) { + super(); + this.code = code; + this.description = description; + this.reasonPhrase = reasonPhrase; + this.uri = uri; + this.contactEmail = contactEmail; + this.homeRef = homeRef; + } + + /** + * Constructor. + * + * @param status The represented status. + */ + public StatusInfo(Status status) { + this(status, null, null); + } + + /** + * Constructor. + * + * @param status The represented status. + * @param contactEmail The email address of the administrator to contact in case of error. + * @param homeRef The home URI to propose in case of error. + */ + public StatusInfo(Status status, String contactEmail, String homeRef) { + this( + status.getCode(), + status.getDescription(), + status.getReasonPhrase(), + status.getUri(), + contactEmail, + homeRef); + } + + /** + * Returns the code of the status. + * + * @return The code of the status. + */ + public int getCode() { + return code; + } + + /** + * Returns the email address of the administrator to contact in case of error. + * + * @return The email address. + */ + public String getContactEmail() { + return contactEmail; + } + + /** + * Returns the description of the status. + * + * @return The description of the status. + */ + public String getDescription() { + return description; + } + + /** + * Returns the home URI to propose in case of error. + * + * @return The home URI. + */ + public String getHomeRef() { + return homeRef; + } + + /** + * Returns the short description of the status. + * + * @return The short description of the status. + */ + public String getReasonPhrase() { + return reasonPhrase; + } + + /** + * Returns the URI of the specification describing the status. + * + * @return The URI of the specification describing the status. + */ + public String getUri() { + return this.uri; + } + + /** + * Sets the code of the status. + * + * @param code The code of the status. + */ + public void setCode(int code) { + this.code = code; + } + + /** + * Sets the email address of the administrator to contact in case of error. + * + * @param email The email address. + */ + public void setContactEmail(String email) { + this.contactEmail = email; + } + + /** + * Sets the description of the status. + * + * @param description The description of the status. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Sets the home URI to propose in case of error. + * + * @param homeRef The home URI. + */ + public void setHomeRef(String homeRef) { + this.homeRef = homeRef; + } + + /** + * Sets the short description of the status. + * + * @param reasonPhrase The short description of the status. + */ + public void setReasonPhrase(String reasonPhrase) { + this.reasonPhrase = reasonPhrase; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/StrictConneg.java b/org.restlet/src/main/java/org/restlet/engine/application/StrictConneg.java index b7440c32b9..4b6a06934a 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/StrictConneg.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/StrictConneg.java @@ -1,327 +1,341 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; -import org.restlet.data.*; +import org.restlet.data.CharacterSet; +import org.restlet.data.Encoding; +import org.restlet.data.Form; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; +import org.restlet.data.Parameter; +import org.restlet.data.Preference; import org.restlet.engine.resource.MethodAnnotationInfo; import org.restlet.engine.resource.VariantInfo; import org.restlet.representation.Variant; import org.restlet.service.MetadataService; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.logging.Level; - /** - * Content negotiation algorithm that strictly interprets the content - * negotiation preferences. - * + * Content negotiation algorithm that strictly interprets the content negotiation preferences. + * * @author Jerome Louvel */ public class StrictConneg extends Conneg { - /** - * Constructor. - * - * @param request The request including client preferences. - * @param metadataService The metadata service used to get default metadata - * values. - */ - public StrictConneg(Request request, MetadataService metadataService) { - super(request, metadataService); - } - - /** - * Returns the enriched list of character set preferences. - * - * @return The enriched list of character set preferences. - */ - protected List> getCharacterSetPrefs() { - return getRequest().getClientInfo().getAcceptedCharacterSets(); - } - - /** - * Returns the enriched list of encoding preferences. - * - * @return The enriched list of encoding preferences. - */ - protected List> getEncodingPrefs() { - return getRequest().getClientInfo().getAcceptedEncodings(); - } - - /** - * Returns the enriched list of language preferences. - * - * @return The enriched list of language preferences. - */ - protected List> getLanguagePrefs() { - return getRequest().getClientInfo().getAcceptedLanguages(); - } - - /** - * Returns the enriched list of media type preferences. - * - * @return The enriched list of media type preferences. - */ - protected List> getMediaTypePrefs() { - return getRequest().getClientInfo().getAcceptedMediaTypes(); - } - - /** - * Scores the annotation descriptor. By default, it assess the quality of the - * query parameters with the URI query constraint defined in the annotation - * value if any. - * - * @param annotation The annotation descriptor to score. - * @return The annotation descriptor score. - */ - protected float scoreAnnotation(MethodAnnotationInfo annotation) { - if (annotation == null) { - return 0.0F; - } - - float score = doScoreAnnotation(annotation); - - if (Context.getCurrentLogger().isLoggable(Level.FINE)) { - Context.getCurrentLogger().fine("Score of annotation \"" + annotation + "\"= " + score); - } - return score; - } - - private float doScoreAnnotation(MethodAnnotationInfo annotation) { - if (annotation.getQuery() == null) { - if ((getRequest().getResourceRef() == null) || (getRequest().getResourceRef().getQuery() == null)) { - // No query filter, but no query provided, average fit - return 0.5F; - } - - // No query filter, but a query provided, lower fit - return 0.25F; - } - - if ((getRequest().getResourceRef() == null) || (getRequest().getResourceRef().getQuery() == null)) { - // Query constraint defined, but no query provided, no fit - return -1.0F; - } - - // Query constraint defined and a query provided, see if fit - Form constraintParams = new Form(annotation.getQuery()); - Form actualParams = getRequest().getResourceRef().getQueryAsForm(); - Set matchedParams = new HashSet(); - Parameter constraintParam; - Parameter actualParam; - - boolean allConstraintsMatched = true; - boolean constraintMatched = false; - - // Verify that each query constraint has been matched - for (int i = 0; allConstraintsMatched && (i < constraintParams.size()); i++) { - constraintParam = constraintParams.get(i); - constraintMatched = false; - - for (int j = 0; !constraintMatched && (j < actualParams.size()); j++) { - actualParam = actualParams.get(j); - - if (constraintParam.getName().equals(actualParam.getName())) { - // Potential match found based on name - if ((constraintParam.getValue() == null) - || constraintParam.getValue().equals(actualParam.getValue())) { - // Actual match found! - constraintMatched = true; - matchedParams.add(actualParam); - } - } - } - - allConstraintsMatched = allConstraintsMatched && constraintMatched; - } - - if (allConstraintsMatched) { - // Test if all actual query parameters matched a constraint, so increase score - if (actualParams.size() == matchedParams.size()) { - // All filter parameters matched, no additional parameter found - return 1.0F; - } - // All filter parameters matched, but additional parameters found - return 0.75F; - } - - return -1.0F; - } - - /** - * Scores a character set relatively to enriched client preferences. - * - * @param characterSet The character set to score. - * @return The score. - */ - public float scoreCharacterSet(CharacterSet characterSet) { - return scoreMetadata(characterSet, getCharacterSetPrefs()); - } - - /** - * Scores encodings relatively to enriched client preferences. - * - * @param encodings The encodings to score. - * @return The score. - */ - public float scoreEncodings(List encodings) { - return scoreMetadata(encodings, getEncodingPrefs()); - } - - /** - * Scores languages relatively to enriched client preferences. - * - * @param languages The languages to score. - * @return The score. - */ - public float scoreLanguages(List languages) { - return scoreMetadata(languages, getLanguagePrefs()); - } - - /** - * Scores a media type relatively to enriched client preferences. - * - * @param mediaType The media type to score. - * @return The score. - */ - public float scoreMediaType(MediaType mediaType) { - float result = -1.0F; - float current; - - if (mediaType != null) { - for (Preference pref : getMediaTypePrefs()) { - if (pref.getMetadata().includes(mediaType, false)) { - current = pref.getQuality(); - } else { - current = -1.0F; - } - - if (current > result) { - result = current; - } - } - } else { - result = 0.0F; - } - - return result; - } - - /** - * Scores a list of metadata relatively to enriched client preferences. - * - * @param metadataList The list of metadata to score. - * @return The score. - */ - protected float scoreMetadata(List metadataList, List> prefs) { - float result = -1.0F; - float current; - - if ((metadataList != null) && !metadataList.isEmpty()) { - for (Preference pref : prefs) { - for (T metadata : metadataList) { - if (pref.getMetadata().includes(metadata)) { - current = pref.getQuality(); - } else { - current = -1.0F; - } - - if (current > result) { - result = current; - } - } - } - } else { - result = 0.0F; - } - - return result; - } - - /** - * Scores a metadata relatively to enriched client preferences. - * - * @param metadata The metadata to score. - * @return The score. - */ - protected float scoreMetadata(T metadata, List> prefs) { - float result = -1.0F; - float current; - - if (metadata != null) { - for (Preference pref : prefs) { - if (pref.getMetadata().includes(metadata)) { - current = pref.getQuality(); - } else { - current = -1.0F; - } - - if (current > result) { - result = current; - } - } - } else { - result = 0.0F; - } - - return result; - } - - /** - * Scores a variant relatively to enriched client preferences. The language has - * a weight of 4, the media type 3, the character set 2 and the encoding 1. - * - * @param variant The variant to score. - * @return The enriched client preferences. - */ - public float scoreVariant(Variant variant) { - float result = -1.0F; - float languageScore = scoreLanguages(variant.getLanguages()); - - if (languageScore != -1.0F) { - float mediaTypeScore = scoreMediaType(variant.getMediaType()); - - if (mediaTypeScore != -1.0F) { - float characterSetScore = scoreCharacterSet(variant.getCharacterSet()); - - if (characterSetScore != -1.0F) { - float encodingScore = scoreEncodings(variant.getEncodings()); - - if (encodingScore != -1.0F) { - if (variant instanceof VariantInfo) { - float annotationScore = scoreAnnotation(((VariantInfo) variant).getAnnotationInfo()); - - // Return the weighted average score - result = ((languageScore * 4.0F) + (mediaTypeScore * 3.0F) + (characterSetScore * 2.0F) - + (encodingScore * 1.0F) + (annotationScore * 2.0F)) / 12.0F; - // Take into account the affinity with the input - // entity - result = result * ((VariantInfo) variant).getInputScore(); - } else { - // Return the weighted average score - result = ((languageScore * 4.0F) + (mediaTypeScore * 3.0F) + (characterSetScore * 2.0F) - + (encodingScore * 1.0F)) / 10.0F; - } - } - } - } - } - - if (Context.getCurrentLogger().isLoggable(Level.FINE)) { - Context.getCurrentLogger().fine("Total score of variant \"" + variant + "\"= " + result); - } - - return result; - } + /** + * Constructor. + * + * @param request The request including client preferences. + * @param metadataService The metadata service used to get default metadata values. + */ + public StrictConneg(Request request, MetadataService metadataService) { + super(request, metadataService); + } + + /** + * Returns the enriched list of character set preferences. + * + * @return The enriched list of character set preferences. + */ + protected List> getCharacterSetPrefs() { + return getRequest().getClientInfo().getAcceptedCharacterSets(); + } + + /** + * Returns the enriched list of encoding preferences. + * + * @return The enriched list of encoding preferences. + */ + protected List> getEncodingPrefs() { + return getRequest().getClientInfo().getAcceptedEncodings(); + } + + /** + * Returns the enriched list of language preferences. + * + * @return The enriched list of language preferences. + */ + protected List> getLanguagePrefs() { + return getRequest().getClientInfo().getAcceptedLanguages(); + } + + /** + * Returns the enriched list of media type preferences. + * + * @return The enriched list of media type preferences. + */ + protected List> getMediaTypePrefs() { + return getRequest().getClientInfo().getAcceptedMediaTypes(); + } + + /** + * Scores the annotation descriptor. By default, it assess the quality of the query parameters + * with the URI query constraint defined in the annotation value if any. + * + * @param annotation The annotation descriptor to score. + * @return The annotation descriptor score. + */ + protected float scoreAnnotation(MethodAnnotationInfo annotation) { + if (annotation == null) { + return 0.0F; + } + + float score = doScoreAnnotation(annotation); + + if (Context.getCurrentLogger().isLoggable(Level.FINE)) { + Context.getCurrentLogger().fine("Score of annotation \"" + annotation + "\"= " + score); + } + return score; + } + + private float doScoreAnnotation(MethodAnnotationInfo annotation) { + if (annotation.getQuery() == null) { + if ((getRequest().getResourceRef() == null) + || (getRequest().getResourceRef().getQuery() == null)) { + // No query filter, but no query provided, average fit + return 0.5F; + } + + // No query filter, but a query provided, lower fit + return 0.25F; + } + + if ((getRequest().getResourceRef() == null) + || (getRequest().getResourceRef().getQuery() == null)) { + // Query constraint defined, but no query provided, no fit + return -1.0F; + } + + // Query constraint defined, and a query provided, see if fit + final Form constraintParams = new Form(annotation.getQuery()); + final Form actualParams = getRequest().getResourceRef().getQueryAsForm(); + final Set matchedParams = new HashSet<>(); + + Parameter constraintParam; + Parameter actualParam; + + boolean allConstraintsMatched = true; + boolean constraintMatched; + + // Verify that each query constraint has been matched + for (int i = 0; allConstraintsMatched && (i < constraintParams.size()); i++) { + constraintParam = constraintParams.get(i); + constraintMatched = false; + + for (int j = 0; !constraintMatched && (j < actualParams.size()); j++) { + actualParam = actualParams.get(j); + + if (constraintParam.getName().equals(actualParam.getName()) + && ((constraintParam.getValue() == null) + || constraintParam.getValue().equals(actualParam.getValue()))) { + // Actual match found! + constraintMatched = true; + matchedParams.add(actualParam); + } + } + + allConstraintsMatched = constraintMatched; + } + + if (allConstraintsMatched) { + // Test if all actual query parameters matched a constraint, so increase score + if (actualParams.size() == matchedParams.size()) { + // All filter parameters matched, no additional parameter found + return 1.0F; + } + // All filter parameters matched, but additional parameters found + return 0.75F; + } + + return -1.0F; + } + + /** + * Scores a character set relatively to enriched client preferences. + * + * @param characterSet The character set to score. + * @return The score. + */ + public float scoreCharacterSet(CharacterSet characterSet) { + return scoreMetadata(characterSet, getCharacterSetPrefs()); + } + + /** + * Scores encodings relatively to enriched client preferences. + * + * @param encodings The encodings to score. + * @return The score. + */ + public float scoreEncodings(List encodings) { + return scoreMetadata(encodings, getEncodingPrefs()); + } + + /** + * Scores languages relatively to enriched client preferences. + * + * @param languages The languages to score. + * @return The score. + */ + public float scoreLanguages(List languages) { + return scoreMetadata(languages, getLanguagePrefs()); + } + + /** + * Scores a media type relatively to enriched client preferences. + * + * @param mediaType The media type to score. + * @return The score. + */ + public float scoreMediaType(MediaType mediaType) { + float result = -1.0F; + float current; + + if (mediaType != null) { + for (Preference pref : getMediaTypePrefs()) { + if (pref.getMetadata().includes(mediaType, false)) { + current = pref.getQuality(); + } else { + current = -1.0F; + } + + if (current > result) { + result = current; + } + } + } else { + result = 0.0F; + } + + return result; + } + + /** + * Scores a list of metadata relatively enriched client preferences. + * + * @param metadataList The list of metadata to score. + * @return The score. + */ + protected float scoreMetadata( + List metadataList, List> prefs) { + float result = -1.0F; + float current; + + if ((metadataList != null) && !metadataList.isEmpty()) { + for (Preference pref : prefs) { + for (T metadata : metadataList) { + if (pref.getMetadata().includes(metadata)) { + current = pref.getQuality(); + } else { + current = -1.0F; + } + + if (current > result) { + result = current; + } + } + } + } else { + result = 0.0F; + } + + return result; + } + + /** + * Scores a metadata relatively to enriched client preferences. + * + * @param metadata The metadata to score. + * @return The score. + */ + protected float scoreMetadata(T metadata, List> prefs) { + float result = -1.0F; + float current; + + if (metadata != null) { + for (Preference pref : prefs) { + if (pref.getMetadata().includes(metadata)) { + current = pref.getQuality(); + } else { + current = -1.0F; + } + + if (current > result) { + result = current; + } + } + } else { + result = 0.0F; + } + + return result; + } + + /** + * Scores a variant relatively to enriched client preferences. The language has a weight of 4, + * the media type 3, the character set 2, and the encoding 1. + * + * @param variant The variant to score. + * @return The enriched client preferences. + */ + public float scoreVariant(Variant variant) { + float result = -1.0F; + float languageScore = scoreLanguages(variant.getLanguages()); + + if (languageScore != -1.0F) { + float mediaTypeScore = scoreMediaType(variant.getMediaType()); + + if (mediaTypeScore != -1.0F) { + float characterSetScore = scoreCharacterSet(variant.getCharacterSet()); + + if (characterSetScore != -1.0F) { + float encodingScore = scoreEncodings(variant.getEncodings()); + + if (encodingScore != -1.0F) { + if (variant instanceof VariantInfo variantInfo) { + float annotationScore = + scoreAnnotation(variantInfo.getAnnotationInfo()); + + // Return the weighted average score + result = + ((languageScore * 4.0F) + + (mediaTypeScore * 3.0F) + + (characterSetScore * 2.0F) + + (annotationScore * 2.0F)) + + (encodingScore) / 12.0F; + // Take into account the affinity with the input + // entity + result *= variantInfo.getInputScore(); + } else { + // Return the weighted average score + result = + ((languageScore * 4.0F) + + (mediaTypeScore * 3.0F) + + (characterSetScore * 2.0F) + + (encodingScore)) + / 10.0F; + } + } + } + } + } + + if (Context.getCurrentLogger().isLoggable(Level.FINE)) { + Context.getCurrentLogger() + .fine("Total score of variant \"" + variant + "\"= " + result); + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/TunnelFilter.java b/org.restlet/src/main/java/org/restlet/engine/application/TunnelFilter.java index 69b7a008d9..c410a6fac1 100644 --- a/org.restlet/src/main/java/org/restlet/engine/application/TunnelFilter.java +++ b/org.restlet/src/main/java/org/restlet/engine/application/TunnelFilter.java @@ -1,18 +1,38 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; -import org.restlet.data.*; +import org.restlet.data.CharacterSet; +import org.restlet.data.ClientInfo; +import org.restlet.data.Encoding; +import org.restlet.data.Form; +import org.restlet.data.Header; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; +import org.restlet.data.Method; +import org.restlet.data.Preference; +import org.restlet.data.Reference; import org.restlet.engine.Engine; import org.restlet.engine.header.HeaderConstants; import org.restlet.engine.header.PreferenceReader; @@ -22,558 +42,559 @@ import org.restlet.service.TunnelService; import org.restlet.util.Series; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.*; -import java.util.Map.Entry; - /** - * Filter tunneling browser calls into full REST calls. The request method can - * be changed (via POST requests only) as well as the accepted media types, - * languages, encodings and character sets. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Filter tunneling browser calls into full REST calls. The request method can be changed (via POST + * requests only) as well as the accepted media types, languages, encodings, and character sets. + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class TunnelFilter extends Filter { - /** - * Used to describe the replacement value for an old client preference and for a - * a series of specific agent (i.e., web client) attributes. - * - * @author Thierry Boileau - */ - private static class HeaderReplacer { - - static class Builder { - Map agentAttributes = new HashMap(); - - String newValue; - - String oldValue; - - HeaderReplacer build() { - return new HeaderReplacer(oldValue, newValue, agentAttributes); - } - - void putAgentAttribute(String key, String value) { - agentAttributes.put(key, value); - } - - void setNewValue(String newValue) { - this.newValue = newValue; - } - - void setOldValue(String oldValue) { - this.oldValue = oldValue; - } - } - - /** Agent attributes that must be checked. */ - private final Map agentAttributes; - - /** New header value. */ - private final String headerNew; - - /** Old header value. */ - private final String headerOld; - - HeaderReplacer(String headerOld, String headerNew, Map agentAttributes) { - this.headerOld = headerOld; - this.headerNew = headerNew; - this.agentAttributes = Collections.unmodifiableMap(agentAttributes); - } - - public Map getAgentAttributes() { - return agentAttributes; - } - - public String getHeaderNew() { - return headerNew; - } - - public String getHeaderOld() { - return headerOld; - } - - /** - * Indicates if the current header replacer matches the request attributes. - * - * @param agentAttributes The user agent attributes to match. - * @param headerOld The facultative value of the current's request header - * to match. - * @return true if the given request's attributes match the current header - * replacer. - */ - public boolean matchesConditions(Map agentAttributes, String headerOld) { - // Check the conditions - boolean checked = true; - // Check that the agent properties match the properties - // set by the rule. - for (Iterator> iterator = getAgentAttributes().entrySet().iterator(); checked - && iterator.hasNext();) { - Entry entry = iterator.next(); - String attribute = agentAttributes.get(entry.getKey()); - checked = (attribute != null && attribute.equalsIgnoreCase(entry.getValue())); - } - if (checked && getHeaderOld() != null) { - // If the rule defines an old header value, check that it is the - // same than the user agent's header value. - checked = getHeaderOld().equals(headerOld); - } - return checked; - } - - } - - /** Used to replace accept-encoding header values. */ - private final List acceptEncodingReplacers = getAcceptEncodingReplacers(); - - /** Used to replace accept header values. */ - private final List acceptReplacers = getAcceptReplacers(); - - /** - * Constructor. - * - * @param context The parent context. - */ - public TunnelFilter(Context context) { - super(context); - } - - @Override - public int beforeHandle(Request request, Response response) { - if (getTunnelService().isUserAgentTunnel()) { - processUserAgent(request); - } - - if (getTunnelService().isExtensionsTunnel()) { - processExtensions(request); - } - - if (getTunnelService().isQueryTunnel()) { - processQuery(request); - } - - if (getTunnelService().isHeadersTunnel()) { - processHeaders(request); - } - - return CONTINUE; - } - - /** - * Returns the list of new accept-encoding header values. Each of them describe - * also a set of conditions required to set the new value. This method is used - * only to initialize the headerReplacers field. - * - * @return The list of new accept-encoding header values. - */ - private List getAcceptEncodingReplacers() { - // Load the accept.properties file. - return getheaderReplacers(Engine.getResource("org/restlet/service/accept-encoding.properties"), - "acceptEncodingOld", "acceptEncodingNew"); - } - - /** - * Returns the list of new accept header values. Each of them describe also a - * set of conditions required to set the new value. This method is used only to - * initialize the headerReplacers field. - * - * @return The list of new accept header values. - */ - private List getAcceptReplacers() { - // Load the accept.properties file. - return getheaderReplacers(Engine.getResource("org/restlet/service/accept.properties"), "acceptOld", - "acceptNew"); - } - - /** - * Returns the list of new header values. Each of them describe also a set of - * conditions required to set the new value. This method is used only to - * initialize the headerReplacers field. - * - * @param userAgentPropertiesUrl The URL of the properties file that describe - * replacement values based on the user agent - * string. - * @param oldHeaderName The name of the property that gives the value - * of the header to be replaced (could be null - - * in that case, the new value is unconditionnaly - * set. - * @param newHeaderName The name of the property that gives the - * replacement value. - * @return The list of new header values. - */ - private List getheaderReplacers(final URL userAgentPropertiesUrl, String oldHeaderName, - String newHeaderName) { - List headerReplacers = new ArrayList(); - - if (userAgentPropertiesUrl != null) { - BufferedReader reader; - try { - reader = new BufferedReader( - new InputStreamReader(userAgentPropertiesUrl.openStream(), CharacterSet.UTF_8.getName()), - IoUtils.BUFFER_SIZE); - - HeaderReplacer.Builder headerReplacerBuilder = new HeaderReplacer.Builder(); - - try { - // Read the entire file, excluding comment lines starting - // with "#" character. - String line = reader.readLine(); - for (; line != null; line = reader.readLine()) { - if (!line.startsWith("#")) { - final String[] keyValue = line.split(":"); - if (keyValue.length == 2) { - final String key = keyValue[0].trim(); - final String value = keyValue[1].trim(); - if (oldHeaderName.equalsIgnoreCase(key)) { - headerReplacerBuilder.setOldValue((value.isEmpty()) ? null : value); - } else if (newHeaderName.equalsIgnoreCase(key)) { - headerReplacerBuilder.setNewValue(value); - headerReplacers.add(headerReplacerBuilder.build()); - - headerReplacerBuilder = new HeaderReplacer.Builder(); - } else { - headerReplacerBuilder.putAgentAttribute(key, value); - } - } - } - } - } finally { - reader.close(); - } - } catch (IOException e) { - getContext().getLogger() - .warning("Cannot read '" + userAgentPropertiesUrl.toString() + "' due to: " + e.getMessage()); - } - } - - return headerReplacers; - } - - /** - * Returns the metadata associated to the given extension using the - * {@link MetadataService}. - * - * @param extension The extension to lookup. - * @return The matched metadata. - */ - private Metadata getMetadata(String extension) { - return getMetadataService().getMetadata(extension); - } - - /** - * Returns the metadata service of the parent application. - * - * @return The metadata service of the parent application. - */ - public MetadataService getMetadataService() { - return getApplication().getMetadataService(); - } - - /** - * Returns the tunnel service of the parent application. - * - * @return The tunnel service of the parent application. - */ - public TunnelService getTunnelService() { - return getApplication().getTunnelService(); - } - - /** - * Updates the client preferences based on file-like extensions. The matched - * extensions are removed from the last segment. - * - * See also section 3.6.1 of JAX-RS specification - * (https://jsr311.dev.java.net) - * - * @param request The request to update. - * @return True if the query has been updated, false otherwise. - */ - private boolean processExtensions(Request request) { - final TunnelService tunnelService = getTunnelService(); - boolean extensionsModified = false; - - // Tunnel the client preferences only for GET or HEAD requests - final Method method = request.getMethod(); - if (tunnelService.isPreferencesTunnel() && (method.equals(Method.GET) || method.equals(Method.HEAD))) { - final Reference resourceRef = request.getResourceRef(); - - if (resourceRef.hasExtensions()) { - final ClientInfo clientInfo = request.getClientInfo(); - boolean encodingFound = false; - boolean characterSetFound = false; - boolean mediaTypeFound = false; - boolean languageFound = false; - String extensions = resourceRef.getExtensions(); - - // Discover extensions from right to left and stop at the first - // unknown extension. Only one extension per type of metadata is - // also allowed: i.e., one language, one media type, one - // encoding, one character set. - while (true) { - final int lastIndexOfPoint = extensions.lastIndexOf('.'); - final String extension = extensions.substring(lastIndexOfPoint + 1); - final Metadata metadata = getMetadata(extension); - - if (!mediaTypeFound && (metadata instanceof MediaType)) { - updateMetadata(clientInfo, metadata); - mediaTypeFound = true; - } else if (!languageFound && (metadata instanceof Language)) { - updateMetadata(clientInfo, metadata); - languageFound = true; - } else if (!characterSetFound && (metadata instanceof CharacterSet)) { - updateMetadata(clientInfo, metadata); - characterSetFound = true; - } else if (!encodingFound && (metadata instanceof Encoding)) { - updateMetadata(clientInfo, metadata); - encodingFound = true; - } else { - // extension does not match -> break loop - break; - } - if (lastIndexOfPoint > 0) { - extensions = extensions.substring(0, lastIndexOfPoint); - } else { - // no more extensions -> break loop - extensions = ""; - break; - } - } - - // Update the extensions if necessary - if (encodingFound || characterSetFound || mediaTypeFound || languageFound) { - resourceRef.setExtensions(extensions); - extensionsModified = true; - } - } - } - - return extensionsModified; - } - - /** - * Updates the request method based on specific header. - * - * @param request The request to update. - */ - @SuppressWarnings("unchecked") - private void processHeaders(Request request) { - final TunnelService tunnelService = getTunnelService(); - - if (tunnelService.isMethodTunnel()) { - // get the headers - Series

extraHeaders = (Series
) request.getAttributes() - .get(HeaderConstants.ATTRIBUTE_HEADERS); - - if (extraHeaders != null) { - // look for the new value of the method - final String newMethodValue = extraHeaders.getFirstValue(getTunnelService().getMethodHeader(), true); - - if (newMethodValue != null && !newMethodValue.trim().isEmpty()) { - // set the current method to the new method - request.setMethod(Method.valueOf(newMethodValue)); - } - } - } - } - - /** - * Updates the request method and client preferences based on query parameters. - * The matched parameters are removed from the query. - * - * @param request The request to update. - * @return True if the query has been updated, false otherwise. - */ - private boolean processQuery(Request request) { - TunnelService tunnelService = getTunnelService(); - boolean queryModified = false; - Reference resourceRef = request.getResourceRef(); - - if (resourceRef.hasQuery()) { - Form query = resourceRef.getQueryAsForm(CharacterSet.UTF_8); - - // Tunnel the request method - Method method = request.getMethod(); - if (tunnelService.isMethodTunnel()) { - String methodName = query.getFirstValue(tunnelService.getMethodParameter()); - - Method tunnelledMethod = Method.valueOf(methodName); - // The OPTIONS method can be tunneled via GET requests. - if (tunnelledMethod != null && (Method.POST.equals(method) || Method.OPTIONS.equals(tunnelledMethod))) { - request.setMethod(tunnelledMethod); - query.removeFirst(tunnelService.getMethodParameter()); - queryModified = true; - } - } - - // Tunnel the client preferences - if (tunnelService.isPreferencesTunnel()) { - // Get the parameter names to look for - String charSetParameter = tunnelService.getCharacterSetParameter(); - String encodingParameter = tunnelService.getEncodingParameter(); - String languageParameter = tunnelService.getLanguageParameter(); - String mediaTypeParameter = tunnelService.getMediaTypeParameter(); - - // Get the preferences from the query - String acceptedCharSet = query.getFirstValue(charSetParameter); - String acceptedEncoding = query.getFirstValue(encodingParameter); - String acceptedLanguage = query.getFirstValue(languageParameter); - String acceptedMediaType = query.getFirstValue(mediaTypeParameter); - - // Updates the client preferences - ClientInfo clientInfo = request.getClientInfo(); - Metadata metadata = getMetadata(acceptedCharSet); - - if ((metadata == null) && (acceptedCharSet != null)) { - metadata = CharacterSet.valueOf(acceptedCharSet); - } - - if (metadata instanceof CharacterSet) { - updateMetadata(clientInfo, metadata); - query.removeFirst(charSetParameter); - queryModified = true; - } - - metadata = getMetadata(acceptedEncoding); - - if ((metadata == null) && (acceptedEncoding != null)) { - metadata = Encoding.valueOf(acceptedEncoding); - } - - if (metadata instanceof Encoding) { - updateMetadata(clientInfo, metadata); - query.removeFirst(encodingParameter); - queryModified = true; - } - - metadata = getMetadata(acceptedLanguage); - - if ((metadata == null) && (acceptedLanguage != null)) { - metadata = Language.valueOf(acceptedLanguage); - } - - if (metadata instanceof Language) { - updateMetadata(clientInfo, metadata); - query.removeFirst(languageParameter); - queryModified = true; - } - - metadata = getMetadata(acceptedMediaType); - - if ((metadata == null) && (acceptedMediaType != null)) { - metadata = MediaType.valueOf(acceptedMediaType); - } - - if (metadata instanceof MediaType) { - updateMetadata(clientInfo, metadata); - query.removeFirst(mediaTypeParameter); - queryModified = true; - } - } - - // Update the query if it has been modified - if (queryModified) { - request.getResourceRef().setQuery(query.getQueryString(CharacterSet.UTF_8)); - } - } - - return queryModified; - } - - /** - * Updates the client preferences according to the user agent properties (name, - * version, etc.) taken from the "agent.properties" file located in the - * classpath. See {@link ClientInfo#getAgentAttributes()} for more details.
- * The list of new media type preferences is loaded from a property file called - * "accept.properties" located in the classpath in the subdirectory - * "org/restlet/service". This property file is composed of blocks of - * properties. One "block" of properties starts either with the beginning of the - * properties file or with the end of the previous block. One block ends with - * the "acceptNew" property which contains the value of the new Accept header. - * Here is a sample block. - * - *
-	 * agentName: firefox
-	 * acceptOld: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,\*\/\*;q=0.5
-	 * acceptNew: application/xhtml+xml,text/html,text/xml;q=0.9,application/xml;q=0.9,text/plain;q=0.8,image/png,\*\/\*;q=0.5
-	 * 
- * - * Each declared property is a condition that must be filled in order to update - * the client preferences. For example, "agentName: firefox" expresses the fact - * this block concerns only "firefox" clients. - * - * The "acceptOld" property allows checking the value of the current "Accept" - * header. If the latest equals to the value of the "acceptOld" property, then - * the preferences will be updated. This is useful for Ajax clients that look - * like their browser (same agentName, agentVersion, etc.) but can provide their - * own "Accept" header. - * - * @param request the request to update. - */ - private void processUserAgent(Request request) { - final Map agentAttributes = request.getClientInfo().getAgentAttributes(); - if (agentAttributes != null) { - if (!this.acceptReplacers.isEmpty() || !this.acceptEncodingReplacers.isEmpty()) { - // Get the old Accept header value - @SuppressWarnings("unchecked") - Series
headers = (Series
) request.getAttributes() - .get(HeaderConstants.ATTRIBUTE_HEADERS); - - String acceptOld = (headers != null) ? headers.getFirstValue(HeaderConstants.HEADER_ACCEPT, true) - : null; - // Check each replacer - for (HeaderReplacer headerReplacer : this.acceptReplacers) { - if (headerReplacer.matchesConditions(agentAttributes, acceptOld)) { - ClientInfo clientInfo = new ClientInfo(); - PreferenceReader.addMediaTypes(headerReplacer.getHeaderNew(), clientInfo); - request.getClientInfo().setAcceptedMediaTypes(clientInfo.getAcceptedMediaTypes()); - break; - } - } - String acceptEncodingOld = (headers != null) - ? headers.getFirstValue(HeaderConstants.HEADER_ACCEPT_ENCODING, true) - : null; - // Check each replacer - for (HeaderReplacer headerReplacer : this.acceptEncodingReplacers) { - if (headerReplacer.matchesConditions(agentAttributes, acceptEncodingOld)) { - ClientInfo clientInfo = new ClientInfo(); - PreferenceReader.addEncodings(headerReplacer.getHeaderNew(), clientInfo); - request.getClientInfo().setAcceptedEncodings(clientInfo.getAcceptedEncodings()); - break; - } - } - } - } - } - - /** - * Updates the client info with the given metadata. It clears existing - * preferences for the same type of metadata if necessary. - * - * @param clientInfo The client info to update. - * @param metadata The metadata to use. - */ - private void updateMetadata(ClientInfo clientInfo, Metadata metadata) { - if (metadata instanceof CharacterSet) { - clientInfo.getAcceptedCharacterSets().clear(); - clientInfo.getAcceptedCharacterSets().add(new Preference<>((CharacterSet) metadata)); - } else if (metadata instanceof Encoding) { - clientInfo.getAcceptedEncodings().clear(); - clientInfo.getAcceptedEncodings().add(new Preference<>((Encoding) metadata)); - } else if (metadata instanceof Language) { - clientInfo.getAcceptedLanguages().clear(); - clientInfo.getAcceptedLanguages().add(new Preference<>((Language) metadata)); - } else if (metadata instanceof MediaType) { - clientInfo.getAcceptedMediaTypes().clear(); - clientInfo.getAcceptedMediaTypes().add(new Preference<>((MediaType) metadata)); - } - } - + /** + * Used to describe the replacement value for an old client preference and for a series of + * specific agent (i.e., web client) attributes. + * + * @author Thierry Boileau + */ + private static class HeaderReplacer { + + static class Builder { + Map agentAttributes = new HashMap<>(); + + String newValue; + + String oldValue; + + HeaderReplacer build() { + return new HeaderReplacer(oldValue, newValue, agentAttributes); + } + + void putAgentAttribute(String key, String value) { + agentAttributes.put(key, value); + } + + void setNewValue(String newValue) { + this.newValue = newValue; + } + + void setOldValue(String oldValue) { + this.oldValue = oldValue; + } + } + + /** Agent attributes that must be checked. */ + private final Map agentAttributes; + + /** New header value. */ + private final String headerNew; + + /** Old header value. */ + private final String headerOld; + + HeaderReplacer(String headerOld, String headerNew, Map agentAttributes) { + this.headerOld = headerOld; + this.headerNew = headerNew; + this.agentAttributes = Collections.unmodifiableMap(agentAttributes); + } + + public Map getAgentAttributes() { + return agentAttributes; + } + + public String getHeaderNew() { + return headerNew; + } + + public String getHeaderOld() { + return headerOld; + } + + /** + * Indicates if the current header replacer matches the request attributes. + * + * @param agentAttributes The user agent attributes to match. + * @param headerOld The facultative value of the current's request header to match. + * @return true if the given request's attributes match the current header replacer. + */ + public boolean matchesConditions(Map agentAttributes, String headerOld) { + // Check the conditions + boolean checked = true; + // Check that the agent properties match the properties + // set by the rule. + for (Iterator> iterator = + getAgentAttributes().entrySet().iterator(); + checked && iterator.hasNext(); ) { + Entry entry = iterator.next(); + String attribute = agentAttributes.get(entry.getKey()); + checked = (attribute != null && attribute.equalsIgnoreCase(entry.getValue())); + } + if (checked && getHeaderOld() != null) { + // If the rule defines an old header value, check that it is the + // same as the user agent's header value. + checked = getHeaderOld().equals(headerOld); + } + return checked; + } + } + + /** Used to replace accept-encoding header values. */ + private final List acceptEncodingReplacers = getAcceptEncodingReplacers(); + + /** Used to replace accept header values. */ + private final List acceptReplacers = getAcceptReplacers(); + + /** + * Constructor. + * + * @param context The parent context. + */ + public TunnelFilter(Context context) { + super(context); + } + + @Override + public int beforeHandle(Request request, Response response) { + if (getTunnelService().isUserAgentTunnel()) { + processUserAgent(request); + } + + if (getTunnelService().isExtensionsTunnel()) { + processExtensions(request); + } + + if (getTunnelService().isQueryTunnel()) { + processQuery(request); + } + + if (getTunnelService().isHeadersTunnel()) { + processHeaders(request); + } + + return CONTINUE; + } + + /** + * Returns the list of new accept-encoding header values. Each of them describes also a set of + * conditions required to set the new value. This method is used only to initialize the + * headerReplacers field. + * + * @return The list of new accept-encoding header values. + */ + private List getAcceptEncodingReplacers() { + // Load the accept.properties file. + return getHeaderReplacers( + Engine.getResource("org/restlet/service/accept-encoding.properties"), + "acceptEncodingOld", + "acceptEncodingNew"); + } + + /** + * Returns the list of new accept header values. Each of them describes also a set of conditions + * required to set the new value. This method is used only to initialize the headerReplacers + * field. + * + * @return The list of new accept header values. + */ + private List getAcceptReplacers() { + // Load the accept.properties file. + return getHeaderReplacers( + Engine.getResource("org/restlet/service/accept.properties"), + "acceptOld", + "acceptNew"); + } + + /** + * Returns the list of new header values. Each of them describes also a set of conditions + * required to set the new value. This method is used only to initialize the headerReplacers + * field. + * + * @param userAgentPropertiesUrl The URL of the properties file that describe replacement values + * based on the user agent string. + * @param oldHeaderName The name of the property that gives the value of the header to be + * replaced (could be null - in that case, the new value is unconditionally set. + * @param newHeaderName The name of the property that gives the replacement value. + * @return The list of new header values. + */ + private List getHeaderReplacers( + final URL userAgentPropertiesUrl, String oldHeaderName, String newHeaderName) { + List headerReplacers = new ArrayList<>(); + + if (userAgentPropertiesUrl != null) { + BufferedReader reader; + try { + reader = + new BufferedReader( + new InputStreamReader( + userAgentPropertiesUrl.openStream(), + CharacterSet.UTF_8.getName()), + IoUtils.BUFFER_SIZE); + + HeaderReplacer.Builder headerReplacerBuilder = new HeaderReplacer.Builder(); + + try { + // Read the entire file, excluding comment lines starting + // with "#" character. + String line = reader.readLine(); + for (; line != null; line = reader.readLine()) { + if (!line.startsWith("#")) { + final String[] keyValue = line.split(":"); + if (keyValue.length == 2) { + final String key = keyValue[0].trim(); + final String value = keyValue[1].trim(); + if (oldHeaderName.equalsIgnoreCase(key)) { + headerReplacerBuilder.setOldValue( + (value.isEmpty()) ? null : value); + } else if (newHeaderName.equalsIgnoreCase(key)) { + headerReplacerBuilder.setNewValue(value); + headerReplacers.add(headerReplacerBuilder.build()); + + headerReplacerBuilder = new HeaderReplacer.Builder(); + } else { + headerReplacerBuilder.putAgentAttribute(key, value); + } + } + } + } + } finally { + reader.close(); + } + } catch (IOException e) { + getContext() + .getLogger() + .warning( + "Cannot read '" + + userAgentPropertiesUrl + + "' due to: " + + e.getMessage()); + } + } + + return headerReplacers; + } + + /** + * Returns the metadata associated with the given extension using the {@link MetadataService}. + * + * @param extension The extension to lookup. + * @return The matched metadata. + */ + private Metadata getMetadata(String extension) { + return getMetadataService().getMetadata(extension); + } + + /** + * Returns the metadata service of the parent application. + * + * @return The metadata service of the parent application. + */ + public MetadataService getMetadataService() { + return getApplication().getMetadataService(); + } + + /** + * Returns the tunnel service of the parent application. + * + * @return The tunnel service of the parent application. + */ + public TunnelService getTunnelService() { + return getApplication().getTunnelService(); + } + + /** + * Updates the client preferences based on file-like extensions. The matched extensions are + * removed from the last segment. + * + *

See also section 3.6.1 of JAX-RS specification (https://jsr311.dev.java.net) + * + * @param request The request to update. + * @return True if the query has been updated, false otherwise. + */ + private boolean processExtensions(Request request) { + final TunnelService tunnelService = getTunnelService(); + boolean extensionsModified = false; + + // Tunnel the client preferences only for GET or HEAD requests + final Method method = request.getMethod(); + if (tunnelService.isPreferencesTunnel() + && (method.equals(Method.GET) || method.equals(Method.HEAD))) { + final Reference resourceRef = request.getResourceRef(); + + if (resourceRef.hasExtensions()) { + final ClientInfo clientInfo = request.getClientInfo(); + boolean encodingFound = false; + boolean characterSetFound = false; + boolean mediaTypeFound = false; + boolean languageFound = false; + String extensions = resourceRef.getExtensions(); + + // Discover extensions from right to left and stop at the first + // unknown extension. Only one extension per type of metadata is + // also allowed: i.e., one language, one media type, one + // encoding, one character set. + while (true) { + final int lastIndexOfPoint = extensions.lastIndexOf('.'); + final String extension = extensions.substring(lastIndexOfPoint + 1); + final Metadata metadata = getMetadata(extension); + + if (!mediaTypeFound && (metadata instanceof MediaType)) { + updateMetadata(clientInfo, metadata); + mediaTypeFound = true; + } else if (!languageFound && (metadata instanceof Language)) { + updateMetadata(clientInfo, metadata); + languageFound = true; + } else if (!characterSetFound && (metadata instanceof CharacterSet)) { + updateMetadata(clientInfo, metadata); + characterSetFound = true; + } else if (!encodingFound && (metadata instanceof Encoding)) { + updateMetadata(clientInfo, metadata); + encodingFound = true; + } else { + // extension does not match -> break loop + break; + } + if (lastIndexOfPoint > 0) { + extensions = extensions.substring(0, lastIndexOfPoint); + } else { + // no more extensions -> break loop + extensions = ""; + break; + } + } + + // Update the extensions if necessary + if (encodingFound || characterSetFound || mediaTypeFound || languageFound) { + resourceRef.setExtensions(extensions); + extensionsModified = true; + } + } + } + + return extensionsModified; + } + + /** + * Updates the request method based on a specific header. + * + * @param request The request to update. + */ + @SuppressWarnings("unchecked") + private void processHeaders(Request request) { + final TunnelService tunnelService = getTunnelService(); + + if (tunnelService.isMethodTunnel()) { + // get the headers + Series

extraHeaders = + (Series
) request.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS); + + if (extraHeaders != null) { + // look for the new value of the method + final String newMethodValue = + extraHeaders.getFirstValue(getTunnelService().getMethodHeader(), true); + + if (newMethodValue != null && !newMethodValue.trim().isEmpty()) { + // set the current method to the new method + request.setMethod(Method.valueOf(newMethodValue)); + } + } + } + } + + /** + * Updates the request method and client preferences based on query parameters. The matched + * parameters are removed from the query. + * + * @param request The request to update. + * @return True if the query has been updated, false otherwise. + */ + private boolean processQuery(Request request) { + TunnelService tunnelService = getTunnelService(); + boolean queryModified = false; + Reference resourceRef = request.getResourceRef(); + + if (resourceRef.hasQuery()) { + Form query = resourceRef.getQueryAsForm(CharacterSet.UTF_8); + + // Tunnel the request method + Method method = request.getMethod(); + if (tunnelService.isMethodTunnel()) { + String methodName = query.getFirstValue(tunnelService.getMethodParameter()); + + Method tunnelledMethod = Method.valueOf(methodName); + // The OPTIONS method can be tunneled via GET requests. + if (tunnelledMethod != null + && (Method.POST.equals(method) || Method.OPTIONS.equals(tunnelledMethod))) { + request.setMethod(tunnelledMethod); + query.removeFirst(tunnelService.getMethodParameter()); + queryModified = true; + } + } + + // Tunnel the client preferences + if (tunnelService.isPreferencesTunnel()) { + // Get the parameter names to look for + String charSetParameter = tunnelService.getCharacterSetParameter(); + String encodingParameter = tunnelService.getEncodingParameter(); + String languageParameter = tunnelService.getLanguageParameter(); + String mediaTypeParameter = tunnelService.getMediaTypeParameter(); + + // Get the preferences from the query + String acceptedCharSet = query.getFirstValue(charSetParameter); + String acceptedEncoding = query.getFirstValue(encodingParameter); + String acceptedLanguage = query.getFirstValue(languageParameter); + String acceptedMediaType = query.getFirstValue(mediaTypeParameter); + + // Updates the client preferences + ClientInfo clientInfo = request.getClientInfo(); + Metadata metadata = getMetadata(acceptedCharSet); + + if ((metadata == null) && (acceptedCharSet != null)) { + metadata = CharacterSet.valueOf(acceptedCharSet); + } + + if (metadata instanceof CharacterSet) { + updateMetadata(clientInfo, metadata); + query.removeFirst(charSetParameter); + queryModified = true; + } + + metadata = getMetadata(acceptedEncoding); + + if ((metadata == null) && (acceptedEncoding != null)) { + metadata = Encoding.valueOf(acceptedEncoding); + } + + if (metadata instanceof Encoding) { + updateMetadata(clientInfo, metadata); + query.removeFirst(encodingParameter); + queryModified = true; + } + + metadata = getMetadata(acceptedLanguage); + + if ((metadata == null) && (acceptedLanguage != null)) { + metadata = Language.valueOf(acceptedLanguage); + } + + if (metadata instanceof Language) { + updateMetadata(clientInfo, metadata); + query.removeFirst(languageParameter); + queryModified = true; + } + + metadata = getMetadata(acceptedMediaType); + + if ((metadata == null) && (acceptedMediaType != null)) { + metadata = MediaType.valueOf(acceptedMediaType); + } + + if (metadata instanceof MediaType) { + updateMetadata(clientInfo, metadata); + query.removeFirst(mediaTypeParameter); + queryModified = true; + } + } + + // Update the query if it has been modified + if (queryModified) { + request.getResourceRef().setQuery(query.getQueryString(CharacterSet.UTF_8)); + } + } + + return queryModified; + } + + /** + * Updates the client preferences according to the user agent properties (name, version, etc.) + * taken from the "agent.properties" file located in the classpath. See {@link + * ClientInfo#getAgentAttributes()} for more details.
+ * The list of new media type preferences is loaded from a property file called + * "accept.properties" located in the classpath in the subdirectory "org/restlet/service". This + * property file is composed of blocks of properties. One "block" of properties starts either + * with the beginning of the properties file or with the end of the previous block. One block + * ends with the "acceptNew" property which contains the value of the new Accept header. Here is + * a sample block. + * + *
+     * agentName: firefox
+     * acceptOld: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,\*\/\*;q=0.5
+     * acceptNew: application/xhtml+xml,text/html,text/xml;q=0.9,application/xml;q=0.9,text/plain;q=0.8,image/png,\*\/\*;q=0.5
+     * 
+ * + * Each declared property is a condition that must be filled to update the client preferences. + * For example, "agentName: firefox" expresses the fact this block concerns only "firefox" + * clients. + * + *

The "acceptOld" property allows checking the value of the current "Accept" header. If the + * latest equals to the value of the "acceptOld" property, then the preferences will be updated. + * This is useful for Ajax clients that look like their browser (same agentName, agentVersion, + * etc.) but can provide their own "Accept" header. + * + * @param request the request to update. + */ + private void processUserAgent(Request request) { + final Map agentAttributes = request.getClientInfo().getAgentAttributes(); + if (agentAttributes != null + && (!this.acceptReplacers.isEmpty() || !this.acceptEncodingReplacers.isEmpty())) { + // Get the old Accept header value + @SuppressWarnings("unchecked") + Series

headers = + (Series
) request.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS); + + String acceptOld = + (headers != null) + ? headers.getFirstValue(HeaderConstants.HEADER_ACCEPT, true) + : null; + // Check each replacer + for (HeaderReplacer headerReplacer : this.acceptReplacers) { + if (headerReplacer.matchesConditions(agentAttributes, acceptOld)) { + ClientInfo clientInfo = new ClientInfo(); + PreferenceReader.addMediaTypes(headerReplacer.getHeaderNew(), clientInfo); + request.getClientInfo() + .setAcceptedMediaTypes(clientInfo.getAcceptedMediaTypes()); + break; + } + } + String acceptEncodingOld = + (headers != null) + ? headers.getFirstValue(HeaderConstants.HEADER_ACCEPT_ENCODING, true) + : null; + // Check each replacer + for (HeaderReplacer headerReplacer : this.acceptEncodingReplacers) { + if (headerReplacer.matchesConditions(agentAttributes, acceptEncodingOld)) { + ClientInfo clientInfo = new ClientInfo(); + PreferenceReader.addEncodings(headerReplacer.getHeaderNew(), clientInfo); + request.getClientInfo().setAcceptedEncodings(clientInfo.getAcceptedEncodings()); + break; + } + } + } + } + + /** + * Updates the client info with the given metadata. It clears existing preferences for the same + * type of metadata if necessary. + * + * @param clientInfo The client info to update. + * @param metadata The metadata to use. + */ + private void updateMetadata(ClientInfo clientInfo, Metadata metadata) { + if (metadata instanceof CharacterSet characterSet) { + clientInfo.getAcceptedCharacterSets().clear(); + clientInfo.getAcceptedCharacterSets().add(new Preference<>(characterSet)); + } else if (metadata instanceof Encoding encoding) { + clientInfo.getAcceptedEncodings().clear(); + clientInfo.getAcceptedEncodings().add(new Preference<>(encoding)); + } else if (metadata instanceof Language language) { + clientInfo.getAcceptedLanguages().clear(); + clientInfo.getAcceptedLanguages().add(new Preference<>(language)); + } else if (metadata instanceof MediaType mediaType) { + clientInfo.getAcceptedMediaTypes().clear(); + clientInfo.getAcceptedMediaTypes().add(new Preference<>(mediaType)); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/application/package-info.java b/org.restlet/src/main/java/org/restlet/engine/application/package-info.java new file mode 100644 index 0000000000..2ef77fdd7f --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/application/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports Restlet applications. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.application; diff --git a/org.restlet/src/main/java/org/restlet/engine/application/package.html b/org.restlet/src/main/java/org/restlet/engine/application/package.html deleted file mode 100644 index 089e6e8889..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/application/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Supports Restlet applications. -

-@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/component/ClientRoute.java b/org.restlet/src/main/java/org/restlet/engine/component/ClientRoute.java index 009c7fbe30..de5adc3c58 100644 --- a/org.restlet/src/main/java/org/restlet/engine/component/ClientRoute.java +++ b/org.restlet/src/main/java/org/restlet/engine/component/ClientRoute.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.component; +import java.util.logging.Level; import org.restlet.Client; import org.restlet.Request; import org.restlet.Response; @@ -16,70 +16,64 @@ import org.restlet.routing.Route; import org.restlet.routing.Router; -import java.util.logging.Level; - /** * Router scorer based on a target client connector. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class ClientRoute extends Route { - /** - * Constructor. - * - * @param router The parent router. - * @param target The target client. - */ - public ClientRoute(Router router, Client target) { - super(router, target); - } - - /** - * Returns the target client. - * - * @return The target client. - */ - public Client getClient() { - return (Client) getNext(); - } + /** + * Constructor. + * + * @param router The parent router. + * @param target The target client. + */ + public ClientRoute(Router router, Client target) { + super(router, target); + } - /** - * Returns the score for a given call (between 0 and 1.0). - * - * @param request The request to score. - * @param response The response to score. - * @return The score for a given call (between 0 and 1.0). - */ - @Override - public float score(Request request, Response response) { - float result = 0F; + /** + * Returns the target client. + * + * @return The target client. + */ + public Client getClient() { + return (Client) getNext(); + } - // Add the protocol score - final Protocol protocol = request.getProtocol(); + /** + * Returns the score for a given call (between 0 and 1.0). + * + * @param request The request to score. + * @param response The response to score. + * @return The score for a given call (between 0 and 1.0). + */ + @Override + public float score(Request request, Response response) { + float result = 0F; - if (protocol == null) { - getLogger().warning("Unable to determine the protocol to use for this call."); - } else if (getClient().getProtocols().contains(protocol)) { - result = 1.0F; - } + // Add the protocol score + final Protocol protocol = request.getProtocol(); - if (getLogger().isLoggable(Level.FINER)) { - getLogger().finer("Call score for the \"" + getClient().getProtocols().toString() + "\" client: " + result); - } + if (protocol == null) { + getLogger().warning("Unable to determine the protocol to use for this call."); + } else if (getClient().getProtocols().contains(protocol)) { + result = 1.0F; + } - return result; - } + if (getLogger().isLoggable(Level.FINER)) { + getLogger() + .finer( + "Call score for the \"" + + getClient().getProtocols().toString() + + "\" client: " + + result); + } - /** - * Sets the next client. - * - * @param next The next client. - */ - public void setNext(Client next) { - super.setNext(next); - } + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/component/ClientRouter.java b/org.restlet/src/main/java/org/restlet/engine/component/ClientRouter.java index 64bdc29dee..7dc7fb3c8c 100644 --- a/org.restlet/src/main/java/org/restlet/engine/component/ClientRouter.java +++ b/org.restlet/src/main/java/org/restlet/engine/component/ClientRouter.java @@ -1,84 +1,88 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.component; -import org.restlet.*; -import org.restlet.routing.Router; - import java.util.logging.Level; +import org.restlet.Client; +import org.restlet.Component; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.routing.Router; /** - * Router that collects calls from all applications and dispatches them to the - * appropriate client connectors. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Router that collects calls from all applications and dispatches them to the appropriate client + * connectors. + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class ClientRouter extends Router { - /** The parent component. */ - private volatile Component component; + /** The parent component. */ + private final Component component; - /** - * Constructor. - * - * @param component The parent component. - */ - public ClientRouter(Component component) { - super((component == null) ? null : component.getContext().createChildContext()); - this.component = component; - } + /** + * Constructor. + * + * @param component The parent component. + */ + public ClientRouter(Component component) { + super((component == null) ? null : component.getContext().createChildContext()); + this.component = component; + } - @Override - protected void logRoute(org.restlet.routing.Route route) { - if (getLogger().isLoggable(Level.FINE)) { - if (route instanceof ClientRoute) { - Client client = ((ClientRoute) route).getClient(); + @Override + protected void logRoute(org.restlet.routing.Route route) { + if (getLogger().isLoggable(Level.FINE)) { + if (route instanceof ClientRoute clientRoute) { + Client client = clientRoute.getClient(); - getLogger().fine("This client was selected: \"" + client.getProtocols() + "\""); - } else { - super.logRoute(route); - } - } - } + getLogger().fine("This client was selected: \"" + client.getProtocols() + "\""); + } else { + super.logRoute(route); + } + } + } - @Override - public Restlet getNext(Request request, Response response) { - Restlet result = super.getNext(request, response); + @Override + public Restlet getNext(Request request, Response response) { + Restlet result = super.getNext(request, response); - if (result == null) { - getLogger().warning("The protocol used by this request is not declared in the list of client connectors. (" - + request.getResourceRef().getSchemeProtocol() - + "). In case you are using an instance of the Component class, check its \"clients\" property."); - } - return result; - } + if (result == null) { + getLogger() + .warning( + "The protocol used by this request is not declared in the list of client connectors. (" + + request.getResourceRef().getSchemeProtocol() + + "). In case you are using an instance of the Component class, check its \"clients\" property."); + } + return result; + } - /** - * Returns the parent component. - * - * @return The parent component. - */ - private Component getComponent() { - return this.component; - } + /** + * Returns the parent component. + * + * @return The parent component. + */ + private Component getComponent() { + return this.component; + } - /** Starts the Restlet. */ - @Override - public synchronized void start() throws Exception { - for (final Client client : getComponent().getClients()) { - getRoutes().add(new ClientRoute(this, client)); - } + /** Starts the Restlet. */ + @Override + public synchronized void start() throws Exception { + for (final Client client : getComponent().getClients()) { + getRoutes().add(new ClientRoute(this, client)); + } - super.start(); - } + super.start(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/component/ComponentClientDispatcher.java b/org.restlet/src/main/java/org/restlet/engine/component/ComponentClientDispatcher.java index 831602a815..5197dda7f2 100644 --- a/org.restlet/src/main/java/org/restlet/engine/component/ComponentClientDispatcher.java +++ b/org.restlet/src/main/java/org/restlet/engine/component/ComponentClientDispatcher.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.component; +import java.util.Iterator; import org.restlet.Component; import org.restlet.Request; import org.restlet.Response; @@ -17,119 +17,141 @@ import org.restlet.engine.util.TemplateDispatcher; import org.restlet.routing.VirtualHost; -import java.util.Iterator; - /** * Component client dispatcher. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state as member variables. - * + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state as member variables. + * * @author Jerome Louvel */ public class ComponentClientDispatcher extends TemplateDispatcher { - /** The component context. */ - private ComponentContext componentContext; - - /** - * Constructor. - * - * @param componentContext The component context. - */ - public ComponentClientDispatcher(ComponentContext componentContext) { - this.componentContext = componentContext; - } - - @Override - protected int doHandle(Request request, Response response) { - int result = CONTINUE; - Protocol protocol = request.getProtocol(); - - if (protocol.equals(Protocol.RIAP)) { - // Let's dispatch it - LocalReference cr = new LocalReference(request.getResourceRef()); - Component component = getComponent(); - - if (component != null) { - if (cr.getRiapAuthorityType() == LocalReference.RIAP_COMPONENT) { - // This causes the baseRef of the resource reference to be - // set as if it had actually arrived from a server - // connector. - request.getResourceRef().setBaseRef(request.getResourceRef().getHostIdentifier()); - - // Ask the private internal route to handle the call - component.getInternalRouter().handle(request, response); - } else if (cr.getRiapAuthorityType() == LocalReference.RIAP_HOST) { - VirtualHost host = null; - VirtualHost currentHost = null; - final Integer hostHashCode = VirtualHost.getCurrent(); - - // Lookup the virtual host - for (final Iterator hostIter = getComponent().getHosts().iterator(); (host == null) - && hostIter.hasNext();) { - currentHost = hostIter.next(); - - if (currentHost.hashCode() == hostHashCode) { - host = currentHost; - } - } - - if ((host == null) && (component.getDefaultHost() != null)) { - if (component.getDefaultHost().hashCode() == hostHashCode) { - host = component.getDefaultHost(); - } - } - - if (host != null) { - // This causes the baseRef of the resource reference to - // be set as if it had actually arrived from a server - // connector. - request.getResourceRef().setBaseRef(request.getResourceRef().getHostIdentifier()); - - // Ask the virtual host to handle the call - host.handle(request, response); - } else { - getLogger().warning("No virtual host is available to route the RIAP Host request."); - result = STOP; - } - } else { - getLogger().warning("Unknown RIAP authority. Only \"component\" is supported."); - result = STOP; - } - } else { - getLogger().warning("No component is available to route the RIAP request."); - result = STOP; - } - } else { - getComponentContext().getComponentHelper().getClientRouter().handle(request, response); - } - - return result; - } - - /** - * Returns the parent component. - * - * @return The parent component. - */ - private Component getComponent() { - Component result = null; - - if ((getComponentContext() != null) && (getComponentContext().getComponentHelper() != null)) { - result = getComponentContext().getComponentHelper().getHelped(); - } - - return result; - - } - - /** - * Returns the component context. - * - * @return The component context. - */ - private ComponentContext getComponentContext() { - return componentContext; - } + /** The component context. */ + private final ComponentContext componentContext; + + /** + * Constructor. + * + * @param componentContext The component context. + */ + public ComponentClientDispatcher(ComponentContext componentContext) { + this.componentContext = componentContext; + } + + @Override + protected int doHandle(Request request, Response response) { + int result = CONTINUE; + + if (Protocol.RIAP.equals(request.getProtocol())) { + result = doHandleRiapRequest(request, response); + } else { + getComponentContext().getComponentHelper().getClientRouter().handle(request, response); + } + + return result; + } + + private int doHandleRiapRequest(final Request request, final Response response) { + Component component = getComponent(); + if (component == null) { + getLogger().warning("No component is available to route the RIAP request."); + return STOP; + } + + // Let's dispatch it + final int result; + + LocalReference cr = new LocalReference(request.getResourceRef()); + if (cr.getRiapAuthorityType() == LocalReference.RIAP_COMPONENT) { + result = doHandleRiapComponentRequest(component, request, response); + } else if (cr.getRiapAuthorityType() == LocalReference.RIAP_HOST) { + result = doHandleRiapHostRequest(component, request, response); + } else { + getLogger() + .warning( + "Unknown RIAP authority. Only \"component\" and \"host\" are supported: " + + cr.getRiapAuthorityType()); + result = STOP; + } + return result; + } + + private int doHandleRiapHostRequest( + final Component component, final Request request, final Response response) { + int result = CONTINUE; + + VirtualHost host = null; + VirtualHost currentHost; + + final Integer hostHashCode = VirtualHost.getCurrent(); + + // Look up the virtual host + for (final Iterator hostIter = component.getHosts().iterator(); + (host == null) && hostIter.hasNext(); ) { + currentHost = hostIter.next(); + + if (currentHost.hashCode() == hostHashCode) { + host = currentHost; + } + } + + if ((host == null) + && (component.getDefaultHost() != null) + && component.getDefaultHost().hashCode() == hostHashCode) { + host = component.getDefaultHost(); + } + + if (host != null) { + // This causes the baseRef of the resource reference to + // be set as if it had actually arrived from a server + // connector. + request.getResourceRef().setBaseRef(request.getResourceRef().getHostIdentifier()); + + // Ask the virtual host to handle the call + host.handle(request, response); + } else { + getLogger().warning("No virtual host is available to route the RIAP Host request."); + result = STOP; + } + return result; + } + + private static int doHandleRiapComponentRequest( + final Component component, final Request request, final Response response) { + // This causes the baseRef of the resource reference to be + // set as if it had actually arrived from a server + // connector. + request.getResourceRef().setBaseRef(request.getResourceRef().getHostIdentifier()); + + // Ask the private internal route to handle the call + component.getInternalRouter().handle(request, response); + + return CONTINUE; + } + + /** + * Returns the parent component. + * + * @return The parent component. + */ + private Component getComponent() { + Component result = null; + + if ((getComponentContext() != null) + && (getComponentContext().getComponentHelper() != null)) { + result = getComponentContext().getComponentHelper().getHelped(); + } + + return result; + } + + /** + * Returns the component context. + * + * @return The component context. + */ + private ComponentContext getComponentContext() { + return componentContext; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/component/ComponentContext.java b/org.restlet/src/main/java/org/restlet/engine/component/ComponentContext.java index 1154caa4f6..7f7cc51f9b 100644 --- a/org.restlet/src/main/java/org/restlet/engine/component/ComponentContext.java +++ b/org.restlet/src/main/java/org/restlet/engine/component/ComponentContext.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.component; import org.restlet.Context; @@ -15,47 +14,47 @@ /** * Context allowing access to the component's connectors. - * + * * @author Jerome Louvel */ public class ComponentContext extends Context { - /** The component helper. */ - private volatile ComponentHelper componentHelper; - - /** - * Constructor. - * - * @param componentHelper The component helper. - */ - public ComponentContext(ComponentHelper componentHelper) { - super(LogUtils.getLoggerName("org.restlet", componentHelper.getHelped())); - this.componentHelper = componentHelper; - setClientDispatcher(new ComponentClientDispatcher(this)); - setServerDispatcher(new ComponentServerDispatcher(this)); - setExecutorService(componentHelper.getHelped().getTaskService()); - } - - @Override - public Context createChildContext() { - return new ChildContext(getComponentHelper().getHelped().getContext()); - } - - /** - * Returns the component helper. - * - * @return The component helper. - */ - protected ComponentHelper getComponentHelper() { - return this.componentHelper; - } - - /** - * Sets the component helper. - * - * @param componentHelper The component helper. - */ - protected void setComponentHelper(ComponentHelper componentHelper) { - this.componentHelper = componentHelper; - } + /** The component helper. */ + private volatile ComponentHelper componentHelper; + + /** + * Constructor. + * + * @param componentHelper The component helper. + */ + public ComponentContext(ComponentHelper componentHelper) { + super(LogUtils.getLoggerName("org.restlet", componentHelper.getHelped())); + this.componentHelper = componentHelper; + setClientDispatcher(new ComponentClientDispatcher(this)); + setServerDispatcher(new ComponentServerDispatcher(this)); + setExecutorService(componentHelper.getHelped().getTaskService()); + } + + @Override + public Context createChildContext() { + return new ChildContext(getComponentHelper().getHelped().getContext()); + } + + /** + * Returns the component helper. + * + * @return The component helper. + */ + protected ComponentHelper getComponentHelper() { + return this.componentHelper; + } + + /** + * Sets the component helper. + * + * @param componentHelper The component helper. + */ + protected void setComponentHelper(ComponentHelper componentHelper) { + this.componentHelper = componentHelper; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/component/ComponentHelper.java b/org.restlet/src/main/java/org/restlet/engine/component/ComponentHelper.java index d8d5d75c2c..517922b9a5 100644 --- a/org.restlet/src/main/java/org/restlet/engine/component/ComponentHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/component/ComponentHelper.java @@ -1,15 +1,20 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.component; -import org.restlet.*; +import java.util.Iterator; +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Component; +import org.restlet.Context; +import org.restlet.Restlet; +import org.restlet.Server; import org.restlet.data.Protocol; import org.restlet.engine.CompositeHelper; import org.restlet.routing.Filter; @@ -17,220 +22,228 @@ import org.restlet.routing.VirtualHost; import org.restlet.service.Service; -import java.util.Iterator; - /** * Component helper. - * + * * @author Jerome Louvel */ public class ComponentHelper extends CompositeHelper { - /** The internal client router. */ - private final ClientRouter clientRouter; - - /** The internal server router. */ - private volatile ServerRouter serverRouter; - - /** - * Constructor. - * - * @param component The helper component. - */ - public ComponentHelper(Component component) { - super(component); - component.setContext(new ComponentContext(this)); - this.clientRouter = new ClientRouter(getHelped()); - this.serverRouter = new ServerRouter(getHelped()); - } - - /** - * Check the applications attached to a virtual host. - * - * @param host The parent virtual host. - * @return True if the check succeeded. - * @throws Exception - */ - private boolean checkVirtualHost(VirtualHost host) throws Exception { - boolean result = true; - - if (host != null) { - for (Route route : host.getRoutes()) { - Restlet next = route.getNext(); - - if (next instanceof Application application) { - - if (application.getConnectorService() != null) { - if (application.getConnectorService().getClientProtocols() != null) { - for (Protocol clientProtocol : application.getConnectorService().getClientProtocols()) { - boolean clientFound = false; - - // Try to find a client connector matching the - // client protocol - Client client; - for (Iterator iter = getHelped().getClients().iterator(); !clientFound - && iter.hasNext();) { - client = iter.next(); - clientFound = client.getProtocols().contains(clientProtocol); - } - - if (!clientFound) { - getLogger().severe("Unable to start the application \"" + application.getName() - + "\". Client connector for protocol " + clientProtocol.getName() - + " is missing."); - result = false; - } - } - } - - if (application.getConnectorService().getServerProtocols() != null) { - for (Protocol serverProtocol : application.getConnectorService().getServerProtocols()) { - boolean serverFound = false; - - // Try to find a server connector matching the - // server protocol - Server server; - for (Iterator iter = getHelped().getServers().iterator(); !serverFound - && iter.hasNext();) { - server = iter.next(); - serverFound = server.getProtocols().contains(serverProtocol); - } - - if (!serverFound) { - getLogger().severe("Unable to start the application \"" + application.getName() - + "\". Server connector for protocol " + serverProtocol.getName() - + " is missing."); - result = false; - } - } - } - } - - if (result && application.isStopped()) { - application.start(); - } - } - } - } - - return result; - } - - /** - * Returns the internal client router. - * - * @return the internal client router. - */ - public ClientRouter getClientRouter() { - return this.clientRouter; - } - - /** - * Returns the internal host router. - * - * @return the internal host router. - */ - public ServerRouter getServerRouter() { - return this.serverRouter; - } - - /** - * Sets the internal server router. - * - * @param serverRouter The internal host router. - */ - public void setServerRouter(ServerRouter serverRouter) { - this.serverRouter = serverRouter; - } - - @Override - public synchronized void start() throws Exception { - // Checking if all applications have proper connectors - boolean success = checkVirtualHost(getHelped().getDefaultHost()); - - if (success) { - for (VirtualHost host : getHelped().getHosts()) { - success = success && checkVirtualHost(host); - } - } - - // Let's actually start the component - if (!success) { - getHelped().stop(); - } else { - Filter filter = null; - - for (Service service : getHelped().getServices()) { - if (service.isEnabled()) { - // Attach the service inbound filters - filter = service - .createInboundFilter((getContext() == null) ? null : getContext().createChildContext()); - - if (filter != null) { - addInboundFilter(filter); - } - - // Attach the service outbound filters - filter = service - .createOutboundFilter((getContext() == null) ? null : getContext().createChildContext()); - - if (filter != null) { - addOutboundFilter(filter); - } - } - } - - // Re-attach the original filter's attached Restlet - setInboundNext(getServerRouter()); - } - } - - @Override - public synchronized void stop() throws Exception { - // Stop the server's router - getServerRouter().stop(); - - // Stop all applications - stopHostApplications(getHelped().getDefaultHost()); - - for (VirtualHost host : getHelped().getHosts()) { - stopHostApplications(host); - } - } - - /** - * Stop all applications attached to a virtual host - * - * @param host - * @throws Exception - */ - private void stopHostApplications(VirtualHost host) throws Exception { - for (Route route : host.getRoutes()) { - if (route.getNext().isStarted()) { - route.getNext().stop(); - } - } - } - - /** - * Set the new server router that will compute the new routes when the first - * request will be received (automatic start). - */ - @Override - public void update() throws Exception { - // Note the old router to be able to stop it at the end - ServerRouter oldRouter = getServerRouter(); - - // Set the new server router that will compute the new routes when the - // first request will be received (automatic start). - setServerRouter(new ServerRouter(getHelped())); - - // Replace the old server router - setInboundNext(getServerRouter()); - - // Stop the old server router - if (oldRouter != null) { - oldRouter.stop(); - } - } - + /** The internal client router. */ + private final ClientRouter clientRouter; + + /** The internal server router. */ + private volatile ServerRouter serverRouter; + + /** + * Constructor. + * + * @param component The helper component. + */ + public ComponentHelper(Component component) { + super(component); + component.setContext(new ComponentContext(this)); + this.clientRouter = new ClientRouter(getHelped()); + this.serverRouter = new ServerRouter(getHelped()); + } + + /** + * Check the applications attached to a virtual host. + * + * @param host The parent virtual host. + * @return True if the check succeeded. + * @throws Exception + */ + private boolean checkVirtualHost(VirtualHost host) throws Exception { + boolean result = true; + + if (host != null) { + for (Route route : host.getRoutes()) { + Restlet next = route.getNext(); + + if (next instanceof Application application) { + + if (application.getConnectorService() != null) { + if (application.getConnectorService().getClientProtocols() != null) { + for (Protocol clientProtocol : + application.getConnectorService().getClientProtocols()) { + boolean clientFound = false; + + // Try to find a client connector matching the + // client protocol + Client client; + for (Iterator iter = getHelped().getClients().iterator(); + !clientFound && iter.hasNext(); ) { + client = iter.next(); + clientFound = client.getProtocols().contains(clientProtocol); + } + + if (!clientFound) { + getLogger() + .severe( + "Unable to start the application \"" + + application.getName() + + "\". Client connector for protocol " + + clientProtocol.getName() + + " is missing."); + result = false; + } + } + } + + if (application.getConnectorService().getServerProtocols() != null) { + for (Protocol serverProtocol : + application.getConnectorService().getServerProtocols()) { + boolean serverFound = false; + + // Try to find a server connector matching the + // server protocol + Server server; + for (Iterator iter = getHelped().getServers().iterator(); + !serverFound && iter.hasNext(); ) { + server = iter.next(); + serverFound = server.getProtocols().contains(serverProtocol); + } + + if (!serverFound) { + getLogger() + .severe( + "Unable to start the application \"" + + application.getName() + + "\". Server connector for protocol " + + serverProtocol.getName() + + " is missing."); + result = false; + } + } + } + } + + if (result && application.isStopped()) { + application.start(); + } + } + } + } + + return result; + } + + /** + * Returns the internal client router. + * + * @return the internal client router. + */ + public ClientRouter getClientRouter() { + return this.clientRouter; + } + + /** + * Returns the internal host router. + * + * @return the internal host router. + */ + public ServerRouter getServerRouter() { + return this.serverRouter; + } + + /** + * Sets the internal server router. + * + * @param serverRouter The internal host router. + */ + public void setServerRouter(ServerRouter serverRouter) { + this.serverRouter = serverRouter; + } + + @Override + public synchronized void start() throws Exception { + // Checking if all applications have proper connectors + boolean success = checkVirtualHost(getHelped().getDefaultHost()); + + if (success) { + for (VirtualHost host : getHelped().getHosts()) { + success = success && checkVirtualHost(host); + } + } + + // Let's actually start the component + if (!success) { + getHelped().stop(); + } else { + Filter filter = null; + + for (Service service : getHelped().getServices()) { + if (service.isEnabled()) { + // Attach the service inbound filters + Context context = + (getContext() == null) ? null : getContext().createChildContext(); + + filter = service.createInboundFilter(context); + if (filter != null) { + addInboundFilter(filter); + } + + // Attach the service outbound filters + context = (getContext() == null) ? null : getContext().createChildContext(); + + filter = service.createOutboundFilter(context); + if (filter != null) { + addOutboundFilter(filter); + } + } + } + + // Re-attach the original filter's attached Restlet + setInboundNext(getServerRouter()); + } + } + + @Override + public synchronized void stop() throws Exception { + // Stop the server's router + getServerRouter().stop(); + + // Stop all applications + stopHostApplications(getHelped().getDefaultHost()); + + for (VirtualHost host : getHelped().getHosts()) { + stopHostApplications(host); + } + } + + /** + * Stop all applications attached to a virtual host + * + * @param host + * @throws Exception + */ + private void stopHostApplications(VirtualHost host) throws Exception { + for (Route route : host.getRoutes()) { + if (route.getNext().isStarted()) { + route.getNext().stop(); + } + } + } + + /** + * Set the new server router that will compute the new routes when the first request is received + * (automatic start). + */ + @Override + public void update() throws Exception { + // Note the old router to be able to stop it at the end + ServerRouter oldRouter = getServerRouter(); + + // Set the new server router that will compute the new routes when the + // first request is received (automatic start). + setServerRouter(new ServerRouter(getHelped())); + + // Replace the old server router + setInboundNext(getServerRouter()); + + // Stop the old server router + if (oldRouter != null) { + oldRouter.stop(); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/component/ComponentServerDispatcher.java b/org.restlet/src/main/java/org/restlet/engine/component/ComponentServerDispatcher.java index ea7b943387..144067e60c 100644 --- a/org.restlet/src/main/java/org/restlet/engine/component/ComponentServerDispatcher.java +++ b/org.restlet/src/main/java/org/restlet/engine/component/ComponentServerDispatcher.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.component; import org.restlet.Request; @@ -15,53 +14,52 @@ /** * Component server dispatcher. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state as member variables. - * + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state as member variables. + * * @author Jerome Louvel */ public class ComponentServerDispatcher extends TemplateDispatcher { - /** The component context. */ - private ComponentContext componentContext; + /** The component context. */ + private final ComponentContext componentContext; - /** - * Constructor. - * - * @param componentContext The component context. - */ - public ComponentServerDispatcher(ComponentContext componentContext) { - this.componentContext = componentContext; - } + /** + * Constructor. + * + * @param componentContext The component context. + */ + public ComponentServerDispatcher(ComponentContext componentContext) { + this.componentContext = componentContext; + } - @Override - public int beforeHandle(Request request, Response response) { - int result = super.beforeHandle(request, response); + @Override + public int beforeHandle(Request request, Response response) { + int result = super.beforeHandle(request, response); - // This causes the baseRef of the resource reference to be set - // as if it had actually arrived from a server connector. - request.getResourceRef().setBaseRef(request.getResourceRef().getHostIdentifier()); + // This causes the baseRef of the resource reference to be set + // as if it had actually arrived from a server connector. + request.getResourceRef().setBaseRef(request.getResourceRef().getHostIdentifier()); - return result; - } + return result; + } - @Override - protected int doHandle(Request request, Response response) { + @Override + protected int doHandle(Request request, Response response) { // Ask the server router to actually handle the call - getComponentContext().getComponentHelper().getServerRouter().handle(request, response); - - return CONTINUE; - } + getComponentContext().getComponentHelper().getServerRouter().handle(request, response); - /** - * Returns the component context. - * - * @return The component context. - */ - private ComponentContext getComponentContext() { - return componentContext; - } + return CONTINUE; + } + /** + * Returns the component context. + * + * @return The component context. + */ + private ComponentContext getComponentContext() { + return componentContext; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/component/HostRoute.java b/org.restlet/src/main/java/org/restlet/engine/component/HostRoute.java index 902879d058..67a85d4317 100644 --- a/org.restlet/src/main/java/org/restlet/engine/component/HostRoute.java +++ b/org.restlet/src/main/java/org/restlet/engine/component/HostRoute.java @@ -1,188 +1,186 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.component; +import java.util.logging.Level; +import java.util.regex.Pattern; import org.restlet.Request; import org.restlet.Response; +import org.restlet.data.Reference; import org.restlet.routing.Route; import org.restlet.routing.Router; import org.restlet.routing.VirtualHost; -import java.util.logging.Level; -import java.util.regex.Pattern; - /** * Route based on a target VirtualHost. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class HostRoute extends Route { - /** - * Constructor. - * - * @param router The parent router. - * @param target The target virtual host. - */ - public HostRoute(Router router, VirtualHost target) { - super(router, target); - } - - /** - * Allows filtering before processing by the next Restlet. Set the base - * reference. - * - * @param request The request to handle. - * @param response The response to update. - * @return The continuation status. - */ - @Override - protected int beforeHandle(Request request, Response response) { - if (request.getHostRef() == null) { - request.getResourceRef().setBaseRef(request.getResourceRef().getHostIdentifier()); - } else { - request.getResourceRef().setBaseRef(request.getHostRef()); - } - - if (request.isLoggable() && getLogger().isLoggable(Level.FINE)) { - getLogger().fine("Base URI: \"" + request.getResourceRef().getBaseRef() + "\". Remaining part: \"" - + request.getResourceRef().getRemainingPart() + "\""); - } - - return CONTINUE; - } - - /** - * Returns the target virtual host. - * - * @return The target virtual host. - */ - public VirtualHost getVirtualHost() { - return (VirtualHost) getNext(); - } - - /** - * Matches a formatted string against a regex pattern, in a case insensitive - * manner. - * - * @param regex The pattern to use. - * @param formattedString The formatted string to match. - * @return True if the formatted string matched the pattern. - */ - private boolean matches(String regex, String formattedString) { - return Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(formattedString).matches(); - } - - /** - * Returns the score for a given call (between 0 and 1.0). - * - * @param request The request to score. - * @param response The response to score. - * @return The score for a given call (between 0 and 1.0). - */ - @Override - public float score(Request request, Response response) { - float result = 0F; - - // Prepare the value to be matched - String hostDomain = ""; - String hostPort = ""; - String hostScheme = ""; - - if (request.getHostRef() != null) { - hostDomain = request.getHostRef().getHostDomain(); - - if (hostDomain == null) { - hostDomain = ""; - } - - int basePortValue = request.getHostRef().getHostPort(); - - if (basePortValue == -1) { - basePortValue = request.getHostRef().getSchemeProtocol().getDefaultPort(); - } - - hostPort = Integer.toString(basePortValue); - - hostScheme = request.getHostRef().getScheme(); - - if (hostScheme == null) { - hostScheme = ""; - } - } - - if (request.getResourceRef() != null) { - String resourceDomain = request.getResourceRef().getHostDomain(); - - if (resourceDomain == null) { - resourceDomain = ""; - } - - int resourcePortValue = request.getResourceRef().getHostPort(); - - if (resourcePortValue == -1 && request.getResourceRef().getSchemeProtocol() != null) { - resourcePortValue = request.getResourceRef().getSchemeProtocol().getDefaultPort(); - } - - String resourcePort = (resourcePortValue == -1) ? "" : Integer.toString(resourcePortValue); - - String resourceScheme = request.getResourceRef().getScheme(); - - if (resourceScheme == null) { - resourceScheme = ""; - } - - String serverAddress = response.getServerInfo().getAddress(); - - if (serverAddress == null) { - serverAddress = ""; - } - - int serverPortValue = response.getServerInfo().getPort(); - - if (serverPortValue == -1) { - serverPortValue = request.getProtocol().getDefaultPort(); - } - - String serverPort = Integer.toString(response.getServerInfo().getPort()); - - // Check if all the criteria match - if (matches(getVirtualHost().getHostDomain(), hostDomain) - && matches(getVirtualHost().getHostPort(), hostPort) - && matches(getVirtualHost().getHostScheme(), hostScheme) - && matches(getVirtualHost().getResourceDomain(), resourceDomain) - && matches(getVirtualHost().getResourcePort(), resourcePort) - && matches(getVirtualHost().getResourceScheme(), resourceScheme) - && matches(getVirtualHost().getServerAddress(), serverAddress) - && matches(getVirtualHost().getServerPort(), serverPort)) { - result = 1F; - } - } - - // Log the result of the matching - if (getLogger().isLoggable(Level.FINER)) { - getLogger().finer("Call score for the \"" + getVirtualHost().getName() + "\" host: " + result); - } - - return result; - } - - /** - * Sets the next virtual host. - * - * @param next The next virtual host. - */ - public void setNext(VirtualHost next) { - super.setNext(next); - } + /** + * Constructor. + * + * @param router The parent router. + * @param target The target virtual host. + */ + public HostRoute(Router router, VirtualHost target) { + super(router, target); + } + + /** + * Allows filtering before processing by the next Restlet. Set the base reference. + * + * @param request The request to handle. + * @param response The response to update. + * @return The continuation status. + */ + @Override + protected int beforeHandle(Request request, Response response) { + if (request.getHostRef() == null) { + request.getResourceRef().setBaseRef(request.getResourceRef().getHostIdentifier()); + } else { + request.getResourceRef().setBaseRef(request.getHostRef()); + } + + if (request.isLoggable() && getLogger().isLoggable(Level.FINE)) { + getLogger() + .fine( + "Base URI: \"" + + request.getResourceRef().getBaseRef() + + "\". Remaining part: \"" + + request.getResourceRef().getRemainingPart() + + "\""); + } + + return CONTINUE; + } + + /** + * Returns the target virtual host. + * + * @return The target virtual host. + */ + public VirtualHost getVirtualHost() { + return (VirtualHost) getNext(); + } + + /** + * Matches a domain against a regex pattern, in a case-insensitive manner. + * + * @param regex The pattern to use. + * @param domain The domain name to match. + * @return True if the formatted string matched the pattern. + */ + private boolean matchesDomain(String regex, String domain) { + return Pattern.compile(regex, Pattern.CASE_INSENSITIVE) + .matcher(domain == null ? "" : domain) + .matches(); + } + + /** + * Matches a port number against a regex pattern. + * + * @param regex The pattern to use. + * @param port The port to match. + * @return True if the port matched the pattern. + */ + private boolean matchesPort(String regex, int port) { + return Pattern.compile(regex, Pattern.CASE_INSENSITIVE) + .matcher(port == -1 ? "" : Integer.toString(port)) + .matches(); + } + + /** + * Matches a scheme against a regex pattern, in a case-insensitive manner. + * + * @param regex The pattern to use. + * @param scheme The scheme to match. + * @return True if the scheme matched the pattern. + */ + private boolean matchesScheme(String regex, String scheme) { + return Pattern.compile(regex, Pattern.CASE_INSENSITIVE) + .matcher(scheme == null ? "" : scheme) + .matches(); + } + + /** + * Returns the score for a given call (between 0 and 1.0). + * + * @param request The request to score. + * @param response The response to score. + * @return The score for a given call (between 0 and 1.0). + */ + @Override + public float score(Request request, Response response) { + final float result; + + // Prepare the value to be matched + String hostScheme = null; + String hostDomain = null; + int hostPort = -1; + + if (request.getHostRef() != null) { + hostDomain = request.getHostRef().getHostDomain(); + hostScheme = request.getHostRef().getScheme(); + hostPort = getHostPortOrProtocolDefaultPort(request.getHostRef()); + } + + if (request.getResourceRef() != null) { + String resourceScheme = request.getResourceRef().getScheme(); + String resourceDomain = request.getResourceRef().getHostDomain(); + final int resourcePort = getHostPortOrProtocolDefaultPort(request.getResourceRef()); + + String serverAddress = response.getServerInfo().getAddress(); + int serverPort = response.getServerInfo().getPort(); + if (serverPort == -1) { + serverPort = request.getProtocol().getDefaultPort(); + } + + // Check if all the criteria match + if (matchesDomain(getVirtualHost().getHostDomain(), hostDomain) + && matchesPort(getVirtualHost().getHostPort(), hostPort) + && matchesScheme(getVirtualHost().getHostScheme(), hostScheme) + && matchesDomain(getVirtualHost().getResourceDomain(), resourceDomain) + && matchesPort(getVirtualHost().getResourcePort(), resourcePort) + && matchesScheme(getVirtualHost().getResourceScheme(), resourceScheme) + && matchesDomain(getVirtualHost().getServerAddress(), serverAddress) + && matchesPort(getVirtualHost().getServerPort(), serverPort)) { + result = 1F; + } else { + result = 0F; + } + } else { + result = 0F; + } + + // Log the result of the matching + getLogger() + .finer( + () -> + "Call score for the \"" + + getVirtualHost().getName() + + "\" host: " + + result); + + return result; + } + + private int getHostPortOrProtocolDefaultPort(final Reference reference) { + int hostPort = reference.getHostPort(); + + if (hostPort == -1 && reference.getSchemeProtocol() != null) { + hostPort = reference.getSchemeProtocol().getDefaultPort(); + } + return hostPort; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/component/InternalRouter.java b/org.restlet/src/main/java/org/restlet/engine/component/InternalRouter.java index 1b121cbfea..5d5f45d6d6 100644 --- a/org.restlet/src/main/java/org/restlet/engine/component/InternalRouter.java +++ b/org.restlet/src/main/java/org/restlet/engine/component/InternalRouter.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.component; import org.restlet.Context; @@ -20,77 +19,77 @@ import org.restlet.routing.TemplateRoute; /** - * Provides the behavior of the internal router of a Component. It overrides the - * default behavior of a classic Router. - * + * Provides the behavior of the internal router of a Component. It overrides the default behavior of + * a classic Router. + * * @author Thierry Boileau */ public class InternalRouter extends Router { - /** - * Constructor. - * - * @param context The current context. - */ - public InternalRouter(Context context) { - super(context); - // Override Router's default modes - setDefaultMatchingMode(Template.MODE_STARTS_WITH); - setRoutingMode(Router.MODE_BEST_MATCH); - } - - @Override - protected TemplateRoute createRoute(String uriPattern, Restlet target, int matchingMode) { - TemplateRoute result = new TemplateRoute(this, uriPattern, target) { - @Override - protected int beforeHandle(Request request, Response response) { - final int result = super.beforeHandle(request, response); - - // Set the request's root reference in order to help the - // retrieval of the relative reference. - request.setRootRef(request.getResourceRef().getBaseRef()); - - return result; - } - }; - - result.getTemplate().setMatchingMode(matchingMode); - result.setMatchingQuery(getDefaultMatchingQuery()); - return result; - } - - @Override - public TemplateRoute attach(Restlet target) { - if (target.getContext() == null) { - target.setContext(getContext().createChildContext()); - } - - return super.attach(target); - } - - @Override - public TemplateRoute attach(String uriPattern, Restlet target) { - if (target.getContext() == null) { - target.setContext(getContext().createChildContext()); - } - - return super.attach(uriPattern, target); - } - - @Override - public TemplateRoute attachDefault(Restlet defaultTarget) { - if (defaultTarget.getContext() == null) { - defaultTarget.setContext(getContext().createChildContext()); - } - - return super.attachDefault(defaultTarget); - } - - @Override - public Finder createFinder(Class targetClass) { - Finder result = super.createFinder(targetClass); - result.setContext(getContext().createChildContext()); - return result; - } - + /** + * Constructor. + * + * @param context The current context. + */ + public InternalRouter(Context context) { + super(context); + // Override Router's default modes + setDefaultMatchingMode(Template.MODE_STARTS_WITH); + setRoutingMode(Router.MODE_BEST_MATCH); + } + + @Override + protected TemplateRoute createRoute(String uriPattern, Restlet target, int matchingMode) { + TemplateRoute result = + new TemplateRoute(this, uriPattern, target) { + @Override + protected int beforeHandle(Request request, Response response) { + final int result = super.beforeHandle(request, response); + + // Set the request's root reference to help the + // retrieval of the relative reference. + request.setRootRef(request.getResourceRef().getBaseRef()); + + return result; + } + }; + + result.getTemplate().setMatchingMode(matchingMode); + result.setMatchingQuery(getDefaultMatchingQuery()); + return result; + } + + @Override + public TemplateRoute attach(Restlet target) { + if (target.getContext() == null) { + target.setContext(getContext().createChildContext()); + } + + return super.attach(target); + } + + @Override + public TemplateRoute attach(String uriPattern, Restlet target) { + if (target.getContext() == null) { + target.setContext(getContext().createChildContext()); + } + + return super.attach(uriPattern, target); + } + + @Override + public TemplateRoute attachDefault(Restlet defaultTarget) { + if (defaultTarget.getContext() == null) { + defaultTarget.setContext(getContext().createChildContext()); + } + + return super.attachDefault(defaultTarget); + } + + @Override + public Finder createFinder(Class targetClass) { + Finder result = super.createFinder(targetClass); + result.setContext(getContext().createChildContext()); + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/component/ServerRouter.java b/org.restlet/src/main/java/org/restlet/engine/component/ServerRouter.java index 5d9a1c684b..22801ef188 100644 --- a/org.restlet/src/main/java/org/restlet/engine/component/ServerRouter.java +++ b/org.restlet/src/main/java/org/restlet/engine/component/ServerRouter.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.component; +import java.util.logging.Level; import org.restlet.Component; import org.restlet.Request; import org.restlet.Response; @@ -17,92 +17,99 @@ import org.restlet.routing.Router; import org.restlet.routing.VirtualHost; -import java.util.logging.Level; - /** - * Router that collects calls from all server connectors and dispatches them to - * the appropriate host routers. The host routers then dispatch them to the user - * applications. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Router that collects calls from all server connectors and dispatches them to the appropriate host + * routers. The host routers then dispatch them to the user applications. + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class ServerRouter extends Router { - /** The parent component. */ - private volatile Component component; + /** The parent component. */ + private volatile Component component; - /** - * Constructor. - * - * @param component The parent component. - */ - public ServerRouter(Component component) { - super((component == null) ? null : component.getContext().createChildContext()); - this.component = component; - setRoutingMode(MODE_FIRST_MATCH); - } + /** + * Constructor. + * + * @param component The parent component. + */ + public ServerRouter(Component component) { + super((component == null) ? null : component.getContext().createChildContext()); + this.component = component; + setRoutingMode(MODE_FIRST_MATCH); + } - /** - * Returns the parent component. - * - * @return The parent component. - */ - private Component getComponent() { - return this.component; - } + /** + * Returns the parent component. + * + * @return The parent component. + */ + private Component getComponent() { + return this.component; + } - @Override - protected void logRoute(org.restlet.routing.Route route) { - if (getLogger().isLoggable(Level.FINE)) { - if (route instanceof HostRoute) { - VirtualHost vhost = ((HostRoute) route).getVirtualHost(); + @Override + protected void logRoute(org.restlet.routing.Route route) { + if (getLogger().isLoggable(Level.FINE)) { + if (route instanceof HostRoute hostRoute) { + VirtualHost vhost = hostRoute.getVirtualHost(); - if (getComponent().getDefaultHost() == vhost) { - getLogger().fine("Default virtual host selected"); - } else { - getLogger().fine("Virtual host selected: \"" + vhost.getHostScheme() + "\", \"" - + vhost.getHostDomain() + "\", \"" + vhost.getHostPort() + "\""); - } - } else { - super.logRoute(route); - } - } - } + if (getComponent().getDefaultHost() == vhost) { + getLogger().fine("Default virtual host selected"); + } else { + getLogger() + .fine( + "Virtual host selected: \"" + + vhost.getHostScheme() + + "\", \"" + + vhost.getHostDomain() + + "\", \"" + + vhost.getHostPort() + + "\""); + } + } else { + super.logRoute(route); + } + } + } - /** Starts the Restlet. */ - @Override - public synchronized void start() throws Exception { - // Attach all virtual hosts - for (VirtualHost host : getComponent().getHosts()) { - getRoutes().add(new HostRoute(this, host)); - } + /** Starts the Restlet. */ + @Override + public synchronized void start() throws Exception { + // Attach all virtual hosts + for (VirtualHost host : getComponent().getHosts()) { + getRoutes().add(new HostRoute(this, host)); + } - // Also attach the default host if it exists - if (getComponent().getDefaultHost() != null) { - getRoutes().add(new HostRoute(this, getComponent().getDefaultHost())); - } + // Also attach the default host if it exists + if (getComponent().getDefaultHost() != null) { + getRoutes().add(new HostRoute(this, getComponent().getDefaultHost())); + } - // If no host matches, display and error page with a precise message - final Restlet noHostMatched = new Restlet(getComponent().getContext().createChildContext()) { - @Override - public void handle(Request request, Response response) { - response.setStatus(Status.CLIENT_ERROR_NOT_FOUND, "No virtual host could handle the request"); - } - }; + // If no host matches, display and error page with a precise message + final Restlet noHostMatched = + new Restlet(getComponent().getContext().createChildContext()) { + @Override + public void handle(Request request, Response response) { + response.setStatus( + Status.CLIENT_ERROR_NOT_FOUND, + "No virtual host could handle the request"); + } + }; - setDefaultRoute(new org.restlet.routing.TemplateRoute(this, "", noHostMatched)); + setDefaultRoute(new org.restlet.routing.TemplateRoute(this, "", noHostMatched)); - // Start the router - super.start(); - } + // Start the router + super.start(); + } - @Override - public synchronized void stop() throws Exception { - getRoutes().clear(); - super.stop(); - } + @Override + public synchronized void stop() throws Exception { + getRoutes().clear(); + super.stop(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/component/package-info.java b/org.restlet/src/main/java/org/restlet/engine/component/package-info.java new file mode 100644 index 0000000000..55f89804e8 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/component/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports Restlet components. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.component; diff --git a/org.restlet/src/main/java/org/restlet/engine/component/package.html b/org.restlet/src/main/java/org/restlet/engine/component/package.html deleted file mode 100644 index e26fa85934..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/component/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Supports Restlet components. -

-@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/ClientHelper.java b/org.restlet/src/main/java/org/restlet/engine/connector/ClientHelper.java index 05562a2374..7d2d4fe425 100644 --- a/org.restlet/src/main/java/org/restlet/engine/connector/ClientHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/connector/ClientHelper.java @@ -1,20 +1,20 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import org.restlet.Client; /** - * Client connector helper. Base client helper based on NIO non-blocking - * sockets. Here is the list of parameters that are supported. They should be - * set in the Client's context before it is started: + * Client connector helper. Base client helper based on NIO non-blocking sockets. Here is the list + * of parameters that are supported. They should be set in the Client's context before it is + * started: + * * * * @@ -24,18 +24,17 @@ * * *
list of supported parameters
Description
- * + * * @author Jerome Louvel */ public class ClientHelper extends ConnectorHelper { - /** - * Constructor. - * - * @param client The client to help. - */ - public ClientHelper(Client client) { - super(client); - } - + /** + * Constructor. + * + * @param client The client to help. + */ + public ClientHelper(Client client) { + super(client); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/ConnectorHelper.java b/org.restlet/src/main/java/org/restlet/engine/connector/ConnectorHelper.java index c640c61bc8..8e45567ce0 100644 --- a/org.restlet/src/main/java/org/restlet/engine/connector/ConnectorHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/connector/ConnectorHelper.java @@ -1,21 +1,20 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import org.restlet.Application; import org.restlet.Connector; -import org.restlet.Context; import org.restlet.data.Protocol; import org.restlet.engine.RestletHelper; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import org.restlet.service.ConnectorService; /** * Base connector helper. @@ -24,64 +23,48 @@ */ public abstract class ConnectorHelper extends RestletHelper { - /** - * Returns the connector service associated to a request. - * - * @return The connector service associated to a request. - */ - public static org.restlet.service.ConnectorService getConnectorService() { - org.restlet.service.ConnectorService result = null; - org.restlet.Application application = org.restlet.Application.getCurrent(); - - if (application != null) { - result = application.getConnectorService(); - } else { - result = new org.restlet.service.ConnectorService(); - } - - return result; - } + /** + * Returns the connector service associated with a request. + * + * @return The connector service associated with a request. + */ + public static ConnectorService getConnectorService() { + final ConnectorService result; + Application application = Application.getCurrent(); - /** The protocols simultaneously supported. */ - private final List protocols; + if (application != null) { + result = application.getConnectorService(); + } else { + result = new ConnectorService(); + } - /** - * Constructor. - */ - public ConnectorHelper(T connector) { - super(connector); - this.protocols = new CopyOnWriteArrayList(); - } + return result; + } - /** - * Returns the helped Restlet context. - * - * @return The helped Restlet context. - */ - @Override - public Context getContext() { - return super.getContext(); - } + /** The protocols simultaneously supported. */ + private final List protocols; - /** - * Returns the protocols simultaneously supported. - * - * @return The protocols simultaneously supported. - */ - public List getProtocols() { - return this.protocols; - } + /** Constructor. */ + protected ConnectorHelper(T connector) { + super(connector); + this.protocols = new CopyOnWriteArrayList<>(); + } - @Override - public void start() throws Exception { - } + /** + * Returns the protocols simultaneously supported. + * + * @return The protocols simultaneously supported. + */ + public List getProtocols() { + return this.protocols; + } - @Override - public void stop() throws Exception { - } + @Override + public void start() throws Exception {} - @Override - public void update() throws Exception { - } + @Override + public void stop() throws Exception {} + @Override + public void update() throws Exception {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/HttpClientHelper.java b/org.restlet/src/main/java/org/restlet/engine/connector/HttpClientHelper.java index b84cda48a9..ae03874126 100644 --- a/org.restlet/src/main/java/org/restlet/engine/connector/HttpClientHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/connector/HttpClientHelper.java @@ -1,15 +1,13 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; -import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.file.Path; @@ -17,7 +15,6 @@ import java.util.Optional; import java.util.concurrent.Executor; import java.util.logging.Level; - import org.eclipse.jetty.client.AuthenticationStore; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClientTransport; @@ -53,8 +50,9 @@ import org.restlet.engine.util.ReferenceUtils; /** - * HTTP client connector using the Jetty project. Here is the list of parameters that are supported. They should be set - * in the Client's context before it is started: + * HTTP client connector using the Jetty project. Here is the list of parameters that are supported. + * They should be set in the Client's context before it is started: + * * * * @@ -207,6 +205,7 @@ * you configure a secured enough directory. * *
list of supported parameters
+ * * For the default SSL parameters see the Javadocs of the {@link DefaultSslContextFactory} class. * * @author Jerome Louvel @@ -214,27 +213,21 @@ */ public class HttpClientHelper extends org.restlet.engine.adapter.HttpClientHelper { - /** - * The wrapped Jetty HTTP client. - */ + /** The wrapped Jetty HTTP client. */ private volatile HttpClient httpClient; - /** - * The wrapped Jetty authentication store. - */ + /** The wrapped Jetty authentication store. */ private volatile AuthenticationStore authenticationStore; - /** - * The wrapped Jetty cookie store. - */ + /** The wrapped Jetty cookie store. */ private volatile HttpCookieStore cookieStore; /** The wrapper Executor. */ private volatile Executor executor; /** - * Constructor. Properties can still be set before the wrapped Jetty HTTP client is effectively created and - * configured via the {@link #createHttpClient()} method. + * Constructor. Properties can still be set before the wrapped Jetty HTTP client is effectively + * created and configured via the {@link #createHttpClient()} method. * * @param client The client connector to help. */ @@ -243,7 +236,8 @@ public HttpClientHelper(Client client) { getProtocols().add(Protocol.HTTP); getProtocols().add(Protocol.HTTPS); this.authenticationStore = null; - this.cookieStore = isCookieSupported() ? new HttpCookieStore.Default() : new HttpCookieStore.Empty(); + this.cookieStore = + isCookieSupported() ? new HttpCookieStore.Default() : new HttpCookieStore.Empty(); this.executor = null; } @@ -256,12 +250,11 @@ public HttpClientHelper(Client client) { public ClientCall create(Request request) { ClientCall result = null; - try { - result = new JettyClientCall(this, request.getMethod().toString(), - ReferenceUtils.update(request.getResourceRef(), request).toString()); - } catch (IOException e) { - getLogger().log(Level.WARNING, "Unable to create the Jetty HTTP/HTTPS client call", e); - } + result = + new JettyClientCall( + this, + request.getMethod().toString(), + ReferenceUtils.update(request.getResourceRef(), request).toString()); return result; } @@ -275,72 +268,80 @@ protected HttpClient createHttpClient() { SslContextFactory.Client sslContextFactory = null; try { - sslContextFactory = new RestletSslContextFactoryClient(SslUtils.getSslContextFactory(this)); + sslContextFactory = + new RestletSslContextFactoryClient(SslUtils.getSslContextFactory(this)); } catch (Exception e) { getLogger().log(Level.WARNING, "Unable to create the Jetty SSL context factory", e); } - HttpTransportProtocol httpTransportProtocol = HttpTransportProtocol.fromName(getHttpClientTransportMode()); - final HttpClientTransport httpTransport = switch (httpTransportProtocol) { - case HTTP1_1 -> getHttpTransportForHttp1_1(); - case HTTP2 -> getHttpClientTransportForHttp2(); - case HTTP3 -> getHttpClientTransportForHttp3(sslContextFactory); - case DYNAMIC -> getHttpClientTransportForDynamicMode(sslContextFactory); - }; - - final HttpClient httpClient = new HttpClient(httpTransport); - httpClient.setAddressResolutionTimeout(getAddressResolutionTimeout()); + HttpTransportProtocol httpTransportProtocol = + HttpTransportProtocol.fromName(getHttpClientTransportMode()); + final HttpClientTransport httpTransport = + switch (httpTransportProtocol) { + case HTTP1_1 -> getHttpTransportForHttp11(); + case HTTP2 -> getHttpClientTransportForHttp2(); + case HTTP3 -> getHttpClientTransportForHttp3(sslContextFactory); + case DYNAMIC -> getHttpClientTransportForDynamicMode(sslContextFactory); + }; + + final HttpClient result = new HttpClient(httpTransport); + result.setAddressResolutionTimeout(getAddressResolutionTimeout()); if (getAuthenticationStore() != null) { - httpClient.setAuthenticationStore(getAuthenticationStore()); + result.setAuthenticationStore(getAuthenticationStore()); } - httpClient.setBindAddress(getBindAddress()); - httpClient.setConnectBlocking(isConnectBlocking()); - httpClient.setConnectTimeout(getConnectTimeout()); - httpClient.setDestinationIdleTimeout(getDestinationIdleTimeout()); - httpClient.setExecutor(getExecutor()); - httpClient.setFollowRedirects(isFollowRedirects()); + result.setBindAddress(getBindAddress()); + result.setConnectBlocking(isConnectBlocking()); + result.setConnectTimeout(getConnectTimeout()); + result.setDestinationIdleTimeout(getDestinationIdleTimeout()); + result.setExecutor(getExecutor()); + result.setFollowRedirects(isFollowRedirects()); final String httpComplianceMode = getHttpComplianceMode(); - final HttpCompliance httpCompliance = switch (httpComplianceMode) { - case "RFC7230" -> HttpCompliance.RFC7230; - case "RFC7230_LEGACY" -> HttpCompliance.RFC7230_LEGACY; - case "RFC2616" -> HttpCompliance.RFC2616; - case "RFC2616_LEGACY" -> HttpCompliance.RFC2616_LEGACY; - default -> { - getLogger().log(Level.WARNING, "Unknown HTTP compliance mode: {0}, default to RFC7230", httpComplianceMode); - yield HttpCompliance.RFC7230; - } - }; - httpClient.setHttpCompliance(httpCompliance); - - httpClient.setHttpCookieStore(getCookieStore()); - httpClient.setIdleTimeout(getIdleTimeout()); - httpClient.setMaxConnectionsPerDestination(getMaxConnectionsPerDestination()); - httpClient.setMaxRedirects(getMaxRedirects()); - httpClient.setMaxRequestsQueuedPerDestination(getMaxRequestsQueuedPerDestination()); - httpClient.setMaxResponseHeadersSize(getMaxResponseHeadersSize()); + final HttpCompliance httpCompliance = + switch (httpComplianceMode) { + case "RFC7230" -> HttpCompliance.RFC7230; + case "RFC7230_LEGACY" -> HttpCompliance.RFC7230_LEGACY; + case "RFC2616" -> HttpCompliance.RFC2616; + case "RFC2616_LEGACY" -> HttpCompliance.RFC2616_LEGACY; + default -> { + getLogger() + .log( + Level.WARNING, + "Unknown HTTP compliance mode: {0}, default to RFC7230", + httpComplianceMode); + yield HttpCompliance.RFC7230; + } + }; + result.setHttpCompliance(httpCompliance); + + result.setHttpCookieStore(getCookieStore()); + result.setIdleTimeout(getIdleTimeout()); + result.setMaxConnectionsPerDestination(getMaxConnectionsPerDestination()); + result.setMaxRedirects(getMaxRedirects()); + result.setMaxRequestsQueuedPerDestination(getMaxRequestsQueuedPerDestination()); + result.setMaxResponseHeadersSize(getMaxResponseHeadersSize()); final String httpProxyHost = getProxyHost(); if (httpProxyHost != null) { HttpProxy proxy = new HttpProxy(httpProxyHost, getProxyPort()); - httpClient.getProxyConfiguration().addProxy(proxy); + result.getProxyConfiguration().addProxy(proxy); } - httpClient.setRequestBufferSize(getRequestBufferSize()); - httpClient.setResponseBufferSize(getResponseBufferSize()); - httpClient.setScheduler(getScheduler()); - httpClient.setSslContextFactory(sslContextFactory); - httpClient.setStrictEventOrdering(isStrictEventOrdering()); + result.setRequestBufferSize(getRequestBufferSize()); + result.setResponseBufferSize(getResponseBufferSize()); + result.setScheduler(getScheduler()); + result.setSslContextFactory(sslContextFactory); + result.setStrictEventOrdering(isStrictEventOrdering()); final String userAgentField = getUserAgentField(); if (userAgentField != null) { - httpClient.setUserAgentField(new HttpField(HttpHeader.USER_AGENT, userAgentField)); + result.setUserAgentField(new HttpField(HttpHeader.USER_AGENT, userAgentField)); } - return httpClient; + return result; } - private static HttpClientTransportOverHTTP getHttpTransportForHttp1_1() { + private static HttpClientTransportOverHTTP getHttpTransportForHttp11() { return new HttpClientTransportOverHTTP(); } @@ -352,37 +353,43 @@ private HttpClientTransport getHttpClientTransportForHttp2() { return http2Transport; } - private HttpClientTransport getHttpClientTransportForHttp3(SslContextFactory.Client sslContextFactory) { + private HttpClientTransport getHttpClientTransportForHttp3( + SslContextFactory.Client sslContextFactory) { Path pemWorkDirectory = getHttp3PemWorkDirectoryPath(); - ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslContextFactory, pemWorkDirectory); + ClientQuicConfiguration quicConfiguration = + new ClientQuicConfiguration(sslContextFactory, pemWorkDirectory); HTTP3Client http3Client = new HTTP3Client(quicConfiguration); http3Client.getQuicConfiguration().setSessionRecvWindow(64 * 1024 * 1024); return new HttpClientTransportOverHTTP3(http3Client); } - private HttpClientTransport getHttpClientTransportForDynamicMode(SslContextFactory.Client sslContextFactory) { + private HttpClientTransport getHttpClientTransportForDynamicMode( + SslContextFactory.Client sslContextFactory) { ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11; HTTP2Client http2Client = new HTTP2Client(); - ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); + ClientConnectionFactoryOverHTTP2.HTTP2 http2 = + new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client); - ClientQuicConfiguration quicConfiguration = new ClientQuicConfiguration(sslContextFactory, - getHttp3PemWorkDirectoryPath()); + ClientQuicConfiguration quicConfiguration = + new ClientQuicConfiguration(sslContextFactory, getHttp3PemWorkDirectoryPath()); HTTP3Client http3Client = new HTTP3Client(quicConfiguration); - ClientConnectionFactoryOverHTTP3.HTTP3 http3 = new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client); + ClientConnectionFactoryOverHTTP3.HTTP3 http3 = + new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client); return new HttpClientTransportDynamic(new ClientConnector(), http1, http2, http3); } /** - * The timeout in milliseconds for the DNS resolution of host addresses. Defaults to 15000. + * The timeout in milliseconds for the DNS resolution of host addresses. Defaults to 15_000. * * @return The address resolution timeout. */ public long getAddressResolutionTimeout() { - return Long.parseLong(getHelpedParameters().getFirstValue("addressResolutionTimeout", "15000")); + return Long.parseLong( + getHelpedParameters().getFirstValue("addressResolutionTimeout", "15000")); } /** @@ -420,7 +427,8 @@ public SocketAddress getBindAddress() { } /** - * The max time in milliseconds a connection can take to connect to destinations. Defaults to 15000. + * The max time in milliseconds a connection can take to connect to destinations. Defaults to + * 15_000. * * @return The connect timeout. */ @@ -483,8 +491,8 @@ public HttpClient getHttpClient() { } /** - * Returns the HTTP compliance mode among the following options: "RFC7230", "RFC2616", "LEGACY", "RFC7230_LEGACY". - * See {@link HttpCompliance}. Default to "RFC7230". + * Returns the HTTP compliance mode among the following options: "RFC7230", "RFC2616", "LEGACY", + * "RFC7230_LEGACY". See {@link HttpCompliance}. Default to "RFC7230". * * @return The HTTP compliance mode. */ @@ -493,18 +501,19 @@ public String getHttpComplianceMode() { } /** - * Returns the HTTP client transport mode among the following options: "HTTP1_1", "HTTP2", "HTTP3", "DYNAMIC. See - * {@link HttpClientTransport}. Default to "HTTP1_1". + * Returns the HTTP client transport mode among the following options: "HTTP1_1", "HTTP2", + * "HTTP3", "DYNAMIC. See {@link HttpClientTransport}. Default to "HTTP1_1". * * @return The HTTP client transport mode. */ public String getHttpClientTransportMode() { - return getHelpedParameters().getFirstValue("httpClientTransportMode", HttpTransportProtocol.HTTP1_1.name()); + return getHelpedParameters() + .getFirstValue("httpClientTransportMode", HttpTransportProtocol.HTTP1_1.name()); } /** * Directory where are extracted the supported certificates. - * + * * @return Directory where are extracted the supported certificates. */ public String getHttp3PemWorkDir() { @@ -516,8 +525,8 @@ private Path getHttp3PemWorkDirectoryPath() { } /** - * The max time in milliseconds a connection can be idle (that is, without traffic of bytes in either direction). - * Defaults to 30000. + * The max time in milliseconds a connection can be idle (that is, without traffic of bytes in + * either direction). Defaults to 30_000. * * @return The idle timeout. */ @@ -527,16 +536,17 @@ public long getIdleTimeout() { /** * Sets the max number of connections to open to each destination. Defaults to 64. - *

- * RFC 2616 suggests that 2 connections should be opened per each destination, but browsers commonly open 6. If this - * client is used for load testing, it is common to have only one destination (the server to load test), and it is - * recommended to set this value to a high value (at least as much as the threads present in the - * {@link #getExecutor() executor}). + * + *

RFC 2616 suggests that 2 connections should be opened per each destination, but browsers + * commonly open 6. If this client is used for load testing, it is common to have only one + * destination (the server to load test), and it is recommended to set this value to a high + * value (at least as much as the threads present in the {@link #getExecutor() executor}). * * @return The maximum connections per destination. */ public int getMaxConnectionsPerDestination() { - return Integer.parseInt(getHelpedParameters().getFirstValue("maxConnectionsPerDestination", "64")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("maxConnectionsPerDestination", "64")); } /** @@ -550,17 +560,19 @@ public int getMaxRedirects() { /** * Sets the max number of requests that may be queued to a destination. Defaults to 1024. - *

- * If this client performs a high rate of requests to a destination, and all the connections managed by that - * destination are busy with other requests, then new requests will be queued up in the destination. This parameter - * controls how many requests can be queued before starting to reject them. If this client is used for load testing, - * it is common to have this parameter set to a high value, although this may impact latency (requests sit in the - * queue for a long time before being sent). + * + *

If this client performs a high rate of requests to a destination, and all the connections + * managed by that destination are busy with other requests, then new requests will be queued up + * in the destination. This parameter controls how many requests can be queued before starting + * to reject them. If this client is used for load testing, it is common to have this parameter + * set to a high value, although this may impact latency (requests sit in the queue for a long + * time before being sent). * * @return The maximum requests queues per destination. */ public int getMaxRequestsQueuedPerDestination() { - return Integer.parseInt(getHelpedParameters().getFirstValue("maxRequestsQueuedPerDestination", "1024")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("maxRequestsQueuedPerDestination", "1024")); } /** @@ -569,7 +581,8 @@ public int getMaxRequestsQueuedPerDestination() { * @return the max size in bytes of the response headers. */ public int getMaxResponseHeadersSize() { - return Integer.parseInt(getHelpedParameters().getFirstValue("maxResponseHeadersSize", "-1")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("maxResponseHeadersSize", "-1")); } /** @@ -578,7 +591,8 @@ public int getMaxResponseHeadersSize() { * @return the host name of the HTTP proxy, if specified. */ public String getProxyHost() { - return getHelpedParameters().getFirstValue("proxyHost", System.getProperty("http.proxyHost")); + return getHelpedParameters() + .getFirstValue("proxyHost", System.getProperty("http.proxyHost")); } /** @@ -588,7 +602,8 @@ public String getProxyHost() { */ public int getProxyPort() { return Integer.parseInt( - getHelpedParameters().getFirstValue("proxyPort", System.getProperty("http.proxyPort", "3128"))); + getHelpedParameters() + .getFirstValue("proxyPort", System.getProperty("http.proxyPort", "3128"))); } /** @@ -601,7 +616,7 @@ public int getRequestBufferSize() { } /** - * The size in bytes of the buffer used to read responses. Defaults to 16384. + * The size in bytes of the buffer used to read responses. Defaults to 16_384. * * @return The response buffer size. */ @@ -610,7 +625,8 @@ public int getResponseBufferSize() { } /** - * The scheduler. Defaults to null. When null, creates a new instance of {@link ScheduledExecutorScheduler}. + * The scheduler. Defaults to null. When null, creates a new instance of {@link + * ScheduledExecutorScheduler}. * * @return The scheduler. */ @@ -628,21 +644,22 @@ public String getUserAgentField() { } /** - * Indicates whether the connect operation is blocking. See {@link HttpClient#isConnectBlocking()}. + * Indicates whether the connect operation is blocking. See {@link + * HttpClient#isConnectBlocking()}. * * @return True if the connect operation is blocking. */ public boolean isConnectBlocking() { - return Boolean.parseBoolean(getHelpedParameters().getFirstValue("connectBlocking", "false")); + return Boolean.parseBoolean(getHelpedParameters().getFirstValue("connectBlocking")); } /** - * Whether to support cookies, storing and automatically sending them back. Defaults to false. + * Whether to support cookies, storing, and automatically sending them back. Defaults to false. * * @return Whether to support cookies. */ public boolean isCookieSupported() { - return Boolean.parseBoolean(getHelpedParameters().getFirstValue("cookieSupported", "false")); + return Boolean.parseBoolean(getHelpedParameters().getFirstValue("cookieSupported")); } /** @@ -656,25 +673,28 @@ public boolean isFollowRedirects() { /** * Whether request events must be strictly ordered. Defaults to false. - *

- * Client listeners may send a second request. If the second request is for the same destination, there is an - * inherent race condition for the use of the connection: the first request may still be associated with the - * connection, so the second request cannot use that connection and is forced to open another one. - *

- * From the point of view of connection usage, the connection is reusable just before the "complete" event, so it - * would be possible to reuse that connection from complete listeners; but in this case the second request's events - * will fire before the "complete" events of the first request. - *

- * This setting enforces strict event ordering so that a "begin" event of a second request can never fire before the - * "complete" event of a first request, but at the expense of an increased usage of connections. - *

- * When not enforced, a "begin" event of a second request may happen before the "complete" event of a first request - * and allow for better usage of connections. + * + *

Client listeners may send a second request. If the second request is for the same + * destination, there is an inherent race condition for the use of the connection: the first + * request may still be associated with the connection, so the second request cannot use that + * connection and is forced to open another one. + * + *

From the point of view of connection usage, the connection is reusable just before the + * "complete" event, so it would be possible to reuse that connection from complete listeners; + * but in this case the second request's events will fire before the "complete" events of the + * first request. + * + *

This setting enforces strict event ordering so that a "begin" event of a second request + * can never fire before the "complete" event of a first request, but at the expense of an + * increased usage of connections. + * + *

When not enforced, a "begin" event of a second request may happen before the "complete" + * event of a first request and allow for better usage of connections. * * @return Whether request events must be strictly ordered. */ public boolean isStrictEventOrdering() { - return Boolean.parseBoolean(getHelpedParameters().getFirstValue("strictEventOrdering", "false")); + return Boolean.parseBoolean(getHelpedParameters().getFirstValue("strictEventOrdering")); } @Override @@ -685,7 +705,6 @@ public void start() throws Exception { this.httpClient = createHttpClient(); } - final HttpClient httpClient = getHttpClient(); if (httpClient != null) { getLogger().info("Starting a Jetty HTTP/HTTPS client"); httpClient.start(); @@ -694,7 +713,6 @@ public void start() throws Exception { @Override public void stop() throws Exception { - final HttpClient httpClient = getHttpClient(); if (httpClient != null) { getLogger().info("Stopping a Jetty HTTP/HTTPS client"); httpClient.stop(); @@ -703,20 +721,24 @@ public void stop() throws Exception { super.stop(); } - /** - * Supported HTTP transport protocols. - */ + /** Supported HTTP transport protocols. */ private enum HttpTransportProtocol { - HTTP1_1, HTTP2, HTTP3, DYNAMIC; + HTTP1_1, + HTTP2, + HTTP3, + DYNAMIC; static HttpTransportProtocol fromName(final String name) { try { return HttpTransportProtocol.valueOf(name); } catch (final IllegalArgumentException iae) { - String supportedHttpTransportProtocols = Arrays.toString(HttpTransportProtocol.values()); + String supportedHttpTransportProtocols = + Arrays.toString(HttpTransportProtocol.values()); - final String errorMessage = String.format("'%s' is not one of the supported values: %s", name, - supportedHttpTransportProtocols); + final String errorMessage = + String.format( + "'%s' is not one of the supported values: %s", + name, supportedHttpTransportProtocols); throw new IllegalArgumentException(errorMessage); } diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/HttpProtocolHelper.java b/org.restlet/src/main/java/org/restlet/engine/connector/HttpProtocolHelper.java index 1d83cdd7ab..948a86643c 100644 --- a/org.restlet/src/main/java/org/restlet/engine/connector/HttpProtocolHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/connector/HttpProtocolHelper.java @@ -1,35 +1,33 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import org.restlet.data.Method; /** * Protocol helper for the HTTP protocol. - * + * * @author Thierry Boileau */ public class HttpProtocolHelper extends ProtocolHelper { - @Override - public void registerMethods() { - Method.register(Method.ALL); - Method.register(Method.CONNECT); - Method.register(Method.DELETE); - Method.register(Method.GET); - Method.register(Method.HEAD); - Method.register(Method.OPTIONS); - Method.register(Method.PATCH); - Method.register(Method.POST); - Method.register(Method.PUT); - Method.register(Method.TRACE); - } - + @Override + public void registerMethods() { + Method.register(Method.ALL); + Method.register(Method.CONNECT); + Method.register(Method.DELETE); + Method.register(Method.GET); + Method.register(Method.HEAD); + Method.register(Method.OPTIONS); + Method.register(Method.PATCH); + Method.register(Method.POST); + Method.register(Method.PUT); + Method.register(Method.TRACE); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/HttpServerHelper.java b/org.restlet/src/main/java/org/restlet/engine/connector/HttpServerHelper.java index ee2a90e82d..929e6c16cf 100644 --- a/org.restlet/src/main/java/org/restlet/engine/connector/HttpServerHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/connector/HttpServerHelper.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; +import java.util.Arrays; +import java.util.List; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Connector; @@ -17,9 +18,6 @@ import org.restlet.Server; import org.restlet.data.Protocol; -import java.util.Arrays; -import java.util.List; - /** * Jetty HTTP server connector. * @@ -56,14 +54,17 @@ public HttpServerHelper(Server server) { */ @Override protected ConnectionFactory[] createConnectionFactories(final HttpConfiguration configuration) { - HttpTransportProtocol httpTransportProtocol = HttpTransportProtocol.fromName(getHttpTransportProtocol()); + HttpTransportProtocol httpTransportProtocol = + HttpTransportProtocol.fromName(getHttpTransportProtocol()); return switch (httpTransportProtocol) { - case HTTP1_1 -> new ConnectionFactory[] { - new HttpConnectionFactory(configuration) }; - case HTTP2 -> new ConnectionFactory[] { - new HttpConnectionFactory(configuration), // still necessary to support protocol upgrade - new HTTP2CServerConnectionFactory(configuration) }; + case HTTP1_1 -> new ConnectionFactory[] {new HttpConnectionFactory(configuration)}; + case HTTP2 -> + new ConnectionFactory[] { + new HttpConnectionFactory( + configuration), // still necessary to support protocol upgrade + new HTTP2CServerConnectionFactory(configuration) + }; }; } @@ -78,27 +79,29 @@ protected List createConnectors(org.eclipse.jetty.server.Server serve * @return Supported HTTP transport protocol. */ public String getHttpTransportProtocol() { - return getHelpedParameters().getFirstValue("http.transport.protocol", HttpTransportProtocol.HTTP1_1.name()); + return getHelpedParameters() + .getFirstValue("http.transport.protocol", HttpTransportProtocol.HTTP1_1.name()); } - /** - * Supported HTTP transport protocols. - */ + /** Supported HTTP transport protocols. */ private enum HttpTransportProtocol { - HTTP1_1, HTTP2; + HTTP1_1, + HTTP2; static HttpTransportProtocol fromName(final String name) { try { return HttpTransportProtocol.valueOf(name); } catch (final IllegalArgumentException iae) { - String supportedHttpTransportProtocols = Arrays.toString(HttpTransportProtocol.values()); + String supportedHttpTransportProtocols = + Arrays.toString(HttpTransportProtocol.values()); - final String errorMessage = String.format("'%s' is not one of the supported values: %s", name, - supportedHttpTransportProtocols); + final String errorMessage = + String.format( + "'%s' is not one of the supported values: %s", + name, supportedHttpTransportProtocols); throw new IllegalArgumentException(errorMessage); } } } - } diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/HttpsServerHelper.java b/org.restlet/src/main/java/org/restlet/engine/connector/HttpsServerHelper.java index feb2bb2403..d04a227c92 100644 --- a/org.restlet/src/main/java/org/restlet/engine/connector/HttpsServerHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/connector/HttpsServerHelper.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import java.nio.file.Path; @@ -15,7 +14,6 @@ import java.util.List; import java.util.Optional; import java.util.logging.Level; - import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; import org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory; @@ -32,10 +30,12 @@ import org.restlet.data.Protocol; import org.restlet.engine.security.RestletSslContextFactoryServer; import org.restlet.engine.ssl.DefaultSslContextFactory; +import org.restlet.engine.ssl.SslUtils; /** - * Jetty HTTPS server connector. Here is the list of additional parameters that are supported. They should be set in the - * Server's context before it is started: + * Jetty HTTPS server connector. Here is the list of additional parameters that are supported. They + * should be set in the Server's context before it is started: + * * * * @@ -65,9 +65,11 @@ * you configure a secured enough directory. * *
list of supported parameters
+ * * For the default SSL parameters see the Javadocs of the {@link DefaultSslContextFactory} class. * - * @see Configure SSL for Jetty + * @see Configure + * SSL for Jetty * @author Jerome Louvel * @author Tal Liron */ @@ -87,15 +89,16 @@ public HttpsServerHelper(Server server) { protected List createConnectors(org.eclipse.jetty.server.Server server) { final List result = new ArrayList<>(); - final List httpTransportProtocols = getHttpTransportProtocols().stream() - .map(HttpTransportProtocol::fromName).toList(); + final List httpTransportProtocols = + getHttpTransportProtocols().stream().map(HttpTransportProtocol::fromName).toList(); if (httpTransportProtocols.stream().anyMatch(HttpTransportProtocol::isTcpProtocol)) { HttpConfiguration configuration = createHttpConfiguration(); ServerConnector connector = createServerConnector(server, configuration); result.add(connector); } else if (httpTransportProtocols.contains(HttpTransportProtocol.HTTP3)) { - ServerQuicConfiguration configuration = createQuicConfiguration(getQuicServerSslContextFactory()); + ServerQuicConfiguration configuration = + createQuicConfiguration(getQuicServerSslContextFactory()); QuicServerConnector connector = createQuicServerConnector(server, configuration); result.add(connector); } @@ -107,33 +110,45 @@ protected List createConnectors(org.eclipse.jetty.server.Server serve protected ConnectionFactory[] createConnectionFactories(final HttpConfiguration configuration) { final List connectionFactories = new ArrayList<>(); - final List tcpBasedTransportProtocols = getHttpTransportProtocols().stream() - .map(HttpTransportProtocol::fromName).filter(HttpTransportProtocol::isTcpProtocol).toList(); + final List tcpBasedTransportProtocols = + getHttpTransportProtocols().stream() + .map(HttpTransportProtocol::fromName) + .filter(HttpTransportProtocol::isTcpProtocol) + .toList(); for (HttpTransportProtocol tcpBasedTransportProtocol : tcpBasedTransportProtocols) { - final List protocolConnectionFactories = switch (tcpBasedTransportProtocol) { - case HTTP1_1 -> List.of(new HttpConnectionFactory(configuration)); - case HTTP2 -> List.of(new ALPNServerConnectionFactory(), new HTTP2ServerConnectionFactory(configuration)); - default -> { - String supportedHttpTransportProtocols = tcpBasedTransportProtocols.toString(); - final String errorMessage = String.format("'%s' is not one of the supported values: %s", - tcpBasedTransportProtocol, supportedHttpTransportProtocols); - throw new IllegalArgumentException(errorMessage); - } - }; + final List protocolConnectionFactories = + switch (tcpBasedTransportProtocol) { + case HTTP1_1 -> List.of(new HttpConnectionFactory(configuration)); + case HTTP2 -> + List.of( + new ALPNServerConnectionFactory(), + new HTTP2ServerConnectionFactory(configuration)); + default -> { + String supportedHttpTransportProtocols = + tcpBasedTransportProtocols.toString(); + final String errorMessage = + String.format( + "'%s' is not one of the supported values: %s", + tcpBasedTransportProtocol, + supportedHttpTransportProtocols); + throw new IllegalArgumentException(errorMessage); + } + }; connectionFactories.addAll(protocolConnectionFactories); } SslContextFactory.Server sslContextFactory = getServerSslContextFactory(); - return AbstractConnectionFactory.getFactories(sslContextFactory, - connectionFactories.toArray(new ConnectionFactory[0])); + return AbstractConnectionFactory.getFactories( + sslContextFactory, connectionFactories.toArray(new ConnectionFactory[0])); } - private QuicServerConnector createQuicServerConnector(org.eclipse.jetty.server.Server server, - ServerQuicConfiguration configuration) { - QuicServerConnector connector = new QuicServerConnector(server, configuration, - new HTTP3ServerConnectionFactory(configuration)); + private QuicServerConnector createQuicServerConnector( + org.eclipse.jetty.server.Server server, ServerQuicConfiguration configuration) { + QuicServerConnector connector = + new QuicServerConnector( + server, configuration, new HTTP3ServerConnectionFactory(configuration)); final String address = getHelped().getAddress(); if (address != null) { connector.setHost(address); @@ -150,14 +165,19 @@ private QuicServerConnector createQuicServerConnector(org.eclipse.jetty.server.S * @return Supported HTTP transport protocols. */ public List getHttpTransportProtocols() { - String httpTransportProtocolsAsString = getHelpedParameters().getFirstValue("http.transport.protocols", - HttpTransportProtocol.HTTP1_1.name()); - return Arrays.stream(httpTransportProtocolsAsString.split(",")).map(String::trim).distinct().toList(); + String httpTransportProtocolsAsString = + getHelpedParameters() + .getFirstValue( + "http.transport.protocols", HttpTransportProtocol.HTTP1_1.name()); + return Arrays.stream(httpTransportProtocolsAsString.split(",")) + .map(String::trim) + .distinct() + .toList(); } /** * Directory where are extracted the supported certificates. - * + * * @return Directory where are extracted the supported certificates. */ public String getHttp3PemWorkDir() { @@ -170,7 +190,7 @@ private Path getHttp3PemWorkDirectoryPath() { private SslContextFactory.Server getServerSslContextFactory() { try { - return new RestletSslContextFactoryServer(org.restlet.engine.ssl.SslUtils.getSslContextFactory(this)); + return new RestletSslContextFactoryServer(SslUtils.getSslContextFactory(this)); } catch (RuntimeException e) { getLogger().log(Level.WARNING, "Unable to create the Jetty SSL context factory", e); throw e; @@ -183,36 +203,63 @@ private SslContextFactory.Server getServerSslContextFactory() { private SslContextFactory.Server getQuicServerSslContextFactory() { SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - sslContextFactory.setKeyStorePassword(getHelpedParameters().getFirstValue("keyStorePassword", true, - System.getProperty("javax.net.ssl.keyStorePassword", ""))); + sslContextFactory.setKeyStorePassword( + getHelpedParameters() + .getFirstValue( + "keyStorePassword", + true, + System.getProperty("javax.net.ssl.keyStorePassword", ""))); sslContextFactory.setKeyStorePath( - getHelpedParameters().getFirstValue("keyStorePath", true, System.getProperty("javax.net.ssl.keyStore"))); + getHelpedParameters() + .getFirstValue( + "keyStorePath", + true, + System.getProperty("javax.net.ssl.keyStore"))); sslContextFactory.setKeyStoreType( - getHelpedParameters().getFirstValue("keyStoreType", true, System.getProperty("javax.net.ssl.keyStoreType"))); + getHelpedParameters() + .getFirstValue( + "keyStoreType", + true, + System.getProperty("javax.net.ssl.keyStoreType"))); sslContextFactory.setProtocol(getHelpedParameters().getFirstValue("protocol", true, "TLS")); - sslContextFactory.setSecureRandomAlgorithm(getHelpedParameters().getFirstValue("secureRandomAlgorithm", true)); - sslContextFactory.setTrustStorePassword(getHelpedParameters().getFirstValue("trustStorePassword", true, - System.getProperty("javax.net.ssl.trustStorePassword"))); + sslContextFactory.setSecureRandomAlgorithm( + getHelpedParameters().getFirstValue("secureRandomAlgorithm", true)); + sslContextFactory.setTrustStorePassword( + getHelpedParameters() + .getFirstValue( + "trustStorePassword", + true, + System.getProperty("javax.net.ssl.trustStorePassword"))); sslContextFactory.setTrustStorePath( - getHelpedParameters().getFirstValue("trustStorePath", true, System.getProperty("javax.net.ssl.trustStore"))); - sslContextFactory.setTrustStoreType(getHelpedParameters().getFirstValue("trustStoreType", true, - System.getProperty("javax.net.ssl.trustStoreType"))); + getHelpedParameters() + .getFirstValue( + "trustStorePath", + true, + System.getProperty("javax.net.ssl.trustStore"))); + sslContextFactory.setTrustStoreType( + getHelpedParameters() + .getFirstValue( + "trustStoreType", + true, + System.getProperty("javax.net.ssl.trustStoreType"))); return sslContextFactory; } - private ServerQuicConfiguration createQuicConfiguration(SslContextFactory.Server sslContextFactory) { + private ServerQuicConfiguration createQuicConfiguration( + SslContextFactory.Server sslContextFactory) { Path pemWorkDirectory = getHttp3PemWorkDirectoryPath(); - ServerQuicConfiguration configuration = new ServerQuicConfiguration(sslContextFactory, pemWorkDirectory); + ServerQuicConfiguration configuration = + new ServerQuicConfiguration(sslContextFactory, pemWorkDirectory); configuration.setOutputBufferSize(getHttpOutputBufferSize()); return configuration; } - /** - * Supported HTTP transport protocols. - */ + /** Supported HTTP transport protocols. */ private enum HttpTransportProtocol { - HTTP1_1(true), HTTP2(true), HTTP3(false); + HTTP1_1(true), + HTTP2(true), + HTTP3(false); private final boolean tcpProtocol; @@ -220,10 +267,13 @@ static HttpTransportProtocol fromName(final String name) { try { return HttpTransportProtocol.valueOf(name); } catch (final IllegalArgumentException iae) { - String supportedHttpTransportProtocols = Arrays.toString(HttpTransportProtocol.values()); + String supportedHttpTransportProtocols = + Arrays.toString(HttpTransportProtocol.values()); - final String errorMessage = String.format("'%s' is not one of the supported values: %s", name, - supportedHttpTransportProtocols); + final String errorMessage = + String.format( + "'%s' is not one of the supported values: %s", + name, supportedHttpTransportProtocols); throw new IllegalArgumentException(errorMessage); } @@ -237,5 +287,4 @@ public boolean isTcpProtocol() { return tcpProtocol; } } - } diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/JettyServerHelper.java b/org.restlet/src/main/java/org/restlet/engine/connector/JettyServerHelper.java index b26d54d7c6..b0c52c7535 100644 --- a/org.restlet/src/main/java/org/restlet/engine/connector/JettyServerHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/connector/JettyServerHelper.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import java.net.Socket; @@ -14,7 +13,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; - import org.eclipse.jetty.io.ArrayByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.server.AbstractNetworkConnector; @@ -37,8 +35,9 @@ import org.restlet.engine.adapter.JettyServerCall; /** - * Abstract Jetty web server connector. Here is the list of parameters that are - * supported. They should be set in the Server's context before it is started: + * Abstract Jetty web server connector. Here is the list of parameters that are supported. They + * should be set in the Server's context before it is started: + * * * * @@ -135,7 +134,7 @@ * * * * * @@ -203,30 +202,28 @@ * * *
list of supported parameters
int8*1024HTTP response header size in bytes; larger headers will allow for more - * and/or larger cookies and longer HTTP headers (e.g. for redirection); + * and/or larger cookies and longer HTTP headers (e.g., for redirection); * however, larger headers will also consume more memory
Server shutdown timeout in milliseconds. Defaults to 30000.
- * - * @see Jetty 12 - * documentation + * + * @see Jetty 12 documentation * @author Jerome Louvel * @author Tal Liron */ -public abstract class JettyServerHelper - extends org.restlet.engine.adapter.HttpServerHelper { +public abstract class JettyServerHelper extends org.restlet.engine.adapter.HttpServerHelper { /** The wrapped Jetty server. */ private volatile org.eclipse.jetty.server.Server wrappedServer; /** * Constructor. - * + * * @param server The server to help. */ - public JettyServerHelper(Server server) { + protected JettyServerHelper(Server server) { super(server); } /** * Creates a Jetty HTTP configuration. - * + * * @return A Jetty HTTP configuration. */ protected HttpConfiguration createHttpConfiguration() { @@ -245,7 +242,7 @@ protected HttpConfiguration createHttpConfiguration() { /** * Creates new internal Jetty connection factories. - * + * * @param configuration The HTTP configuration. * @return New internal Jetty connection factories. */ @@ -258,30 +255,33 @@ protected abstract ConnectionFactory[] createConnectionFactories( * @param server The Jetty server. * @return The Jetty connectors. */ - protected abstract List createConnectors( - org.eclipse.jetty.server.Server server); + protected abstract List createConnectors(org.eclipse.jetty.server.Server server); /** * Creates a Jetty connector based on a classical TCP type of transport. - * + * * @param server The Jetty server. * @return A Jetty connector. */ protected ServerConnector createServerConnector( - final org.eclipse.jetty.server.Server server, - final HttpConfiguration configuration) { + final org.eclipse.jetty.server.Server server, final HttpConfiguration configuration) { final int acceptors = getConnectorAcceptors(); final int selectors = getConnectorSelectors(); final Executor executor = getConnectorExecutor(); final Scheduler scheduler = getConnectorScheduler(); final ByteBufferPool byteBufferPool = getConnectorByteBufferPool(); - final ConnectionFactory[] connectionFactories = createConnectionFactories( - configuration); + final ConnectionFactory[] connectionFactories = createConnectionFactories(configuration); - final ServerConnector connector = new ServerConnector(server, executor, - scheduler, byteBufferPool, acceptors, selectors, - connectionFactories); + final ServerConnector connector = + new ServerConnector( + server, + executor, + scheduler, + byteBufferPool, + acceptors, + selectors, + connectionFactories); final String address = getHelped().getAddress(); if (address != null) { @@ -297,24 +297,21 @@ protected ServerConnector createServerConnector( /** * Creates a Jetty low-resource monitor. - * + * * @param server A Jetty server. * @return A Jetty low-resource monitor or null. */ - private LowResourceMonitor createLowResourceMonitor( - org.eclipse.jetty.server.Server server) { + private LowResourceMonitor createLowResourceMonitor(org.eclipse.jetty.server.Server server) { final LowResourceMonitor result; final int period = getLowResourceMonitorPeriod(); if (period > 0) { result = new LowResourceMonitor(server); - result.setMonitoredConnectors( - Arrays.asList(server.getConnectors())); + result.setMonitoredConnectors(Arrays.asList(server.getConnectors())); result.setPeriod(period); result.setMonitorThreads(getLowResourceMonitorThreads()); result.setMaxMemory(getLowResourceMonitorMaxMemory()); - result.setLowResourcesIdleTimeout( - getLowResourceMonitorIdleTimeout()); + result.setLowResourcesIdleTimeout(getLowResourceMonitorIdleTimeout()); } else { result = null; } @@ -324,7 +321,7 @@ private LowResourceMonitor createLowResourceMonitor( /** * Creates a Jetty server. - * + * * @return A Jetty server. */ private org.eclipse.jetty.server.Server createServer() { @@ -332,15 +329,14 @@ private org.eclipse.jetty.server.Server createServer() { final ThreadPool threadPool = createThreadPool(); // Server - final org.eclipse.jetty.server.Server jettyServer = new org.eclipse.jetty.server.Server( - threadPool); + final org.eclipse.jetty.server.Server jettyServer = + new org.eclipse.jetty.server.Server(threadPool); int serverMaxConnections = getServerMaxConnections(); if (serverMaxConnections > 0) { - ConnectionLimit connectionLimit = new ConnectionLimit( - serverMaxConnections, jettyServer); - connectionLimit - .setIdleTimeout(getServerMaxConnectionsIdleTimeout()); + ConnectionLimit connectionLimit = + new ConnectionLimit(serverMaxConnections, jettyServer); + connectionLimit.setIdleTimeout(getServerMaxConnectionsIdleTimeout()); jettyServer.addBean(connectionLimit); } @@ -356,10 +352,8 @@ private org.eclipse.jetty.server.Server createServer() { // Connectors createConnectors(jettyServer).forEach(jettyServer::addConnector); - // Low-resource monitor (must be created after connectors have been - // added) - LowResourceMonitor lowResourceMonitor = createLowResourceMonitor( - jettyServer); + // Low-resource monitor (must be created after connectors have been added) + LowResourceMonitor lowResourceMonitor = createLowResourceMonitor(jettyServer); jettyServer.addBean(lowResourceMonitor); return jettyServer; @@ -374,17 +368,17 @@ private Handler.Abstract createJettyHandler() { final Handler.Abstract result; final JettyServerHelper jettyServerHelper = this; - Handler.Abstract jettyServerHelperWrapperHandler = new Handler.Abstract() { - @Override - public boolean handle(Request request, Response response, - Callback callback) { - JettyServerCall httpCall = new JettyServerCall( - jettyServerHelper.getHelped(), request, response, - callback); - jettyServerHelper.handle(httpCall); - return true; // Indicates that the request is accepted - }; - }; + Handler.Abstract jettyServerHelperWrapperHandler = + new Handler.Abstract() { + @Override + public boolean handle(Request request, Response response, Callback callback) { + JettyServerCall httpCall = + new JettyServerCall( + jettyServerHelper.getHelped(), request, response, callback); + jettyServerHelper.handle(httpCall); + return true; // Indicates that the request is accepted + } + }; if (getShutdownGracefully()) { // StatisticsHandler for graceful shutdown @@ -400,7 +394,7 @@ public boolean handle(Request request, Response response, /** * Creates a Jetty thread pool. - * + * * @return A Jetty thread pool. */ private ThreadPool createThreadPool() { @@ -419,32 +413,30 @@ private ThreadPool createThreadPool() { } /** - * Connector acceptor thread count. Defaults to -1. When -1, Jetty will - * default to 1. - * + * Connector acceptor thread count. Defaults to -1. When -1, Jetty will default to 1. + * * @return Connector acceptor thread count. */ public int getConnectorAcceptors() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("connector.acceptors", "-1")); + return Integer.parseInt(getHelpedParameters().getFirstValue("connector.acceptors", "-1")); } /** * Connector "accept" queue size. Defaults to 0. - *

- * Also known as "accept" backlog. - * + * + *

Also known as "accept" backlog. + * * @return Connector accept queue size. */ public int getConnectorAcceptQueueSize() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("connector.acceptQueueSize", "0")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("connector.acceptQueueSize", "0")); } /** - * Connector byte buffer pool. Defaults to null. When null, will use a new - * {@link ArrayByteBufferPool}. - * + * Connector byte buffer pool. Defaults to null. When null, will use a new {@link + * ArrayByteBufferPool}. + * * @return Connector byte buffer pool or null. */ public ByteBufferPool getConnectorByteBufferPool() { @@ -452,9 +444,8 @@ public ByteBufferPool getConnectorByteBufferPool() { } /** - * Connector executor. Defaults to null. When null, will use the server's - * thread pool. - * + * Connector executor. Defaults to null. When null, will use the server's thread pool. + * * @return Connector executor or null. */ public Executor getConnectorExecutor() { @@ -463,24 +454,23 @@ public Executor getConnectorExecutor() { /** * Connector idle timeout in milliseconds. Defaults to 30000. - *

- * See {@link Socket#setSoTimeout(int)}. - *

- * This value is interpreted as the maximum time between some progress being - * made on the connection. So if a single byte is read or written, then the - * timeout is reset. - * + * + *

See {@link Socket#setSoTimeout(int)}. + * + *

This value is interpreted as the maximum time between some progress being made on the + * connection. So if a single byte is read or written, then the timeout is reset. + * * @return Connector idle timeout. */ public int getConnectorIdleTimeout() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("connector.idleTimeout", "30000")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("connector.idleTimeout", "30000")); } /** - * Connector scheduler. Defaults to null. When null, will use a new - * {@link ScheduledExecutorScheduler}. - * + * Connector scheduler. Defaults to null. When null, will use a new {@link + * ScheduledExecutorScheduler}. + * * @return Connector scheduler or null. */ public Scheduler getConnectorScheduler() { @@ -488,114 +478,107 @@ public Scheduler getConnectorScheduler() { } /** - * Connector selector thread count. Defaults to -1. When less or equal than - * 0, Jetty computes a default value derived from a heuristic over available - * CPUs and thread pool size. - * + * Connector selector thread count. Defaults to -1. When less or equal than 0, Jetty computes a + * default value derived from a heuristic over available CPUs and thread pool size. + * * @return Connector acceptor thread count. */ public int getConnectorSelectors() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("connector.selectors", "-1")); + return Integer.parseInt(getHelpedParameters().getFirstValue("connector.selectors", "-1")); } /** * HTTP header cache size in bytes. Defaults to 512. - * + * * @return HTTP header cache size. */ public int getHttpHeaderCacheSize() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("http.headerCacheSize", "1024")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("http.headerCacheSize", "1024")); } /** * HTTP output buffer size in bytes. Defaults to 32*1024. - *

- * A larger buffer can improve performance by allowing a content producer to - * run without blocking, however, larger buffers consume more memory and may - * induce some latency before a client starts processing the content. - * + * + *

A larger buffer can improve performance by allowing a content producer to run without + * blocking, however, larger buffers consume more memory and may induce some latency before a + * client starts processing the content. + * * @return HTTP output buffer size. */ public int getHttpOutputBufferSize() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("http.outputBufferSize", "32768")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("http.outputBufferSize", "32768")); } /** * HTTP request header size in bytes. Defaults to 8*1024. - *

- * Larger headers will allow for more and/or larger cookies plus larger form - * content encoded in a URL. However, larger headers consume more memory and - * can make a server more vulnerable to denial of service attacks. - * + * + *

Larger headers will allow for more and/or larger cookies plus larger form content encoded + * in a URL. However, larger headers consume more memory and can make a server more vulnerable + * to denial of service attacks. + * * @return HTTP request header size. */ public int getHttpRequestHeaderSize() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("http.requestHeaderSize", "8192")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("http.requestHeaderSize", "8192")); } /** * HTTP response header size in bytes. Defaults to 8*1024. - *

- * Larger headers will allow for more and/or larger cookies and longer HTTP - * headers (e.g., for redirection). However, larger headers will also - * consume more memory. - * + * + *

Larger headers will allow for more and/or larger cookies and longer HTTP headers (e.g., + * for redirection). However, larger headers will also consume more memory. + * * @return HTTP response header size. */ public int getHttpResponseHeaderSize() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("http.responseHeaderSize", "8192")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("http.responseHeaderSize", "8192")); } /** * Low-resource monitor idle timeout in milliseconds. Defaults to 1000. - *

- * Applied to EndPoints when in the low-resources state. - * + * + *

Applied to EndPoints when in the low-resources state. + * * @return Low-resource monitor idle timeout. */ public int getLowResourceMonitorIdleTimeout() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("lowResource.idleTimeout", "1000")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("lowResource.idleTimeout", "1000")); } /** - * Low-resource monitor max memory in bytes. Defaults to 0. When 0, the - * check is disabled. - *

- * Memory used is calculated as (totalMemory-freeMemory). - * + * Low-resource monitor max memory in bytes. Defaults to 0. When 0, the check is disabled. + * + *

Memory used is calculated as (totalMemory-freeMemory). + * * @return Low-resource monitor max memory. */ public long getLowResourceMonitorMaxMemory() { - return Long.parseLong(getHelpedParameters() - .getFirstValue("lowResource.maxMemory", "0")); + return Long.parseLong(getHelpedParameters().getFirstValue("lowResource.maxMemory", "0")); } /** - * Low-resource monitor period in milliseconds. Defaults to 1000. When 0, - * low-resource monitoring is disabled. - * + * Low-resource monitor period in milliseconds. Defaults to 1000. When 0, low-resource + * monitoring is disabled. + * * @return Low-resource monitor period. */ public int getLowResourceMonitorPeriod() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("lowResource.period", "1000")); + return Integer.parseInt(getHelpedParameters().getFirstValue("lowResource.period", "1000")); } /** - * Low-resource monitor, whether to check if we're low on threads. Defaults - * to true. - * + * Low-resource monitor, whether to check if we're low on threads. Defaults to true. + * * @return Low-resource monitor threads. */ public boolean getLowResourceMonitorThreads() { - return Boolean.parseBoolean(getHelpedParameters() - .getFirstValue("lowResource.threads", "true")); + return Boolean.parseBoolean( + getHelpedParameters().getFirstValue("lowResource.threads", "true")); } /** @@ -604,39 +587,35 @@ public boolean getLowResourceMonitorThreads() { * @return Low-resource monitor max connections. */ public int getServerMaxConnections() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("server.maxConnections", "0")); + return Integer.parseInt(getHelpedParameters().getFirstValue("server.maxConnections", "0")); } /** - * The endpoint idle timeout in milliseconds to apply when the connection - * limit is reached. Defaults to 0. When 0, there is no idle timeout. - *

- * The maximum time allowed for the endpoint to close when the connection - * limit is reached. + * The endpoint idle timeout in milliseconds to apply when the connection limit is reached. + * Defaults to 0. When 0, there is no idle timeout. + * + *

The maximum time allowed for the endpoint to close when the connection limit is reached. * - * @return The endpoint idle timeout in milliseconds to apply when the - * connection limit is reached. + * @return The endpoint idle timeout in milliseconds to apply when the connection limit is + * reached. */ public long getServerMaxConnectionsIdleTimeout() { - return Long.parseLong(getHelpedParameters() - .getFirstValue("server.maxConnections.idleTimeout", "0")); + return Long.parseLong( + getHelpedParameters().getFirstValue("server.maxConnections.idleTimeout", "0")); } /** - * When true, upon JVM shutdown, the Jetty server will block incoming - * requests and wait for pending requests to end before shutting down. - * Otherwise, incoming requests are not blocked and all requests are - * aborted. Defaults to true. - * - * @return True if upon JVM shutdown, the Jetty server will block incoming - * requests and wait for pending requests to end before shutting - * down. Otherwise, incoming requests are not blocked and all - * requests are aborted. + * When true, upon JVM shutdown, the Jetty server will block incoming requests and wait for + * pending requests to end before shutting down. Otherwise, incoming requests are not blocked + * and all requests are aborted. Defaults to true. + * + * @return True if upon JVM shutdown, the Jetty server will block incoming requests and wait for + * pending requests to end before shutting down. Otherwise, incoming requests are not + * blocked and all requests are aborted. */ public boolean getShutdownGracefully() { - return Boolean.parseBoolean(getHelpedParameters() - .getFirstValue("shutdown.gracefully", "true")); + return Boolean.parseBoolean( + getHelpedParameters().getFirstValue("shutdown.gracefully", "true")); } /** @@ -645,68 +624,68 @@ public boolean getShutdownGracefully() { * @return Server shutdown timeout. */ public long getShutdownTimeout() { - return Long.parseLong( - getHelpedParameters().getFirstValue("shutdown.timeout", "0")); + return Long.parseLong(getHelpedParameters().getFirstValue("shutdown.timeout", "0")); } /** * Thread pool idle timeout in milliseconds. Defaults to 60000. - *

- * Threads that are idle for longer than this period may be stopped. - * + * + *

Threads that are idle for longer than this period may be stopped. + * * @return Thread pool idle timeout. */ public int getThreadPoolIdleTimeout() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("threadPool.idleTimeout", "60000")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("threadPool.idleTimeout", "60000")); } /** * Thread pool maximum threads. Defaults to 200. - * + * * @return Thread pool maximum threads. */ public int getThreadPoolMaxThreads() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("threadPool.maxThreads", "200")); + return Integer.parseInt( + getHelpedParameters().getFirstValue("threadPool.maxThreads", "200")); } /** * Thread pool minimum threads. Defaults to 8. - * + * * @return Thread pool minimum threads. */ public int getThreadPoolMinThreads() { - return Integer.parseInt(getHelpedParameters() - .getFirstValue("threadPool.minThreads", "8")); + return Integer.parseInt(getHelpedParameters().getFirstValue("threadPool.minThreads", "8")); } /** * Thread pool stop timeout in milliseconds. Defaults to 5000. - *

- * The maximum time allowed for the service to shut down. - * + * + *

The maximum time allowed for the service to shut down. + * * @return Thread pool stop timeout. */ public long getThreadPoolStopTimeout() { - return Long.parseLong(getHelpedParameters() - .getFirstValue("threadPool.stopTimeout", "5000")); + return Long.parseLong( + getHelpedParameters().getFirstValue("threadPool.stopTimeout", "5000")); } /** * Thread pool threads priority. Defaults to {@link Thread#NORM_PRIORITY}. - * + * * @return Thread pool maximum threads. */ public int getThreadPoolThreadsPriority() { - return Integer.parseInt(getHelpedParameters().getFirstValue( - "threadPool.threadsPriority", - String.valueOf(Thread.NORM_PRIORITY))); + return Integer.parseInt( + getHelpedParameters() + .getFirstValue( + "threadPool.threadsPriority", + String.valueOf(Thread.NORM_PRIORITY))); } /** * Returns the wrapped Jetty server. - * + * * @return The wrapped Jetty server. */ protected org.eclipse.jetty.server.Server getWrappedServer() { @@ -718,11 +697,10 @@ protected org.eclipse.jetty.server.Server getWrappedServer() { /** * Sets the wrapped Jetty server. - * + * * @param wrappedServer The wrapped Jetty server. */ - protected void setWrappedServer( - org.eclipse.jetty.server.Server wrappedServer) { + protected void setWrappedServer(org.eclipse.jetty.server.Server wrappedServer) { Objects.requireNonNull(wrappedServer); this.wrappedServer = wrappedServer; } @@ -731,11 +709,14 @@ protected void setWrappedServer( public void start() throws Exception { super.start(); org.eclipse.jetty.server.Server server = getWrappedServer(); - AbstractNetworkConnector connector = (AbstractNetworkConnector) server - .getConnectors()[0]; - - getLogger().info("Starting the Jetty " + getProtocols() - + " server on port " + getHelped().getPort()); + AbstractNetworkConnector connector = (AbstractNetworkConnector) server.getConnectors()[0]; + + getLogger() + .info( + "Starting the Jetty " + + getProtocols() + + " server on port " + + getHelped().getPort()); try { server.start(); // We won't know the local port until after the server starts @@ -746,13 +727,16 @@ public void start() throws Exception { server.stop(); throw e; } - } @Override - public void stop() throws Exception { - getLogger().info("Stopping the Jetty " + getProtocols() - + " server on port " + getHelped().getPort()); + public synchronized void stop() throws Exception { + getLogger() + .info( + "Stopping the Jetty " + + getProtocols() + + " server on port " + + getHelped().getPort()); if (this.wrappedServer != null) { getWrappedServer().stop(); } diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/Method.java b/org.restlet/src/main/java/org/restlet/engine/connector/Method.java index 20dc2d18c1..306942bef7 100644 --- a/org.restlet/src/main/java/org/restlet/engine/connector/Method.java +++ b/org.restlet/src/main/java/org/restlet/engine/connector/Method.java @@ -1,24 +1,27 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; -import org.restlet.resource.*; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.restlet.resource.Delete; +import org.restlet.resource.Get; +import org.restlet.resource.Options; +import org.restlet.resource.Patch; +import org.restlet.resource.Post; +import org.restlet.resource.Put; /** * Meta annotation to declare method annotations. - * + * * @see Get * @see Post * @see Put @@ -31,11 +34,10 @@ @Retention(RetentionPolicy.RUNTIME) public @interface Method { - /** - * Method name identified by the underlying annotation. - * - * @return the method name identified by the underlying annotation. - */ - String value(); - + /** + * Method name identified by the underlying annotation. + * + * @return the method name identified by the underlying annotation. + */ + String value(); } diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/ProtocolHelper.java b/org.restlet/src/main/java/org/restlet/engine/connector/ProtocolHelper.java index 070c3f3bd6..db17f4ffab 100644 --- a/org.restlet/src/main/java/org/restlet/engine/connector/ProtocolHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/connector/ProtocolHelper.java @@ -1,35 +1,31 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import org.restlet.engine.Helper; /** * Protocol helper. - * + * * @author Thierry Boileau */ public abstract class ProtocolHelper extends Helper { - /** - * Constructor. - */ - public ProtocolHelper() { - super(); - registerMethods(); - } - - /** - * Register all supported methods. The implementation relies on the - * {@link org.restlet.data.Method#register(org.restlet.data.Method)} method. - */ - public abstract void registerMethods(); + /** Constructor. */ + protected ProtocolHelper() { + super(); + registerMethods(); + } + /** + * Register all supported methods. The implementation relies on the {@link + * org.restlet.data.Method#register(org.restlet.data.Method)} method. + */ + public abstract void registerMethods(); } diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/ServerHelper.java b/org.restlet/src/main/java/org/restlet/engine/connector/ServerHelper.java index 83016bd92e..dac9e300cb 100644 --- a/org.restlet/src/main/java/org/restlet/engine/connector/ServerHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/connector/ServerHelper.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import org.restlet.Request; @@ -15,64 +14,65 @@ /** * Server connector helper. - * + * * @author Jerome Louvel */ public class ServerHelper extends ConnectorHelper { - /** - * Constructor. - * - * @param server The client to help. - */ - public ServerHelper(Server server) { - super(server); - - // Clear the ephemeral port - getAttributes().put("ephemeralPort", -1); - } + private static final String ATTRIBUTE_EPHEMERAL_PORT = "ephemeralPort"; - /** - * Handles a call by invoking the helped Server's - * {@link Server#handle(Request, Response)} method. - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - public void handle(Request request, Response response) { - super.handle(request, response); - getHelped().handle(request, response); - } + /** + * Constructor. + * + * @param server The client to help. + */ + public ServerHelper(Server server) { + super(server); + clearEphemeralPort(); + } - /** - * Sets the ephemeral port in the attributes map if necessary. - * - * @param localPort The ephemeral local port. - */ - public void setEphemeralPort(int localPort) { - // If an ephemeral port is used, make sure we update the attribute for - // the API - if (getHelped().getPort() == 0) { - getAttributes().put("ephemeralPort", localPort); - } - } + /** + * Handles a call by invoking the helped Server's {@link Server#handle(Request, Response)} + * method. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public void handle(Request request, Response response) { + super.handle(request, response); + getHelped().handle(request, response); + } - /** - * Sets the ephemeral port in the attributes map if necessary. - * - * @param socket The bound server socket. - */ - public void setEphemeralPort(java.net.ServerSocket socket) { - setEphemeralPort(socket.getLocalPort()); - } + /** + * Sets the ephemeral port in the attributes map if necessary. + * + * @param localPort The ephemeral local port. + */ + public void setEphemeralPort(int localPort) { + // If an ephemeral port is used, make sure we update the attribute for the API + if (getHelped().getPort() == 0) { + getAttributes().put(ATTRIBUTE_EPHEMERAL_PORT, localPort); + } + } - @Override - public synchronized void stop() throws Exception { - super.stop(); + /** + * Sets the ephemeral port in the attributes map if necessary. + * + * @param socket The bound server socket. + */ + public void setEphemeralPort(java.net.ServerSocket socket) { + setEphemeralPort(socket.getLocalPort()); + } - // Clear the ephemeral port - getAttributes().put("ephemeralPort", -1); - } + @Override + public synchronized void stop() throws Exception { + super.stop(); + clearEphemeralPort(); + } + private void clearEphemeralPort() { + // Clear the ephemeral port + getAttributes().put(ATTRIBUTE_EPHEMERAL_PORT, -1); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/package-info.java b/org.restlet/src/main/java/org/restlet/engine/connector/package-info.java new file mode 100644 index 0000000000..4592f7a21d --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/connector/package-info.java @@ -0,0 +1,9 @@ +/** + * Integration with Java URLConnection class. Provides FTP, HTTP, and HTTPS client connectors. + * + * @since Restlet 1.0 + * @see URLConnection + * Javadocs + */ +package org.restlet.engine.connector; diff --git a/org.restlet/src/main/java/org/restlet/engine/connector/package.html b/org.restlet/src/main/java/org/restlet/engine/connector/package.html deleted file mode 100644 index eac5301117..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/connector/package.html +++ /dev/null @@ -1,9 +0,0 @@ - - -Integration with Java URLConnection class. Provides FTP, HTTP and HTTPS -client connectors. - -@since Restlet 1.0 -@see URLConnection Javadocs - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/converter/ConverterHelper.java b/org.restlet/src/main/java/org/restlet/engine/converter/ConverterHelper.java index 2c814d059f..c96becad43 100644 --- a/org.restlet/src/main/java/org/restlet/engine/converter/ConverterHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/converter/ConverterHelper.java @@ -1,14 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.converter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import org.restlet.data.ClientInfo; import org.restlet.data.MediaType; import org.restlet.data.Preference; @@ -18,196 +20,193 @@ import org.restlet.representation.Variant; import org.restlet.resource.Resource; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - /** * Converter between Representations and regular Java objects. - * + * * @author Jerome Louvel */ public abstract class ConverterHelper extends Helper { - /** - * Adds an object class to the given list. Creates a new list if necessary. - * - * @param objectClasses The object classes list to update or null. - * @param objectClass The object class to add. - * @return The input object classes list or a new one. - */ - protected List> addObjectClass(List> objectClasses, Class objectClass) { - if (objectClasses == null) { - objectClasses = new ArrayList>(); - } - - objectClasses.add(objectClass); - return objectClasses; - } - - /** - * Adds a variant to the given list. Creates a new list if necessary. - * - * @param variants The variants list to update or null. - * @param userVariant The variant to add if not null. - * @return The input variants list or a new one. - */ - protected List addVariant(List variants, VariantInfo userVariant) { - if (userVariant != null) { - if (variants == null) { - variants = new ArrayList<>(); - } - - variants.add(userVariant); - } - - return variants; - } - - /** - * Returns the list of variants that can be converted from a given object class. - * - * @param sourceClass The source class. - * @param targetVariant The expected representation metadata. - * @param variants The variants list to update. - * @return The list of variants that can be converted from a given object class. - * @throws IOException - */ - public List addVariants(Class sourceClass, Variant targetVariant, List variants) - throws IOException { - // List of variants that can be converted from the source class - List helperVariants = getVariants(sourceClass); - - if (helperVariants != null) { - // Loop over the variants list - for (VariantInfo helperVariant : helperVariants) { - if (targetVariant == null) { - variants = addVariant(variants, helperVariant); - } else if (helperVariant.includes(targetVariant)) { - // Detected a more generic variant, but still - // consider - // the conversion is possible to the target variant. - variants = addVariant(variants, new VariantInfo(targetVariant.getMediaType())); - } else if (targetVariant.includes(helperVariant)) { - // Detected a more specific variant, but still - // consider - // the conversion is possible to the target variant. - variants = addVariant(variants, helperVariant); - } - } - } - - return variants; - } - - /** - * Returns the list of object classes that can be converted from a given - * variant. - * - * @param source The source variant. - * @return The list of object class that can be converted. - */ - public abstract List> getObjectClasses(Variant source); - - /** - * Returns the list of variants that can be converted from a given object class. - * The preferred variant should be set in first position. - * - * @param source The source object class. - * @return The list of variants that can be converted. - */ - public abstract List getVariants(Class source) throws IOException; - - /** - * Returns the list of variants that can be converted from a given object class - * by a specific converter helper. - * - * @param sourceClass The source class. - * @param targetVariant The expected representation metadata. - * @return The list of variants that can be converted. - * @throws IOException - */ - public List getVariants(Class sourceClass, Variant targetVariant) throws IOException { - return addVariants(sourceClass, targetVariant, null); - } - - /** - * Scores the affinity of this helper with the source class. - * - * @param source The source object to convert. - * @param target The expected representation metadata. - * @param resource The calling resource. - * @return The affinity score of this helper. - */ - public abstract float score(Object source, Variant target, Resource resource); - - /** - * Scores the affinity of this helper with the source class. - * - * @param The expected class of the Java object. - * @param source The source representation to convert. - * @param target The expected class of the Java object. - * @param resource The calling resource. - * @return The affinity score of this helper. - */ - public abstract float score(Representation source, Class target, Resource resource); - - /** - * Converts a Representation into a regular Java object. - * - * @param The expected class of the Java object. - * @param source The source representation to convert. - * @param target The expected class of the Java object. - * @param resource The calling resource. - * @return The converted Java object. - */ - public abstract T toObject(Representation source, Class target, Resource resource) throws IOException; - - /** - * Converts a regular Java object into a Representation. - * - * @param source The source object to convert. - * @param target The expected representation metadata. - * @param resource The calling resource. - * @return The converted representation. - */ - public abstract Representation toRepresentation(Object source, Variant target, Resource resource) - throws IOException; - - /** - * Updates the preferences of the given {@link ClientInfo} object with - * conversion capabilities for the given entity class. - * - * @param preferences The media type preferences. - * @param entity The entity class to convert. - */ - public void updatePreferences(List> preferences, Class entity) { - // Does nothing by default - } - - /** - * Updates the preferences of the given {@link ClientInfo} object with - * conversion capabilities for the given entity class. - * - * @param preferences The media type preferences. - * @param mediaType The media type to update to add to the preferences. - * @param score The media type score to use as a quality score. - */ - public void updatePreferences(List> preferences, MediaType mediaType, float score) { - boolean found = false; - Preference preference; - - for (int i = 0; !found && (i < preferences.size()); i++) { - preference = preferences.get(i); - - if (preference.getMetadata().equals(mediaType) && (preference.getQuality() < score)) { - preference.setQuality(score); - found = true; - } - } - - if (!found) { - preferences.add(new Preference(mediaType, score)); - } - } + /** + * Adds an object class to the given list. Creates a new list if necessary. + * + * @param objectClasses The object classes list to update or null. + * @param objectClass The object class to add. + * @return The input object classes list or a new one. + */ + protected List> addObjectClass(List> objectClasses, Class objectClass) { + if (objectClasses == null) { + objectClasses = new ArrayList<>(); + } + + objectClasses.add(objectClass); + return objectClasses; + } + + /** + * Adds a variant to the given list. Creates a new list if necessary. + * + * @param variants The variants list to update or null. + * @param userVariant The variant to add if not null. + * @return The input variants list or a new one. + */ + protected List addVariant(List variants, VariantInfo userVariant) { + if (userVariant != null) { + if (variants == null) { + variants = new ArrayList<>(); + } + + variants.add(userVariant); + } + + return variants; + } + + /** + * Returns the list of variants that can be converted from a given object class. + * + * @param sourceClass The source class. + * @param targetVariant The expected representation metadata. + * @param variants The variants list to update. + * @return The list of variants that can be converted from a given object class. + * @throws IOException + */ + public List addVariants( + Class sourceClass, Variant targetVariant, List variants) + throws IOException { + // List of variants that can be converted from the source class + List helperVariants = getVariants(sourceClass); + + if (helperVariants != null) { + // Loop over the variants list + for (VariantInfo helperVariant : helperVariants) { + if (targetVariant == null) { + variants = addVariant(variants, helperVariant); + } else if (helperVariant.includes(targetVariant)) { + // Detected a more generic variant, but still consider the conversion is + // possible to the target variant. + variants = addVariant(variants, new VariantInfo(targetVariant.getMediaType())); + } else if (targetVariant.includes(helperVariant)) { + // Detected a more specific variant, but still consider the conversion is + // possible to the target variant. + variants = addVariant(variants, helperVariant); + } + } + } + + return variants; + } + + /** + * Returns the list of object classes that can be converted from a given variant. + * + * @param source The source variant. + * @return The list of object class that can be converted. + */ + public abstract List> getObjectClasses(Variant source); + + /** + * Returns the list of variants that can be converted from a given object class. The preferred + * variant should be set in the first position. + * + * @param source The source object class. + * @return The list of variants that can be converted. + */ + public abstract List getVariants(Class source) throws IOException; + + /** + * Returns the list of variants that can be converted from a given object class by a specific + * converter helper. + * + * @param sourceClass The source class. + * @param targetVariant The expected representation metadata. + * @return The list of variants that can be converted. + * @throws IOException + */ + public List getVariants(Class sourceClass, Variant targetVariant) + throws IOException { + return addVariants(sourceClass, targetVariant, null); + } + + /** + * Scores the affinity of this helper with the source class. + * + * @param source The source object to convert. + * @param target The expected representation metadata. + * @param resource The calling resource. + * @return The affinity score of this helper. + */ + public abstract float score(Object source, Variant target, Resource resource); + + /** + * Scores the affinity of this helper with the source class. + * + * @param The expected class of the Java object. + * @param source The source representation to convert. + * @param target The expected class of the Java object. + * @param resource The calling resource. + * @return The affinity score of this helper. + */ + public abstract float score(Representation source, Class target, Resource resource); + + /** + * Converts a Representation into a regular Java object. + * + * @param The expected class of the Java object. + * @param source The source representation to convert. + * @param target The expected class of the Java object. + * @param resource The calling resource. + * @return The converted Java object. + */ + public abstract T toObject(Representation source, Class target, Resource resource) + throws IOException; + + /** + * Converts a regular Java object into a Representation. + * + * @param source The source object to convert. + * @param target The expected representation metadata. + * @param resource The calling resource. + * @return The converted representation. + */ + public abstract Representation toRepresentation( + Object source, Variant target, Resource resource) throws IOException; + + /** + * Updates the preferences of the given {@link ClientInfo} object with conversion capabilities + * for the given entity class. + * + * @param preferences The media type preferences. + * @param entity The entity class to convert. + */ + public void updatePreferences(List> preferences, Class entity) { + // Does nothing by default + } + + /** + * Updates the preferences of the given {@link ClientInfo} object with conversion capabilities + * for the given entity class. + * + * @param preferences The media type preferences. + * @param mediaType The media type to update to add to the preferences. + * @param score The media type score to use as a quality score. + */ + public void updatePreferences( + List> preferences, MediaType mediaType, float score) { + boolean found = false; + Preference preference; + + for (int i = 0; !found && (i < preferences.size()); i++) { + preference = preferences.get(i); + + if (preference.getMetadata().equals(mediaType) && (preference.getQuality() < score)) { + preference.setQuality(score); + found = true; + } + } + + if (!found) { + preferences.add(new Preference<>(mediaType, score)); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/converter/ConverterUtils.java b/org.restlet/src/main/java/org/restlet/engine/converter/ConverterUtils.java index f32a5ebad2..cdf76b9272 100644 --- a/org.restlet/src/main/java/org/restlet/engine/converter/ConverterUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/converter/ConverterUtils.java @@ -1,14 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.converter; +import java.io.IOException; +import java.util.List; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.engine.Engine; import org.restlet.engine.resource.VariantInfo; @@ -16,105 +18,113 @@ import org.restlet.representation.Variant; import org.restlet.resource.Resource; -import java.io.IOException; -import java.util.List; -import java.util.logging.Level; - /** * Utilities for the converter service. - * + * * @author Jerome Louvel */ public class ConverterUtils { - /** - * Returns the best converter helper matching the given parameters. - * - * @param source The object to convert to a representation. - * @param target The target representation variant. - * @param resource The optional parent resource. - * @return The matched converter helper or null. - */ - public static ConverterHelper getBestHelper(Object source, Variant target, Resource resource) { - ConverterHelper result = null; - float bestScore = -1.0F; - float currentScore; - - for (ConverterHelper ch : Engine.getInstance().getRegisteredConverters()) { - if (ch != null) { - try { - currentScore = ch.score(source, target, resource); - - if (currentScore > bestScore) { - bestScore = currentScore; - result = ch; - } - } catch (Exception e) { - Context.getCurrentLogger().log(Level.SEVERE, - "Unable get the score of the " + ch + " converter helper.", e); - } - } - } - - return result; - } - - /** - * Returns the best converter helper matching the given parameters. - * - * @param The target class. - * @param source The source representation variant. - * @param target The target class. - * @param resource The parent resource. - * @return The matched converter helper or null. - */ - public static ConverterHelper getBestHelper(Representation source, Class target, Resource resource) { - ConverterHelper result = null; - float bestScore = -1.0F; - float currentScore; - - for (ConverterHelper ch : Engine.getInstance().getRegisteredConverters()) { - if (ch != null) { - currentScore = ch.score(source, target, resource); - - if (currentScore > bestScore) { - bestScore = currentScore; - result = ch; - } - } - } - - return result; - } - - /** - * Returns the list of variants that can be converted from a given object class. - * - * @param sourceClass The source class. - * @param targetVariant The expected representation metadata. - * @return The list of variants that can be converted. - */ - public static List getVariants(Class sourceClass, Variant targetVariant) { - List result = null; - - for (ConverterHelper ch : Engine.getInstance().getRegisteredConverters()) { - if (ch != null) { - try { - result = ch.addVariants(sourceClass, targetVariant, result); - } catch (IOException e) { - Context.getCurrentLogger().log(Level.FINE, - "Unable get the variants of the " + ch + " converter helper.", e); - } - } - } - - return result; - } - - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e. it isn't instantiable and extensible. - */ - private ConverterUtils() { - } + /** + * Returns the best converter helper matching the given parameters. + * + * @param source The object to convert to a representation. + * @param target The target representation variant. + * @param resource The optional parent resource. + * @return The matched converter helper or null. + */ + public static ConverterHelper getBestHelper(Object source, Variant target, Resource resource) { + ConverterHelper result = null; + float bestScore = -1.0F; + float currentScore; + + for (ConverterHelper ch : Engine.getInstance().getRegisteredConverters()) { + if (ch != null) { + try { + currentScore = ch.score(source, target, resource); + + if (currentScore > bestScore) { + bestScore = currentScore; + result = ch; + } + } catch (Exception e) { + Context.getCurrentLogger() + .log( + Level.SEVERE, + e, + () -> + "Unable get the score of the " + + ch + + " converter helper."); + } + } + } + + return result; + } + + /** + * Returns the best converter helper matching the given parameters. + * + * @param The target class. + * @param source The source representation variant. + * @param target The target class. + * @param resource The parent resource. + * @return The matched converter helper or null. + */ + public static ConverterHelper getBestHelper( + Representation source, Class target, Resource resource) { + ConverterHelper result = null; + float bestScore = -1.0F; + float currentScore; + + for (ConverterHelper ch : Engine.getInstance().getRegisteredConverters()) { + if (ch != null) { + currentScore = ch.score(source, target, resource); + + if (currentScore > bestScore) { + bestScore = currentScore; + result = ch; + } + } + } + + return result; + } + + /** + * Returns the list of variants that can be converted from a given object class. + * + * @param sourceClass The source class. + * @param targetVariant The expected representation metadata. + * @return The list of variants that can be converted. + */ + public static List getVariants(Class sourceClass, Variant targetVariant) { + List result = null; + + for (ConverterHelper ch : Engine.getInstance().getRegisteredConverters()) { + if (ch != null) { + try { + result = ch.addVariants(sourceClass, targetVariant, result); + } catch (IOException e) { + Context.getCurrentLogger() + .log( + Level.FINE, + e, + () -> + "Unable get the variants of the " + + ch + + " converter helper."); + } + } + } + + return result; + } + + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private ConverterUtils() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/converter/DefaultConverter.java b/org.restlet/src/main/java/org/restlet/engine/converter/DefaultConverter.java index 6af4cbc3bf..689c3bc2b2 100644 --- a/org.restlet/src/main/java/org/restlet/engine/converter/DefaultConverter.java +++ b/org.restlet/src/main/java/org/restlet/engine/converter/DefaultConverter.java @@ -1,291 +1,328 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.converter; +import static org.restlet.data.MediaType.APPLICATION_OCTET_STREAM; +import static org.restlet.data.MediaType.TEXT_PLAIN; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.Serializable; +import java.util.List; import org.restlet.data.Form; import org.restlet.data.MediaType; import org.restlet.data.Preference; import org.restlet.engine.resource.VariantInfo; -import org.restlet.representation.*; +import org.restlet.representation.EmptyRepresentation; +import org.restlet.representation.FileRepresentation; +import org.restlet.representation.InputRepresentation; +import org.restlet.representation.ObjectRepresentation; +import org.restlet.representation.ReaderRepresentation; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.representation.Variant; import org.restlet.resource.Resource; -import java.io.*; -import java.util.List; - /** * Converter for the built-in Representation classes. - * + * * @author Jerome Louvel */ public class DefaultConverter extends ConverterHelper { - /** Neutral variant. */ - private static final VariantInfo VARIANT_ALL = new VariantInfo(MediaType.ALL); - - /** Web form variant. */ - private static final VariantInfo VARIANT_FORM = new VariantInfo(MediaType.APPLICATION_WWW_FORM); + /** Neutral variant. */ + private static final VariantInfo VARIANT_ALL = new VariantInfo(MediaType.ALL); - /** Octet stream variant. */ - private static final VariantInfo VARIANT_OBJECT = new VariantInfo(MediaType.APPLICATION_JAVA_OBJECT); + /** Web form variant. */ + private static final VariantInfo VARIANT_FORM = new VariantInfo(MediaType.APPLICATION_WWW_FORM); - /** Octet stream variant. */ - private static final VariantInfo VARIANT_OBJECT_XML = new VariantInfo(MediaType.APPLICATION_JAVA_OBJECT_XML); + /** Octet stream variant. */ + private static final VariantInfo VARIANT_OBJECT = + new VariantInfo(MediaType.APPLICATION_JAVA_OBJECT); - @Override - public List> getObjectClasses(Variant source) { - List> result = null; - result = addObjectClass(result, String.class); - result = addObjectClass(result, InputStream.class); - result = addObjectClass(result, Reader.class); + /** Octet stream variant. */ + private static final VariantInfo VARIANT_OBJECT_XML = + new VariantInfo(MediaType.APPLICATION_JAVA_OBJECT_XML); - if (source.getMediaType() != null) { - MediaType mediaType = source.getMediaType(); + @Override + public List> getObjectClasses(Variant source) { + List> result = null; + result = addObjectClass(result, String.class); + result = addObjectClass(result, InputStream.class); + result = addObjectClass(result, Reader.class); - if ((ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED - && MediaType.APPLICATION_JAVA_OBJECT.equals(mediaType)) - || (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED - && MediaType.APPLICATION_JAVA_OBJECT_XML.equals(mediaType))) { - result = addObjectClass(result, Object.class); - } else if (MediaType.APPLICATION_WWW_FORM.equals(mediaType)) { - result = addObjectClass(result, Form.class); - } - } + if (source.getMediaType() != null) { + MediaType mediaType = source.getMediaType(); - return result; - } + if ((ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED + && MediaType.APPLICATION_JAVA_OBJECT.equals(mediaType)) + || (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED + && MediaType.APPLICATION_JAVA_OBJECT_XML.equals(mediaType))) { + result = addObjectClass(result, Object.class); + } else if (MediaType.APPLICATION_WWW_FORM.equals(mediaType)) { + result = addObjectClass(result, Form.class); + } + } - @Override - public List getVariants(Class source) { - List result = null; + return result; + } - if (source != null) { - if (String.class.isAssignableFrom(source) || StringRepresentation.class.isAssignableFrom(source)) { - result = addVariant(result, VARIANT_ALL); - } else if (File.class.isAssignableFrom(source) || FileRepresentation.class.isAssignableFrom(source)) { - result = addVariant(result, VARIANT_ALL); - } else if (InputStream.class.isAssignableFrom(source) - || InputRepresentation.class.isAssignableFrom(source)) { - result = addVariant(result, VARIANT_ALL); - } else if (Reader.class.isAssignableFrom(source) || ReaderRepresentation.class.isAssignableFrom(source)) { - result = addVariant(result, VARIANT_ALL); - } else if (Representation.class.isAssignableFrom(source)) { - result = addVariant(result, VARIANT_ALL); - } else if (Form.class.isAssignableFrom(source)) { - result = addVariant(result, VARIANT_FORM); - } else if (Serializable.class.isAssignableFrom(source)) { - if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED) { - result = addVariant(result, VARIANT_OBJECT); - } - if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED) { - result = addVariant(result, VARIANT_OBJECT_XML); - } - } - } + @Override + public List getVariants(Class source) { + if (source == null) { + return List.of(); + } - return result; - } + List result = null; + if (String.class.isAssignableFrom(source) + || StringRepresentation.class.isAssignableFrom(source)) { + result = addVariant(result, VARIANT_ALL); + } else if (File.class.isAssignableFrom(source) + || FileRepresentation.class.isAssignableFrom(source)) { + result = addVariant(result, VARIANT_ALL); + } else if (InputStream.class.isAssignableFrom(source) + || InputRepresentation.class.isAssignableFrom(source)) { + result = addVariant(result, VARIANT_ALL); + } else if (Reader.class.isAssignableFrom(source) + || ReaderRepresentation.class.isAssignableFrom(source)) { + result = addVariant(result, VARIANT_ALL); + } else if (Representation.class.isAssignableFrom(source)) { + result = addVariant(result, VARIANT_ALL); + } else if (Form.class.isAssignableFrom(source)) { + result = addVariant(result, VARIANT_FORM); + } else if (Serializable.class.isAssignableFrom(source)) { + if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED) { + result = addVariant(result, VARIANT_OBJECT); + } + if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED) { + result = addVariant(result, VARIANT_OBJECT_XML); + } + } - @Override - public float score(Object source, Variant target, Resource resource) { - float result = -1.0F; + return result; + } - if (source instanceof String) { - result = 1.0F; - } else if (source instanceof File) { - result = 1.0F; - } else if (source instanceof Form) { - if ((target != null) && MediaType.APPLICATION_WWW_FORM.isCompatible(target.getMediaType())) { - result = 1.0F; - } else { - result = 0.6F; - } - } else if (source instanceof InputStream) { - result = 1.0F; - } else if (source instanceof Reader) { - result = 1.0F; - } else if (source instanceof Representation) { - result = 1.0F; - } else if (source instanceof Serializable) { - if (target != null) { - if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED - && MediaType.APPLICATION_JAVA_OBJECT.equals(target.getMediaType())) { - result = 1.0F; - } else if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED - && MediaType.APPLICATION_JAVA_OBJECT.isCompatible(target.getMediaType())) { - result = 0.6F; - } else if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED - && MediaType.APPLICATION_JAVA_OBJECT_XML.equals(target.getMediaType())) { - result = 1.0F; - } else if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED - && MediaType.APPLICATION_JAVA_OBJECT_XML.isCompatible(target.getMediaType())) { - result = 0.6F; - } - } else if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED) { - result = 0.5F; - } - } + @Override + public float score(Object source, Variant target, Resource resource) { + float result = -1.0F; - return result; - } + if (source instanceof String) { + result = 1.0F; + } else if (source instanceof File) { + result = 1.0F; + } else if (source instanceof Form) { + if ((target != null) + && MediaType.APPLICATION_WWW_FORM.isCompatible(target.getMediaType())) { + result = 1.0F; + } else { + result = 0.6F; + } + } else if (source instanceof InputStream) { + result = 1.0F; + } else if (source instanceof Reader) { + result = 1.0F; + } else if (source instanceof Representation) { + result = 1.0F; + } else if (source instanceof Serializable) { + if (target != null) { + if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED + && MediaType.APPLICATION_JAVA_OBJECT.equals(target.getMediaType())) { + result = 1.0F; + } else if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED + && MediaType.APPLICATION_JAVA_OBJECT.isCompatible(target.getMediaType())) { + result = 0.6F; + } else if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED + && MediaType.APPLICATION_JAVA_OBJECT_XML.equals(target.getMediaType())) { + result = 1.0F; + } else if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED + && MediaType.APPLICATION_JAVA_OBJECT_XML.isCompatible( + target.getMediaType())) { + result = 0.6F; + } + } else if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED) { + result = 0.5F; + } + } - @Override - public float score(Representation source, Class target, Resource resource) { - float result = -1.0F; + return result; + } - if (target != null) { - if (target.isAssignableFrom(source.getClass())) { - result = 1.0F; - } else if (String.class.isAssignableFrom(target)) { - result = 1.0F; - } else if (StringRepresentation.class.isAssignableFrom(target)) { - result = 1.0F; - } else if (EmptyRepresentation.class.isAssignableFrom(target)) { - result = 1.0F; - } else if (File.class.isAssignableFrom(target)) { - if (source instanceof FileRepresentation) { - result = 1.0F; - } - } else if (Form.class.isAssignableFrom(target)) { - if (MediaType.APPLICATION_WWW_FORM.isCompatible(source.getMediaType())) { - result = 1.0F; - } else { - result = 0.5F; - } - } else if (InputStream.class.isAssignableFrom(target)) { - result = 1.0F; - } else if (InputRepresentation.class.isAssignableFrom(target)) { - result = 1.0F; - } else if (Reader.class.isAssignableFrom(target)) { - result = 1.0F; - } else if (ReaderRepresentation.class.isAssignableFrom(target)) { - result = 1.0F; - } else if (Serializable.class.isAssignableFrom(target) || target.isPrimitive()) { - if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED - && MediaType.APPLICATION_JAVA_OBJECT.equals(source.getMediaType())) { - result = 1.0F; - } else if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED - && MediaType.APPLICATION_JAVA_OBJECT.isCompatible(source.getMediaType())) { - result = 0.6F; - } else if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED - && MediaType.APPLICATION_JAVA_OBJECT_XML.equals(source.getMediaType())) { - result = 1.0F; - } else if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED - && MediaType.APPLICATION_JAVA_OBJECT_XML.isCompatible(source.getMediaType())) { - result = 0.6F; - } else { - result = 0.5F; - } - } - } else if (source instanceof ObjectRepresentation) { - result = 1.0F; - } + @Override + public float score(Representation source, Class target, Resource resource) { + float result = -1.0F; - return result; - } + if (target != null) { + if (target.isAssignableFrom(source.getClass())) { + result = 1.0F; + } else if (String.class.isAssignableFrom(target)) { + result = 1.0F; + } else if (StringRepresentation.class.isAssignableFrom(target)) { + result = 1.0F; + } else if (EmptyRepresentation.class.isAssignableFrom(target)) { + result = 1.0F; + } else if (File.class.isAssignableFrom(target)) { + if (source instanceof FileRepresentation) { + result = 1.0F; + } + } else if (Form.class.isAssignableFrom(target)) { + if (MediaType.APPLICATION_WWW_FORM.isCompatible(source.getMediaType())) { + result = 1.0F; + } else { + result = 0.5F; + } + } else if (InputStream.class.isAssignableFrom(target)) { + result = 1.0F; + } else if (InputRepresentation.class.isAssignableFrom(target)) { + result = 1.0F; + } else if (Reader.class.isAssignableFrom(target)) { + result = 1.0F; + } else if (ReaderRepresentation.class.isAssignableFrom(target)) { + result = 1.0F; + } else if (Serializable.class.isAssignableFrom(target) || target.isPrimitive()) { + if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED + && MediaType.APPLICATION_JAVA_OBJECT.equals(source.getMediaType())) { + result = 1.0F; + } else if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED + && MediaType.APPLICATION_JAVA_OBJECT.isCompatible(source.getMediaType())) { + result = 0.6F; + } else if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED + && MediaType.APPLICATION_JAVA_OBJECT_XML.equals(source.getMediaType())) { + result = 1.0F; + } else if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED + && MediaType.APPLICATION_JAVA_OBJECT_XML.isCompatible( + source.getMediaType())) { + result = 0.6F; + } else { + result = 0.5F; + } + } + } else if (source instanceof ObjectRepresentation) { + result = 1.0F; + } - @SuppressWarnings("unchecked") - @Override - public T toObject(Representation source, Class target, Resource resource) throws IOException { - Object result = null; + return result; + } - if (target != null) { - if (target.isAssignableFrom(source.getClass())) { - result = source; - } else if (String.class.isAssignableFrom(target)) { - result = source.getText(); - } else if (StringRepresentation.class.isAssignableFrom(target)) { - result = new StringRepresentation(source.getText(), source.getMediaType()); - } else if (EmptyRepresentation.class.isAssignableFrom(target)) { - result = null; - } else if (File.class.isAssignableFrom(target)) { - if (source instanceof FileRepresentation) { - result = ((FileRepresentation) source).getFile(); - } else { - result = null; - } - } else if (Form.class.isAssignableFrom(target)) { - result = new Form(source); - } else if (InputStream.class.isAssignableFrom(target)) { - result = source.getStream(); - } else if (InputRepresentation.class.isAssignableFrom(target)) { - result = new InputRepresentation(source.getStream()); - } else if (Reader.class.isAssignableFrom(target)) { - result = source.getReader(); - } else if (ReaderRepresentation.class.isAssignableFrom(target)) { - result = new ReaderRepresentation(source.getReader()); - } else if (Serializable.class.isAssignableFrom(target) || target.isPrimitive()) { - if (source instanceof ObjectRepresentation) { - result = ((ObjectRepresentation) source).getObject(); - } else { - try { - result = new ObjectRepresentation(source).getObject(); - } catch (Exception e) { - IOException ioe = new IOException("Unable to create the Object representation"); - ioe.initCause(e); - throw ioe; - } - } - } - } else if (source instanceof ObjectRepresentation) { - result = ((ObjectRepresentation) source).getObject(); - } + @SuppressWarnings("unchecked") + @Override + public T toObject(Representation source, Class target, Resource resource) + throws IOException { + final Object result; - return (T) result; - } + if (target != null) { + if (target.isAssignableFrom(source.getClass())) { + result = source; + } else if (String.class.isAssignableFrom(target)) { + result = source.getText(); + } else if (StringRepresentation.class.isAssignableFrom(target)) { + result = new StringRepresentation(source.getText(), source.getMediaType()); + } else if (EmptyRepresentation.class.isAssignableFrom(target)) { + result = null; + } else if (File.class.isAssignableFrom(target)) { + if (source instanceof FileRepresentation fileRepresentation) { + result = fileRepresentation.getFile(); + } else { + result = null; + } + } else if (Form.class.isAssignableFrom(target)) { + result = new Form(source); + } else if (InputStream.class.isAssignableFrom(target)) { + result = source.getStream(); + } else if (InputRepresentation.class.isAssignableFrom(target)) { + result = new InputRepresentation(source.getStream()); + } else if (Reader.class.isAssignableFrom(target)) { + result = source.getReader(); + } else if (ReaderRepresentation.class.isAssignableFrom(target)) { + result = new ReaderRepresentation(source.getReader()); + } else if (Serializable.class.isAssignableFrom(target) || target.isPrimitive()) { + if (source instanceof ObjectRepresentation objectRepresentation) { + result = objectRepresentation.getObject(); + } else { + try { + result = new ObjectRepresentation<>(source).getObject(); + } catch (Exception e) { + IOException ioe = + new IOException("Unable to create the Object representation"); + ioe.initCause(e); + throw ioe; + } + } + } else { + result = null; + } + } else if (source instanceof ObjectRepresentation objectRepresentation) { + result = objectRepresentation.getObject(); + } else { + result = null; + } - @Override - public Representation toRepresentation(Object source, Variant target, Resource resource) throws IOException { - Representation result = null; + return (T) result; + } - if (source instanceof String) { - result = new StringRepresentation((String) source, - MediaType.getMostSpecific(target.getMediaType(), MediaType.TEXT_PLAIN)); - } else if (source instanceof File) { - result = new FileRepresentation((File) source, - MediaType.getMostSpecific(target.getMediaType(), MediaType.APPLICATION_OCTET_STREAM)); - } else if (source instanceof Form) { - result = ((Form) source).getWebRepresentation(); - } else if (source instanceof InputStream) { - result = new InputRepresentation((InputStream) source, - MediaType.getMostSpecific(target.getMediaType(), MediaType.APPLICATION_OCTET_STREAM)); - } else if (source instanceof Reader) { - result = new ReaderRepresentation((Reader) source, - MediaType.getMostSpecific(target.getMediaType(), MediaType.TEXT_PLAIN)); - } else if (source instanceof Representation) { - result = (Representation) source; - } else if (source instanceof Serializable) { - result = new ObjectRepresentation((Serializable) source, - MediaType.getMostSpecific(target.getMediaType(), MediaType.APPLICATION_OCTET_STREAM)); - } + @Override + public Representation toRepresentation(Object source, Variant target, Resource resource) + throws IOException { + final Representation result; - return result; - } + switch (source) { + case String string -> { + final MediaType mediaType = + MediaType.getMostSpecific(target.getMediaType(), TEXT_PLAIN); + result = new StringRepresentation(string, mediaType); + } + case File file -> { + final MediaType mediaType = + MediaType.getMostSpecific(target.getMediaType(), APPLICATION_OCTET_STREAM); + result = new FileRepresentation(file, mediaType); + } + case Form form -> result = form.getWebRepresentation(); + case InputStream inputStream -> { + final MediaType mediaType = + MediaType.getMostSpecific(target.getMediaType(), APPLICATION_OCTET_STREAM); + result = new InputRepresentation(inputStream, mediaType); + } + case Reader reader -> { + final MediaType mediaType = + MediaType.getMostSpecific(target.getMediaType(), TEXT_PLAIN); + result = new ReaderRepresentation(reader, mediaType); + } + case Representation representation -> result = representation; + case Serializable serializable -> { + final MediaType mediaType = + MediaType.getMostSpecific(target.getMediaType(), APPLICATION_OCTET_STREAM); + result = new ObjectRepresentation<>(serializable, mediaType); + } + case null, default -> result = null; + } - @Override - public void updatePreferences(List> preferences, Class entity) { - if (Form.class.isAssignableFrom(entity)) { - updatePreferences(preferences, MediaType.APPLICATION_WWW_FORM, 1.0F); - } else if (Serializable.class.isAssignableFrom(entity)) { - if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED) { - updatePreferences(preferences, MediaType.APPLICATION_JAVA_OBJECT, 1.0F); - } - if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED) { - updatePreferences(preferences, MediaType.APPLICATION_JAVA_OBJECT_XML, 1.0F); - } - } else if (String.class.isAssignableFrom(entity) || Reader.class.isAssignableFrom(entity)) { - updatePreferences(preferences, MediaType.TEXT_PLAIN, 1.0F); - updatePreferences(preferences, MediaType.TEXT_ALL, 0.5F); - } else if (InputStream.class.isAssignableFrom(entity)) { - updatePreferences(preferences, MediaType.APPLICATION_OCTET_STREAM, 1.0F); - updatePreferences(preferences, MediaType.APPLICATION_ALL, 0.5F); - } - } + return result; + } + @Override + public void updatePreferences(List> preferences, Class entity) { + if (Form.class.isAssignableFrom(entity)) { + updatePreferences(preferences, MediaType.APPLICATION_WWW_FORM, 1.0F); + } else if (Serializable.class.isAssignableFrom(entity)) { + if (ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED) { + updatePreferences(preferences, MediaType.APPLICATION_JAVA_OBJECT, 1.0F); + } + if (ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED) { + updatePreferences(preferences, MediaType.APPLICATION_JAVA_OBJECT_XML, 1.0F); + } + } else if (String.class.isAssignableFrom(entity) || Reader.class.isAssignableFrom(entity)) { + updatePreferences(preferences, TEXT_PLAIN, 1.0F); + updatePreferences(preferences, MediaType.TEXT_ALL, 0.5F); + } else if (InputStream.class.isAssignableFrom(entity)) { + updatePreferences(preferences, APPLICATION_OCTET_STREAM, 1.0F); + updatePreferences(preferences, MediaType.APPLICATION_ALL, 0.5F); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/converter/StatusInfoHtmlConverter.java b/org.restlet/src/main/java/org/restlet/engine/converter/StatusInfoHtmlConverter.java index edd5db09d5..0493ef6faf 100644 --- a/org.restlet/src/main/java/org/restlet/engine/converter/StatusInfoHtmlConverter.java +++ b/org.restlet/src/main/java/org/restlet/engine/converter/StatusInfoHtmlConverter.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.converter; +import java.io.IOException; +import java.util.List; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.MediaType; @@ -22,149 +23,152 @@ import org.restlet.resource.Resource; import org.restlet.service.StatusService; -import java.io.IOException; -import java.util.List; - /** * Converter for the {@link StatusInfo} class. - * + * * @author Manuel Boillod */ public class StatusInfoHtmlConverter extends ConverterHelper { - /** Variant with media type application/xhtml+xml. */ - private static final VariantInfo VARIANT_APPLICATION_XHTML = new VariantInfo(MediaType.APPLICATION_XHTML); - - /** Variant with media type text/html. */ - private static final VariantInfo VARIANT_TEXT_HTML = new VariantInfo(MediaType.TEXT_HTML); - - @Override - public List> getObjectClasses(Variant source) { - List> result = null; - - if (isCompatible(source)) { - result = addObjectClass(result, StatusInfo.class); - } - - return result; - } - - /** - * Returns the status information to display in the default representation. By - * default it returns the status's reason phrase. - * - * @param status The status. - * @return The status information. - * @see StatusService#toRepresentation(Status, Request, Response) - */ - protected String getStatusLabel(StatusInfo status) { - return (status.getReasonPhrase() != null) ? status.getReasonPhrase() - : "No information available for this result status"; - } - - @Override - public List getVariants(Class source) throws IOException { - List result = null; - - if (source != null && StatusInfo.class.isAssignableFrom(source)) { - result = addVariant(result, VARIANT_TEXT_HTML); - result = addVariant(result, VARIANT_APPLICATION_XHTML); - } - - return result; - } - - /** - * Indicates if the given variant is compatible with the media types supported - * by this converter. - * - * @param variant The variant. - * @return True if the given variant is compatible with the media types - * supported by this converter. - */ - protected boolean isCompatible(Variant variant) { - return (variant != null) - && (VARIANT_TEXT_HTML.isCompatible(variant) || VARIANT_APPLICATION_XHTML.isCompatible(variant)); - } - - @Override - public float score(Object source, Variant target, Resource resource) { - float result = -1.0F; - - if (source instanceof StatusInfo && isCompatible(target)) { - result = 1.0F; - } - - return result; - } - - @Override - public float score(Representation source, Class target, Resource resource) { - return -1.0F; - } - - /** - * Returns a representation for the given status.
- * In order to customize the default representation, this method can be - * overridden. - * - * @param status The status info to represent. - * @return The representation of the given status. - */ - protected Representation toHtml(StatusInfo status) { - final StringBuilder sb = new StringBuilder(); - sb.append("\n"); - sb.append("\n"); - sb.append(" Status page\n"); - sb.append("\n"); - sb.append("\n"); - - sb.append("

"); - sb.append(StringUtils.htmlEscape(getStatusLabel(status))); - sb.append("

\n"); - if (status.getDescription() != null) { - sb.append("

"); - sb.append(StringUtils.htmlEscape(status.getDescription())); - sb.append("

\n"); - } - - sb.append("

You can get technical details here.
\n"); - - if (status.getContactEmail() != null) { - sb.append("For further assistance, you can contact the administrator.
\n"); - } - - if (status.getHomeRef() != null) { - sb.append("Please continue your visit at our home page.\n"); - } - - sb.append("

\n"); - sb.append("\n"); - sb.append("\n"); - - return new StringRepresentation(sb.toString(), MediaType.TEXT_HTML); - } - - @Override - public T toObject(Representation source, Class target, Resource resource) throws IOException { - return null; - } - - @Override - public Representation toRepresentation(Object source, Variant target, Resource resource) throws IOException { - Representation result = null; - - if (source != null && StatusInfo.class.isAssignableFrom(source.getClass())) { - StatusInfo si = (StatusInfo) source; - result = toHtml(si); - } - - return result; - } -} \ No newline at end of file + /** Variant with media type application/xhtml+xml. */ + private static final VariantInfo VARIANT_APPLICATION_XHTML = + new VariantInfo(MediaType.APPLICATION_XHTML); + + /** Variant with media type text/html. */ + private static final VariantInfo VARIANT_TEXT_HTML = new VariantInfo(MediaType.TEXT_HTML); + + @Override + public List> getObjectClasses(Variant source) { + List> result = null; + + if (isCompatible(source)) { + result = addObjectClass(result, StatusInfo.class); + } + + return result; + } + + /** + * Returns the status information to display in the default representation. By default, it + * returns the status's reason phrase. + * + * @param status The status. + * @return The status information. + * @see StatusService#toRepresentation(Status, Request, Response) + */ + protected String getStatusLabel(StatusInfo status) { + return (status.getReasonPhrase() != null) + ? status.getReasonPhrase() + : "No information available for this result status"; + } + + @Override + public List getVariants(Class source) throws IOException { + List result = null; + + if (source != null && StatusInfo.class.isAssignableFrom(source)) { + result = addVariant(result, VARIANT_TEXT_HTML); + result = addVariant(result, VARIANT_APPLICATION_XHTML); + } + + return result; + } + + /** + * Indicates if the given variant is compatible with the media types supported by this + * converter. + * + * @param variant The variant. + * @return True if the given variant is compatible with the media types supported by this + * converter. + */ + protected boolean isCompatible(Variant variant) { + return (variant != null) + && (VARIANT_TEXT_HTML.isCompatible(variant) + || VARIANT_APPLICATION_XHTML.isCompatible(variant)); + } + + @Override + public float score(Object source, Variant target, Resource resource) { + float result = -1.0F; + + if (source instanceof StatusInfo && isCompatible(target)) { + result = 1.0F; + } + + return result; + } + + @Override + public float score(Representation source, Class target, Resource resource) { + return -1.0F; + } + + /** + * Returns a representation for the given status.
+ * To customize the default representation, this method can be overridden. + * + * @param status The status info to represent. + * @return The representation of the given status. + */ + protected Representation toHtml(StatusInfo status) { + final StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("\n"); + sb.append(" Status page\n"); + sb.append("\n"); + sb.append("\n"); + + sb.append("

"); + sb.append(StringUtils.htmlEscape(getStatusLabel(status))); + sb.append("

\n"); + if (status.getDescription() != null) { + sb.append("

"); + sb.append(StringUtils.htmlEscape(status.getDescription())); + sb.append("

\n"); + } + + sb.append("

You can get technical details here.
\n"); + + if (status.getContactEmail() != null) { + sb.append("For further assistance, you can contact the administrator.
\n"); + } + + if (status.getHomeRef() != null) { + sb.append("Please continue your visit at our home page.\n"); + } + + sb.append("

\n"); + sb.append("\n"); + sb.append("\n"); + + return new StringRepresentation(sb.toString(), MediaType.TEXT_HTML); + } + + @Override + public T toObject(Representation source, Class target, Resource resource) + throws IOException { + return null; + } + + @Override + public Representation toRepresentation(Object source, Variant target, Resource resource) + throws IOException { + final Representation result; + + if (source != null && StatusInfo.class.isAssignableFrom(source.getClass())) { + StatusInfo si = (StatusInfo) source; + result = toHtml(si); + } else { + result = null; + } + + return result; + } +} diff --git a/org.restlet/src/main/java/org/restlet/engine/converter/package-info.java b/org.restlet/src/main/java/org/restlet/engine/converter/package-info.java new file mode 100644 index 0000000000..57ea819d75 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/converter/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports the converter service. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.converter; diff --git a/org.restlet/src/main/java/org/restlet/engine/converter/package.html b/org.restlet/src/main/java/org/restlet/engine/converter/package.html deleted file mode 100644 index 9464ed1a1c..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/converter/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Supports the converter service. -

-@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/header/CacheDirectiveReader.java b/org.restlet/src/main/java/org/restlet/engine/header/CacheDirectiveReader.java index 0217b83c80..f00fd1aac7 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/CacheDirectiveReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/CacheDirectiveReader.java @@ -1,49 +1,46 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.CacheDirective; -import org.restlet.data.Header; - import java.io.IOException; import java.util.Collection; +import org.restlet.data.CacheDirective; +import org.restlet.data.Header; /** * Cache directive header reader. - * + * * @author Jerome Louvel */ public class CacheDirectiveReader extends HeaderReader { - /** - * Adds values to the given collection. - * - * @param header The header to read. - * @param collection The collection to update. - */ - public static void addValues(Header header, Collection collection) { - new CacheDirectiveReader(header.getValue()).addValues(collection); - } - - /** - * Constructor. - * - * @param header The header to read. - */ - public CacheDirectiveReader(String header) { - super(header); - } - - @Override - public CacheDirective readValue() throws IOException { - return readNamedValue(CacheDirective.class); - } - + /** + * Adds values to the given collection. + * + * @param header The header to read. + * @param collection The collection to update. + */ + public static void addValues(Header header, Collection collection) { + new CacheDirectiveReader(header.getValue()).addValues(collection); + } + + /** + * Constructor. + * + * @param header The header to read. + */ + public CacheDirectiveReader(String header) { + super(header); + } + + @Override + public CacheDirective readValue() throws IOException { + return readNamedValue(CacheDirective.class); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/CacheDirectiveWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/CacheDirectiveWriter.java index d6378ae256..9d92c9a0b4 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/CacheDirectiveWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/CacheDirectiveWriter.java @@ -1,39 +1,36 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.CacheDirective; - import java.util.List; +import org.restlet.data.CacheDirective; /** * Cache directive header writer. - * + * * @author Thierry Boileau */ public class CacheDirectiveWriter extends HeaderWriter { - /** - * Writes a list of cache directives with a comma separator. - * - * @param directives The list of cache directives. - * @return The formatted list of cache directives. - */ - public static String write(List directives) { - return new CacheDirectiveWriter().append(directives).toString(); - } - - @Override - public CacheDirectiveWriter append(CacheDirective directive) { - appendExtension(directive); - return this; - } + /** + * Writes a list of cache directives with a comma separator. + * + * @param directives The list of cache directives. + * @return The formatted list of cache directives. + */ + public static String write(List directives) { + return new CacheDirectiveWriter().append(directives).toString(); + } + @Override + public CacheDirectiveWriter append(CacheDirective directive) { + appendExtension(directive); + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/ChallengeRequestReader.java b/org.restlet/src/main/java/org/restlet/engine/header/ChallengeRequestReader.java index 2d99dc67b1..1783589cd6 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/ChallengeRequestReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/ChallengeRequestReader.java @@ -1,86 +1,85 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import static org.restlet.engine.header.HeaderUtils.isSpace; + +import java.io.IOException; import org.restlet.data.ChallengeRequest; import org.restlet.data.ChallengeScheme; import org.restlet.data.Parameter; -import java.io.IOException; - -import static org.restlet.engine.header.HeaderUtils.isSpace; - /** * Challenge request header reader. - * + * * @author Thierry Boileau */ public class ChallengeRequestReader extends HeaderReader { - /** - * Constructor. - * - * @param header The header to read. - */ - public ChallengeRequestReader(String header) { - super(header); - } + /** + * Constructor. + * + * @param header The header to read. + */ + public ChallengeRequestReader(String header) { + super(header); + } - @Override - public ChallengeRequest readValue() throws IOException { - ChallengeRequest result = null; + @Override + public ChallengeRequest readValue() throws IOException { + ChallengeRequest result = null; - // The challenge is that this header is a comma separated lst of - // challenges, and that each challenge is also a comma separated list, - // but of parameters. - skipSpaces(); - if (peek() != -1) { - String scheme = readToken(); - result = new ChallengeRequest(new ChallengeScheme("HTTP_" + scheme, scheme)); - skipSpaces(); + // The challenge is that this header is a comma separated lst of + // challenges, and that each challenge is also a comma-separated list, + // but of parameters. + skipSpaces(); + if (peek() != -1) { + String scheme = readToken(); + result = new ChallengeRequest(new ChallengeScheme("HTTP_" + scheme, scheme)); + skipSpaces(); - // Header writer that will reconstruct the raw value of a challenge. - HeaderWriter w = new HeaderWriter() { - @Override - public HeaderWriter append(Parameter value) { - appendExtension(value); - return this; - } - }; + // Header writer that will reconstruct the raw value of a challenge. + HeaderWriter w = + new HeaderWriter() { + @Override + public HeaderWriter append(Parameter value) { + appendExtension(value); + return this; + } + }; - boolean stop = false; - while (peek() != -1 && !stop) { - boolean sepSkipped = skipValueSeparator(); - // Record the start of the segment - mark(); - // Read a token and the next character. - readToken(); - int nextChar = read(); - reset(); - if (isSpace(nextChar)) { - // A new scheme has been discovered. - stop = true; - } else { - // The next segment is considered as a parameter - if (sepSkipped) { - // Add the skipped value separator. - w.appendValueSeparator(); - } - // Append the parameter - w.append(readParameter()); - } - } - result.setRawValue(w.toString()); - w.close(); - } + boolean stop = false; + while (peek() != -1 && !stop) { + boolean sepSkipped = skipValueSeparator(); + // Record the start of the segment + mark(); + // Read a token and the next character. + readToken(); + int nextChar = read(); + reset(); + if (isSpace(nextChar)) { + // A new scheme has been discovered. + stop = true; + } else { + // The next segment is considered as a parameter + if (sepSkipped) { + // Add the skipped value separator. + w.appendValueSeparator(); + } + // Append the parameter + w.append(readParameter()); + } + } + result.setRawValue(w.toString()); + w.close(); + } - return result; - } + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/ChallengeWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/ChallengeWriter.java index c35662b504..e6fccc6168 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/ChallengeWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/ChallengeWriter.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; import org.restlet.data.ChallengeRequest; @@ -14,137 +13,134 @@ /** * Authentication challenge header writer. - * + * * @author Jerome Louvel */ public class ChallengeWriter extends HeaderWriter { - /** Indicates if the first challenge parameter is written. */ - private volatile boolean firstChallengeParameter; - - /** - * Constructor. - */ - public ChallengeWriter() { - this.firstChallengeParameter = true; - } - - @Override - public HeaderWriter append(ChallengeRequest value) { - return this; - } - - /** - * Appends a new challenge parameter, prefixed with a comma. The value is - * separated from the name by an '=' character. - * - * @param parameter The parameter. - * @return This writer. - */ - public ChallengeWriter appendChallengeParameter(Parameter parameter) { - return appendChallengeParameter(parameter.getName(), parameter.getValue()); - } - - /** - * Appends a new parameter, prefixed with a comma. - * - * @param name The parameter name. - * @return The current builder. - */ - public ChallengeWriter appendChallengeParameter(String name) { - appendChallengeParameterSeparator(); - appendToken(name); - return this; - } - - /** - * Appends a new parameter, prefixed with a comma. The value is separated from - * the name by an '=' character. - * - * @param name The parameter name. - * @param value The parameter value. - * @return This writer. - */ - public ChallengeWriter appendChallengeParameter(String name, String value) { - appendChallengeParameterSeparator(); - - if (name != null) { - appendToken(name); - } - - if (value != null) { - append('='); - appendToken(value); - } - - return this; - } - - /** - * Appends a comma as a separator if the first parameter has already been - * written. - * - * @return This writer. - */ - public ChallengeWriter appendChallengeParameterSeparator() { - if (isFirstChallengeParameter()) { - setFirstChallengeParameter(false); - } else { - append(", "); - } - - return this; - } - - /** - * Appends a new parameter, prefixed with a comma. The value is separated from - * the name by an '=' character. - * - * @param parameter The parameter. - * @return This writer. - */ - public ChallengeWriter appendQuotedChallengeParameter(Parameter parameter) { - return appendQuotedChallengeParameter(parameter.getName(), parameter.getValue()); - } - - /** - * Appends a new parameter, prefixed with a comma. The value is quoted and - * separated from the name by an '=' character. - * - * @param name The parameter name. - * @param value The parameter value to quote. - * @return This writer. - */ - public ChallengeWriter appendQuotedChallengeParameter(String name, String value) { - appendChallengeParameterSeparator(); - - if (name != null) { - appendToken(name); - } - - if (value != null) { - append('='); - appendQuotedString(value); - } - - return this; - } - - /** - * Indicates if the first comma-separated value is written. - * - * @return True if the first comma-separated value is written. - */ - public boolean isFirstChallengeParameter() { - return firstChallengeParameter; - } - - /** - * Indicates if the first comma-separated value is written. - * - * @param firstValue True if the first comma-separated value is written. - */ - public void setFirstChallengeParameter(boolean firstValue) { - this.firstChallengeParameter = firstValue; - } + /** Indicates if the first challenge parameter is written. */ + private volatile boolean firstChallengeParameter; + + /** Constructor. */ + public ChallengeWriter() { + this.firstChallengeParameter = true; + } + + @Override + public HeaderWriter append(ChallengeRequest value) { + return this; + } + + /** + * Appends a new challenge parameter, prefixed with a comma. The value is separated from the + * name by a '=' character. + * + * @param parameter The parameter. + * @return This writer. + */ + public ChallengeWriter appendChallengeParameter(Parameter parameter) { + return appendChallengeParameter(parameter.getName(), parameter.getValue()); + } + + /** + * Appends a new parameter, prefixed with a comma. + * + * @param name The parameter name. + * @return The current builder. + */ + public ChallengeWriter appendChallengeParameter(String name) { + appendChallengeParameterSeparator(); + appendToken(name); + return this; + } + + /** + * Appends a new parameter, prefixed with a comma. The value is separated from the name by a '=' + * character. + * + * @param name The parameter name. + * @param value The parameter value. + * @return This writer. + */ + public ChallengeWriter appendChallengeParameter(String name, String value) { + appendChallengeParameterSeparator(); + + if (name != null) { + appendToken(name); + } + + if (value != null) { + append('='); + appendToken(value); + } + + return this; + } + + /** + * Appends a comma as a separator if the first parameter has already been written. + * + * @return This writer. + */ + public ChallengeWriter appendChallengeParameterSeparator() { + if (isFirstChallengeParameter()) { + setFirstChallengeParameter(false); + } else { + append(", "); + } + + return this; + } + + /** + * Appends a new parameter, prefixed with a comma. The value is separated from the name by a '=' + * character. + * + * @param parameter The parameter. + * @return This writer. + */ + public ChallengeWriter appendQuotedChallengeParameter(Parameter parameter) { + return appendQuotedChallengeParameter(parameter.getName(), parameter.getValue()); + } + + /** + * Appends a new parameter, prefixed with a comma. The value is quoted and separated from the + * name by a '=' character. + * + * @param name The parameter name. + * @param value The parameter value to quote. + * @return This writer. + */ + public ChallengeWriter appendQuotedChallengeParameter(String name, String value) { + appendChallengeParameterSeparator(); + + if (name != null) { + appendToken(name); + } + + if (value != null) { + append('='); + appendQuotedString(value); + } + + return this; + } + + /** + * Indicates if the first comma-separated value is written. + * + * @return True if the first comma-separated value is written. + */ + public boolean isFirstChallengeParameter() { + return firstChallengeParameter; + } + + /** + * Indicates if the first comma-separated value is written. + * + * @param firstValue True if the first comma-separated value is written. + */ + public void setFirstChallengeParameter(boolean firstValue) { + this.firstChallengeParameter = firstValue; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/ContentType.java b/org.restlet/src/main/java/org/restlet/engine/header/ContentType.java index 8d8737fec5..aa39df8c1e 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/ContentType.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/ContentType.java @@ -1,160 +1,152 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.io.IOException; import org.restlet.data.CharacterSet; import org.restlet.data.MediaType; import org.restlet.data.Parameter; import org.restlet.representation.Representation; -import java.io.IOException; - /** - * Association of a media type, a character set and modifiers. - * + * Association of a media type, a character set, and modifiers. + * * @author Jerome Louvel */ public class ContentType { - /** - * Parses the given content type header and returns the character set. - * - * @param contentType The content type header to parse. - * @return The character set. - */ - public static CharacterSet readCharacterSet(String contentType) { - return new ContentType(contentType).getCharacterSet(); - } - - /** - * Parses the given content type header and returns the media type. - * - * @param contentType The content type header to parse. - * @return The media type. - */ - public static MediaType readMediaType(String contentType) { - return new ContentType(contentType).getMediaType(); - } - - /** - * Writes the HTTP "Content-Type" header. - * - * @param mediaType The representation media type. - * @param characterSet The representation character set. - * @return The HTTP "Content-Type" header. - */ - public static String writeHeader(MediaType mediaType, CharacterSet characterSet) { - StringBuilder result = new StringBuilder(mediaType.toString()); - - // Specify the character set parameter if required - // TODO I wonder if the given parameter "characterSet" overrides the mediaType's charset'? - /* - if ((mediaType.getParameters().getFirstValue("charset") == null) && (characterSet != null)) { - result = result + "; charset=" + characterSet.getName(); - } - */ - - for (Parameter param : mediaType.getParameters()) { + /** + * Parses the given content type header and returns the character set. + * + * @param contentType The content type header to parse. + * @return The character set. + */ + public static CharacterSet readCharacterSet(String contentType) { + return new ContentType(contentType).getCharacterSet(); + } + + /** + * Parses the given content type header and returns the media type. + * + * @param contentType The content type header to parse. + * @return The media type. + */ + public static MediaType readMediaType(String contentType) { + return new ContentType(contentType).getMediaType(); + } + + /** + * Writes the HTTP "Content-Type" header. + * + * @param mediaType The media type. + * @param characterSet The representation character set. + * @return The HTTP "Content-Type" header. + */ + public static String writeHeader(MediaType mediaType, CharacterSet characterSet) { + final StringBuilder result = new StringBuilder(mediaType.toString()); + + String mediaTypeCharset = null; + for (Parameter param : mediaType.getParameters()) { if (param == null) { - continue; - } - if (characterSet != null && param.getName().equals("charset")) { - // TODO I wonder if the given parameter "characterSet" overrides the mediaType's charset'? - result.append("; ").append(param.getName()).append("=").append(characterSet); - } else { - result.append("; ").append(param.getName()).append("=").append(param.getValue()); - } + continue; + } + if ("charset".equals(param.getName())) { + mediaTypeCharset = param.getValue(); + } else { + result.append("; ").append(param.getName()).append("=").append(param.getValue()); + } + } + + if (characterSet != null) { + result.append("; charset=").append(characterSet.getName()); + } else if (mediaTypeCharset != null) { + result.append("; charset=").append(mediaTypeCharset); + } + + return result.toString(); + } + + /** + * Writes the HTTP "Content-Type" header. + * + * @param representation The related representation. + * @return The HTTP "Content-Type" header. + */ + public static String writeHeader(Representation representation) { + return writeHeader(representation.getMediaType(), representation.getCharacterSet()); + } + + /** The content character set. */ + private volatile CharacterSet characterSet; + + /** The content media type. */ + private volatile MediaType mediaType; + + /** + * Constructor. + * + * @param mediaType The media type. + * @param characterSet The character set. + */ + public ContentType(MediaType mediaType, CharacterSet characterSet) { + this.mediaType = mediaType; + this.characterSet = characterSet; + } + + /** + * Constructor. + * + * @param representation The representation. + */ + public ContentType(Representation representation) { + this(representation.getMediaType(), representation.getCharacterSet()); + } + + /** + * Constructor. + * + * @param headerValue The "Content-type" header to parse. + */ + public ContentType(String headerValue) { + try { + ContentTypeReader ctr = new ContentTypeReader(headerValue); + ContentType ct = ctr.readValue(); + + if (ct != null) { + this.mediaType = ct.getMediaType(); + this.characterSet = ct.getCharacterSet(); + } + } catch (IOException ioe) { + throw new IllegalArgumentException("The Content Type could not be read.", ioe); } - - return result.toString(); - } - - /** - * Writes the HTTP "Content-Type" header. - * - * @param representation The related representation. - * @return The HTTP "Content-Type" header. - */ - public static String writeHeader(Representation representation) { - return writeHeader(representation.getMediaType(), representation.getCharacterSet()); - } - - /** - * The content character set. - */ - private volatile CharacterSet characterSet; - - /** - * The content media type. - */ - private volatile MediaType mediaType; - - /** - * Constructor. - * - * @param mediaType The media type. - * @param characterSet The character set. - */ - public ContentType(MediaType mediaType, CharacterSet characterSet) { - this.mediaType = mediaType; - this.characterSet = characterSet; - } - - /** - * Constructor. - * - * @param representation The representation. - */ - public ContentType(Representation representation) { - this(representation.getMediaType(), representation.getCharacterSet()); - } - - /** - * Constructor. - * - * @param headerValue The "Content-type" header to parse. - */ - public ContentType(String headerValue) { - try { - ContentTypeReader ctr = new ContentTypeReader(headerValue); - ContentType ct = ctr.readValue(); - - if (ct != null) { - this.mediaType = ct.getMediaType(); - this.characterSet = ct.getCharacterSet(); - } - } catch (IOException ioe) { - throw new IllegalArgumentException("The Content Type could not be read.", ioe); - } - } - - /** - * Returns the character set. - * - * @return The character set. - */ - public CharacterSet getCharacterSet() { - return this.characterSet; - } - - /** - * Returns the media type. - * - * @return The media type. - */ - public MediaType getMediaType() { - return this.mediaType; - } - - @Override - public String toString() { - return writeHeader(getMediaType(), getCharacterSet()); - } + } + + /** + * Returns the character set. + * + * @return The character set. + */ + public CharacterSet getCharacterSet() { + return this.characterSet; + } + + /** + * Returns the media type. + * + * @return The media type. + */ + public MediaType getMediaType() { + return this.mediaType; + } + + @Override + public String toString() { + return writeHeader(getMediaType(), getCharacterSet()); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/ContentTypeReader.java b/org.restlet/src/main/java/org/restlet/engine/header/ContentTypeReader.java index 35ce49f3d5..809ff61aec 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/ContentTypeReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/ContentTypeReader.java @@ -1,203 +1,211 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.io.IOException; import org.restlet.data.CharacterSet; import org.restlet.data.MediaType; import org.restlet.data.Parameter; import org.restlet.util.Series; -import java.io.IOException; - /** * Content type header reader. - * + * * @author Jerome Louvel */ public class ContentTypeReader extends HeaderReader { - /** - * Constructor. - * - * @param header The header to read. - */ - public ContentTypeReader(String header) { - super(header); - } - - /** - * Creates a content type. - * - * @param mediaType The media type name. - * @param parameters The parameters parsed. - * @return The content type. - */ - private ContentType createContentType(StringBuilder mediaType, Series parameters) { - // Attempt to extract the character set - CharacterSet characterSet = null; - - if (parameters != null) { - String charSet = parameters.getFirstValue("charset"); - - if (charSet != null) { - parameters.removeAll("charset"); - characterSet = new CharacterSet(charSet); - } - - return new ContentType(new MediaType(mediaType.toString(), parameters), characterSet); - } - - return new ContentType(new MediaType(mediaType.toString()), null); - } - - @Override - public ContentType readValue() throws IOException { - ContentType result = null; - - boolean readingMediaType = true; - boolean readingParamName = false; - boolean readingParamValue = false; - - StringBuilder mediaTypeBuffer = new StringBuilder(); - StringBuilder paramNameBuffer = null; - StringBuilder paramValueBuffer = null; - - Series parameters = null; - String nextValue = readRawValue(); - int nextIndex = 0; - - if (nextValue != null) { - int nextChar = nextValue.charAt(nextIndex++); - - while (result == null) { - if (readingMediaType) { - if (nextChar == -1) { - if (mediaTypeBuffer.length() > 0) { - // End of metadata section - // No parameters detected - result = createContentType(mediaTypeBuffer, null); - paramNameBuffer = new StringBuilder(); - } else { - // Ignore empty metadata name - } - } else if (nextChar == ';') { - if (mediaTypeBuffer.length() > 0) { - // End of mediaType section - // Parameters detected - readingMediaType = false; - readingParamName = true; - paramNameBuffer = new StringBuilder(); - parameters = new Series(Parameter.class); - } else { - throw new IOException("Empty mediaType name detected."); - } - } else if (HeaderUtils.isSpace(nextChar)) { - // Ignore spaces - } else if (HeaderUtils.isText(nextChar)) { - mediaTypeBuffer.append((char) nextChar); - } else { - throw new IOException( - "The " + (char) nextChar + " character isn't allowed in a media type name."); - } - } else if (readingParamName) { - if (nextChar == '=') { - if (paramNameBuffer.length() > 0) { - // End of parameter name section - readingParamName = false; - readingParamValue = true; - paramValueBuffer = new StringBuilder(); - } else { - throw new IOException("Empty parameter name detected."); - } - } else if (nextChar == -1) { - if (paramNameBuffer.length() > 0) { - // End of parameters section - parameters.add(Parameter.create(paramNameBuffer, null)); - result = createContentType(mediaTypeBuffer, parameters); - } else if (paramNameBuffer.length() == 0) { - result = createContentType(mediaTypeBuffer, parameters); - } else { - throw new IOException("Empty parameter name detected."); - } - } else if (nextChar == ';') { - // End of parameter - parameters.add(Parameter.create(paramNameBuffer, null)); - paramNameBuffer = new StringBuilder(); - readingParamName = true; - readingParamValue = false; - } else if (HeaderUtils.isSpace(nextChar) && (paramNameBuffer.length() == 0)) { - // Ignore white spaces - } else if (HeaderUtils.isTokenChar(nextChar)) { - paramNameBuffer.append((char) nextChar); - } else { - throw new IOException("The \"" + (char) nextChar - + "\" character isn't allowed in a media type parameter name."); - } - } else if (readingParamValue) { - if (nextChar == -1) { - if (paramValueBuffer.length() > 0) { - // End of parameters section - parameters.add(Parameter.create(paramNameBuffer, paramValueBuffer)); - result = createContentType(mediaTypeBuffer, parameters); - } else { - throw new IOException("Empty parameter value detected"); - } - } else if (nextChar == ';') { - // End of parameter - parameters.add(Parameter.create(paramNameBuffer, paramValueBuffer)); - paramNameBuffer = new StringBuilder(); - readingParamName = true; - readingParamValue = false; - } else if ((nextChar == '"') && (paramValueBuffer.length() == 0)) { - // Parse the quoted string - boolean done = false; - boolean quotedPair = false; - - while ((!done) && (nextChar != -1)) { - nextChar = (nextIndex < nextValue.length()) ? nextValue.charAt(nextIndex++) : -1; - - if (quotedPair) { - // End of quoted pair (escape sequence) - if (HeaderUtils.isText(nextChar)) { - paramValueBuffer.append((char) nextChar); - quotedPair = false; - } else { - throw new IOException("Invalid character \"" + (char) nextChar - + "\" detected in quoted string. Please check your value"); - } - } else if (HeaderUtils.isDoubleQuote(nextChar)) { - // End of quoted string - done = true; - } else if (nextChar == '\\') { - // Begin of quoted pair (escape sequence) - quotedPair = true; - } else if (HeaderUtils.isText(nextChar)) { - paramValueBuffer.append((char) nextChar); - } else { - throw new IOException("Invalid character \"" + (char) nextChar - + "\" detected in quoted string. Please check your value"); - } - } - } else if (HeaderUtils.isTokenChar(nextChar)) { - paramValueBuffer.append((char) nextChar); - } else { - throw new IOException("The \"" + (char) nextChar - + "\" character isn't allowed in a media type parameter value."); - } - } - - nextChar = (nextIndex < nextValue.length()) ? nextValue.charAt(nextIndex++) : -1; - } - } - - return result; - } - + /** + * Constructor. + * + * @param header The header to read. + */ + public ContentTypeReader(String header) { + super(header); + } + + /** + * Creates a content type. + * + * @param mediaType The media type name. + * @param parameters The parameters parsed. + * @return The content type. + */ + private ContentType createContentType(StringBuilder mediaType, Series parameters) { + // Attempt to extract the character set + CharacterSet characterSet = null; + + if (parameters != null) { + String charSet = parameters.getFirstValue("charset"); + + if (charSet != null) { + parameters.removeAll("charset"); + characterSet = new CharacterSet(charSet); + } + + return new ContentType(new MediaType(mediaType.toString(), parameters), characterSet); + } + + return new ContentType(new MediaType(mediaType.toString()), null); + } + + @Override + public ContentType readValue() throws IOException { + ContentType result = null; + + boolean readingMediaType = true; + boolean readingParamName = false; + boolean readingParamValue = false; + + StringBuilder mediaTypeBuffer = new StringBuilder(); + StringBuilder paramNameBuffer = null; + StringBuilder paramValueBuffer = null; + + Series parameters = null; + String nextValue = readRawValue(); + int nextIndex = 0; + + if (nextValue != null) { + int nextChar = nextValue.charAt(nextIndex++); + + while (result == null) { + if (readingMediaType) { + if (nextChar == -1) { + if (mediaTypeBuffer.isEmpty()) { + // Ignore empty metadata name + } else { + // End of metadata section + // No parameters detected + result = createContentType(mediaTypeBuffer, null); + paramNameBuffer = new StringBuilder(); + } + } else if (nextChar == ';') { + if (mediaTypeBuffer.isEmpty()) { + throw new IOException("Empty mediaType name detected."); + } else { + // End of mediaType section + // Parameters detected + readingMediaType = false; + readingParamName = true; + paramNameBuffer = new StringBuilder(); + parameters = new Series<>(Parameter.class); + } + } else if (HeaderUtils.isSpace(nextChar)) { + // Ignore spaces + } else if (HeaderUtils.isText(nextChar)) { + mediaTypeBuffer.append((char) nextChar); + } else { + throw new IOException( + "The " + + (char) nextChar + + " character isn't allowed in a media type name."); + } + } else if (readingParamName) { + if (nextChar == '=') { + if (paramNameBuffer.isEmpty()) { + throw new IOException("Empty parameter name detected."); + } else { + // End of a parameter name section + readingParamName = false; + readingParamValue = true; + paramValueBuffer = new StringBuilder(); + } + } else if (nextChar == -1) { + if (paramNameBuffer.isEmpty()) { + result = createContentType(mediaTypeBuffer, parameters); + } else { + // End of the parameters section + parameters.add(Parameter.create(paramNameBuffer, null)); + result = createContentType(mediaTypeBuffer, parameters); + } + } else if (nextChar == ';') { + // End of parameter + parameters.add(Parameter.create(paramNameBuffer, null)); + paramNameBuffer = new StringBuilder(); + readingParamName = true; + readingParamValue = false; + } else if (HeaderUtils.isSpace(nextChar) && paramNameBuffer.isEmpty()) { + // Ignore white spaces + } else if (HeaderUtils.isTokenChar(nextChar)) { + paramNameBuffer.append((char) nextChar); + } else { + throw new IOException( + "The \"" + + (char) nextChar + + "\" character isn't allowed in a media type parameter name."); + } + } else if (readingParamValue) { + if (nextChar == -1) { + if (paramValueBuffer.isEmpty()) { + throw new IOException("Empty parameter value detected"); + } else { + // End of the parameters section + parameters.add(Parameter.create(paramNameBuffer, paramValueBuffer)); + result = createContentType(mediaTypeBuffer, parameters); + } + } else if (nextChar == ';') { + // End of parameter + parameters.add(Parameter.create(paramNameBuffer, paramValueBuffer)); + paramNameBuffer = new StringBuilder(); + readingParamName = true; + readingParamValue = false; + } else if ((nextChar == '"') && paramValueBuffer.isEmpty()) { + // Parse the quoted string + boolean done = false; + boolean quotedPair = false; + + while ((!done) && (nextChar != -1)) { + nextChar = + (nextIndex < nextValue.length()) + ? nextValue.charAt(nextIndex++) + : -1; + + if (quotedPair) { + // End of a quoted pair (escape sequence) + if (HeaderUtils.isText(nextChar)) { + paramValueBuffer.append((char) nextChar); + quotedPair = false; + } else { + throw new IOException( + "Invalid character \"" + + (char) nextChar + + "\" detected in quoted string. Please check your value"); + } + } else if (HeaderUtils.isDoubleQuote(nextChar)) { + // End of quoted string + done = true; + } else if (nextChar == '\\') { + // Begin of a quoted pair (escape sequence) + quotedPair = true; + } else if (HeaderUtils.isText(nextChar)) { + paramValueBuffer.append((char) nextChar); + } else { + throw new IOException( + "Invalid character \"" + + (char) nextChar + + "\" detected in quoted string. Please check your value"); + } + } + } else if (HeaderUtils.isTokenChar(nextChar)) { + paramValueBuffer.append((char) nextChar); + } else { + throw new IOException( + "The \"" + + (char) nextChar + + "\" character isn't allowed in a media type parameter value."); + } + } + + nextChar = (nextIndex < nextValue.length()) ? nextValue.charAt(nextIndex++) : -1; + } + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/CookieReader.java b/org.restlet/src/main/java/org/restlet/engine/header/CookieReader.java index 02ef717ef5..ac95aa4723 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/CookieReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/CookieReader.java @@ -1,176 +1,175 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.io.IOException; import org.restlet.data.Cookie; import org.restlet.data.Parameter; -import java.io.IOException; - /** * Cookie header reader. - * + * * @author Jerome Louvel */ public class CookieReader extends HeaderReader { - private static final String NAME_DOMAIN = "$Domain"; - - private static final String NAME_PATH = "$Path"; - - private static final String NAME_VERSION = "$Version"; - - /** - * Parses the given String to a Cookie - * - * @param cookie - * @return the Cookie parsed from the String - * @throws IllegalArgumentException Thrown if the String can not be parsed as - * Cookie. - */ - public static Cookie read(String cookie) throws IllegalArgumentException { - CookieReader cr = new CookieReader(cookie); - - try { - return cr.readValue(); - } catch (IOException e) { - throw new IllegalArgumentException("Could not read the cookie", e); - } - } - - /** The global cookie specification version. */ - private volatile int globalVersion; - - /** - * Constructor. - * - * @param header The header to read. - */ - public CookieReader(String header) { - super(header); - this.globalVersion = -1; - } - - /** - * Reads the next pair as a parameter. - * - * @param readAttribute True, if the intention is to read only cookie attribute. - * @return The next pair as a parameter. - * @throws IOException - */ - private Parameter readPair(boolean readAttribute) throws IOException { - Parameter result = null; - - boolean readingName = true; - StringBuilder nameBuffer = new StringBuilder(); - StringBuilder valueBuffer = new StringBuilder(); - int nextChar = 0; - - while ((result == null) && (nextChar != -1)) { - nextChar = read(); - - if (readingName) { - if ((HeaderUtils.isSpace(nextChar)) && (nameBuffer.isEmpty())) { - // Skip spaces - } else if ((nextChar == -1) || (nextChar == ';') || (nextChar == ',')) { - if (!nameBuffer.isEmpty()) { - // End of pair with no value - result = Parameter.create(nameBuffer, null); - } else if (nextChar == -1) { - // Do nothing return null preference - } else { - throw new IOException("Empty cookie name detected. Please check your cookies"); - } - } else if (nextChar == '=') { - readingName = false; - } else if (HeaderUtils.isTokenChar(nextChar) || (this.globalVersion < 1)) { - if (readAttribute && nextChar != '$' && (nameBuffer.isEmpty())) { - unread(); - nextChar = -1; - } else { - nameBuffer.append((char) nextChar); - } - } else { - throw new IOException( - "Separator and control characters are not allowed within a token. Please check your cookie header"); - } - } else { - // reading value - if ((HeaderUtils.isSpace(nextChar)) && (valueBuffer.isEmpty())) { - // Skip spaces - } else if ((nextChar == -1) || (nextChar == ';')) { - // End of pair - result = Parameter.create(nameBuffer, valueBuffer); - } else if ((nextChar == '"') && (valueBuffer.isEmpty())) { - // Step back - unread(); - valueBuffer.append(readQuotedString()); - } else if (HeaderUtils.isTokenChar(nextChar) || (this.globalVersion < 1)) { - valueBuffer.append((char) nextChar); - } else { - throw new IOException( - "Separator and control characters are not allowed within a token. Please check your cookie header"); - } - } - } - - return result; - } - - @Override - public Cookie readValue() throws IOException { - Cookie result = null; - Parameter pair = readPair(false); - - if (pair != null && this.globalVersion == -1) { - // Cookies version not yet detected - if (NAME_VERSION.equalsIgnoreCase(pair.getName())) { - if (pair.getValue() != null) { - this.globalVersion = Integer.parseInt(pair.getValue()); - } else { - throw new IOException("Empty cookies version attribute detected. Please check your cookie header"); - } - } else { - // Set the default version for old Netscape cookies - this.globalVersion = 0; - } - } - - while ((pair != null) && (pair.getName().isEmpty() || pair.getName().charAt(0) == '$')) { - // Unexpected special attribute - // Silently ignore it as it may have been introduced by new - // specifications - pair = readPair(false); - } - - if (pair != null) { - // Set the cookie name and value - result = new Cookie(this.globalVersion, pair.getName(), pair.getValue()); - pair = readPair(true); - } - - while ((pair != null) && (pair.getName().isEmpty() || pair.getName().charAt(0) == '$')) { - if (pair.getName().equalsIgnoreCase(NAME_PATH)) { - result.setPath(pair.getValue()); - } else if (pair.getName().equalsIgnoreCase(NAME_DOMAIN)) { - result.setDomain(pair.getValue()); - } else { - // Unexpected special attribute - // Silently ignore it as it may have been introduced by new - // specifications - } - - pair = readPair(true); - } - - return result; - } - + private static final String NAME_DOMAIN = "$Domain"; + + private static final String NAME_PATH = "$Path"; + + private static final String NAME_VERSION = "$Version"; + + /** + * Parses the given String to a Cookie + * + * @param cookie The cookie a string to parse. + * @return the Cookie parsed from the String + * @throws IllegalArgumentException Thrown if the String cannot be parsed as Cookie. + */ + public static Cookie read(String cookie) throws IllegalArgumentException { + CookieReader cr = new CookieReader(cookie); + + try { + return cr.readValue(); + } catch (IOException e) { + throw new IllegalArgumentException("Could not read the cookie", e); + } + } + + /** The global cookie specification version. */ + private volatile int globalVersion; + + /** + * Constructor. + * + * @param header The header to read. + */ + public CookieReader(String header) { + super(header); + this.globalVersion = -1; + } + + /** + * Reads the next pair as a parameter. + * + * @param readAttribute True, if the intention is to only read the cookie attribute. + * @return The next pair as a parameter. + * @throws IOException + */ + private Parameter readPair(boolean readAttribute) throws IOException { + Parameter result = null; + + boolean readingName = true; + StringBuilder nameBuffer = new StringBuilder(); + StringBuilder valueBuffer = new StringBuilder(); + int nextChar = 0; + + while ((result == null) && (nextChar != -1)) { + nextChar = read(); + + if (readingName) { + if (nameBuffer.isEmpty() && HeaderUtils.isSpace(nextChar)) { + // Skip spaces + } else if ((nextChar == -1) || (nextChar == ';') || (nextChar == ',')) { + if (!nameBuffer.isEmpty()) { + // End of a pair with no value + result = Parameter.create(nameBuffer, null); + } else if (nextChar == -1) { + // Do nothing and return a null preference + } else { + throw new IOException( + "Empty cookie name detected. Please check your cookies"); + } + } else if (nextChar == '=') { + readingName = false; + } else if (HeaderUtils.isTokenChar(nextChar) || (this.globalVersion < 1)) { + if (readAttribute && nextChar != '$' && (nameBuffer.isEmpty())) { + unread(); + nextChar = -1; + } else { + nameBuffer.append((char) nextChar); + } + } else { + throw new IOException( + "Separator and control characters are not allowed within a token. Please check your cookie header"); + } + } else { + // reading value + if (valueBuffer.isEmpty() && HeaderUtils.isSpace(nextChar)) { + // Skip spaces + } else if ((nextChar == -1) || (nextChar == ';')) { + // End of a pair + result = Parameter.create(nameBuffer, valueBuffer); + } else if ((nextChar == '"') && (valueBuffer.isEmpty())) { + // Step back + unread(); + valueBuffer.append(readQuotedString()); + } else if (HeaderUtils.isTokenChar(nextChar) || (this.globalVersion < 1)) { + valueBuffer.append((char) nextChar); + } else { + throw new IOException( + "Separator and control characters are not allowed within a token. Please check your cookie header"); + } + } + } + + return result; + } + + @Override + public Cookie readValue() throws IOException { + Cookie result = null; + Parameter pair = readPair(false); + + if (pair != null && this.globalVersion == -1) { + // Cookies version not yet detected + this.globalVersion = getVersion(pair); + } + + while ((pair != null) && (pair.getName().isEmpty() || pair.getName().charAt(0) == '$')) { + // Unexpected special attribute + // Silently ignore it as it may have been introduced by new specifications + pair = readPair(false); + } + + if (pair != null) { + // Set the cookie name and value + result = new Cookie(this.globalVersion, pair.getName(), pair.getValue()); + pair = readPair(true); + } + + while ((pair != null) && (pair.getName().isEmpty() || pair.getName().charAt(0) == '$')) { + if (pair.getName().equalsIgnoreCase(NAME_PATH)) { + result.setPath(pair.getValue()); + } else if (pair.getName().equalsIgnoreCase(NAME_DOMAIN)) { + result.setDomain(pair.getValue()); + } else { + // Unexpected special attribute + // Silently ignore it as it may have been introduced by new specifications + } + + pair = readPair(true); + } + + return result; + } + + private int getVersion(final Parameter pair) throws IOException { + if (NAME_VERSION.equalsIgnoreCase(pair.getName())) { + if (pair.getValue() != null) { + return Integer.parseInt(pair.getValue()); + } else { + throw new IOException( + "Empty cookies version attribute detected. Please check your cookie header"); + } + } else { // Set the default version for old Netscape cookies + return 0; + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingReader.java b/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingReader.java index ba363166e2..ea2efeb471 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingReader.java @@ -1,231 +1,241 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import static org.restlet.engine.util.DateUtils.FORMAT_ASC_TIME; +import static org.restlet.engine.util.DateUtils.FORMAT_RFC_1036; +import static org.restlet.engine.util.DateUtils.FORMAT_RFC_1123; + +import java.io.IOException; +import java.util.Date; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.data.CookieSetting; import org.restlet.data.Parameter; import org.restlet.engine.util.DateUtils; import org.restlet.engine.util.StringUtils; -import java.io.IOException; -import java.util.Date; - -import static org.restlet.engine.util.DateUtils.*; - /** * Cookie setting header reader. - * + * + *

The commentURL and port attributes are not supported yet. + * * @author Jerome Louvel */ public class CookieSettingReader extends HeaderReader { - private static final String NAME_SET_ACCESS_RESTRICTED = "httpOnly"; - - private static final String NAME_SET_COMMENT = "comment"; - - private static final String NAME_SET_COMMENT_URL = "commentURL"; - - private static final String NAME_SET_DISCARD = "discard"; - - private static final String NAME_SET_DOMAIN = "domain"; - - private static final String NAME_SET_EXPIRES = "expires"; - - private static final String NAME_SET_MAX_AGE = "max-age"; - - private static final String NAME_SET_PATH = "path"; - - private static final String NAME_SET_PORT = "port"; - - private static final String NAME_SET_SECURE = "secure"; - - private static final String NAME_SET_VERSION = "version"; - - /** - * Parses the given String to a CookieSetting - * - * @param cookieSetting - * @return the CookieSetting parsed from the String - * @throws IllegalArgumentException Thrown if the String cannot be parsed as - * CookieSetting. - */ - public static CookieSetting read(String cookieSetting) throws IllegalArgumentException { - CookieSettingReader cr = new CookieSettingReader(cookieSetting); - - try { - return cr.readValue(); - } catch (IOException e) { - throw new IllegalArgumentException("Could not read the cookie setting", e); - } - } - - /** The cached pair. Used by the readPair() method. */ - private volatile Parameter cachedPair; - - /** The global cookie specification version. */ - private volatile int globalVersion; - - /** - * Constructor. - * - * @param header The header to read. - */ - public CookieSettingReader(String header) { - super(header); - this.cachedPair = null; - this.globalVersion = -1; - } - - /** - * Reads the next pair as a parameter. - * - * @return The next pair as a parameter. - * @throws IOException - */ - private Parameter readPair() throws IOException { - if (this.cachedPair != null) { - Parameter pair = this.cachedPair; - this.cachedPair = null; - return pair; - } - - Parameter result = null; - - boolean readingName = true; - StringBuilder nameBuffer = new StringBuilder(); - StringBuilder valueBuffer = new StringBuilder(); - int nextChar = 0; - - while ((result == null) && (nextChar != -1)) { - nextChar = read(); - - if (readingName) { - if ((HeaderUtils.isSpace(nextChar)) && (nameBuffer.isEmpty())) { - // Skip spaces - } else if ((nextChar == -1) || (nextChar == ';') || (nextChar == ',')) { - if (!nameBuffer.isEmpty()) { - // End of pair with no value - result = Parameter.create(nameBuffer, null); - } else if (nextChar == -1) { - // Do nothing return null preference - } else { - throw new IOException("Empty cookie name detected. Please check your cookies"); - } - } else if (nextChar == '=') { - readingName = false; - } else if (HeaderUtils.isTokenChar(nextChar) || (this.globalVersion < 1)) { - nameBuffer.append((char) nextChar); - } else { - throw new IOException( - "Separator and control characters are not allowed within a token. Please check your cookie header"); - } - } else { - // reading value - if ((HeaderUtils.isSpace(nextChar)) && (valueBuffer.isEmpty())) { - // Skip spaces - } else if ((nextChar == -1) || (nextChar == ';')) { - // End of pair - result = Parameter.create(nameBuffer, valueBuffer); - } else if ((nextChar == '"') && (valueBuffer.isEmpty())) { - // Step back - unread(); - valueBuffer.append(readQuotedString()); - } else if (HeaderUtils.isTokenChar(nextChar) || (this.globalVersion < 1)) { - valueBuffer.append((char) nextChar); - } else { - throw new IOException( - "Separator and control characters are not allowed within a token. Please check your cookie header"); - } - } - } - - return result; - } - - @Override - public CookieSetting readValue() throws IOException { - Parameter pair = null; - - // Unexpected special attributes - // Silently ignore it as it may have been introduced by new specifications - do { - if ((pair = readPair()) == null) { - return null; - } - } while (pair.getName().charAt(0) == '$'); - - // Set the cookie name and value - CookieSetting result = new CookieSetting(pair.getName(), pair.getValue()); - - while ((pair = readPair()) != null) { - if (pair.getName().equalsIgnoreCase(NAME_SET_PATH)) { - result.setPath(pair.getValue()); - } else if (pair.getName().equalsIgnoreCase(NAME_SET_DOMAIN)) { - result.setDomain(pair.getValue()); - } else if (pair.getName().equalsIgnoreCase(NAME_SET_COMMENT)) { - result.setComment(pair.getValue()); - } else if (pair.getName().equalsIgnoreCase(NAME_SET_COMMENT_URL)) { - // No yet supported - } else if (pair.getName().equalsIgnoreCase(NAME_SET_DISCARD)) { - result.setMaxAge(-1); - } else if (pair.getName().equalsIgnoreCase(NAME_SET_EXPIRES)) { - Date expires = DateUtils.parse(pair.getValue(), FORMAT_RFC_1036); - - if (expires == null) { - expires = DateUtils.parse(pair.getValue(), FORMAT_RFC_1123); - } - - if (expires == null) { - expires = DateUtils.parse(pair.getValue(), FORMAT_ASC_TIME); - } - - if (expires != null) { - final Date current = new Date(System.currentTimeMillis()); - if (DateUtils.after(current, expires)) { - result.setMaxAge((int) ((expires.getTime() - current.getTime()) / 1000)); - } else { - result.setMaxAge(0); - } - } else { - // Ignore the expires header - Context.getCurrentLogger().warning( - "Ignoring cookie setting expiration date. Unable to parse the date: " + pair.getValue()); - } - } else if (pair.getName().equalsIgnoreCase(NAME_SET_MAX_AGE)) { - try { - result.setMaxAge(Integer.parseInt(pair.getValue())); - } catch (NumberFormatException numberFormatException) { - result.setMaxAge(Integer.MAX_VALUE); - Context.getCurrentLogger().warning("Unable to parse the cookie setting max-age value \"" - + pair.getValue() + "\", used Integer.MAX_VALUE instead: " + Integer.MAX_VALUE); - } - } else if (pair.getName().equalsIgnoreCase(NAME_SET_PORT)) { - // No yet supported - } else if (pair.getName().equalsIgnoreCase(NAME_SET_SECURE)) { - if (StringUtils.isNullOrEmpty(pair.getValue())) { - result.setSecure(true); - } - } else if (pair.getName().equalsIgnoreCase(NAME_SET_ACCESS_RESTRICTED)) { - if (StringUtils.isNullOrEmpty(pair.getValue())) { - result.setAccessRestricted(true); - } - } else if (pair.getName().equalsIgnoreCase(NAME_SET_VERSION)) { - result.setVersion(Integer.parseInt(pair.getValue())); - } else { - // Unexpected special attribute - // Silently ignore it as it may have been introduced by new specifications - } - } - - return result; - } - + private static final String NAME_SET_ACCESS_RESTRICTED = "httpOnly"; + + private static final String NAME_SET_COMMENT = "comment"; + + private static final String NAME_SET_DISCARD = "discard"; + + private static final String NAME_SET_DOMAIN = "domain"; + + private static final String NAME_SET_EXPIRES = "expires"; + + private static final String NAME_SET_MAX_AGE = "max-age"; + + private static final String NAME_SET_PATH = "path"; + + private static final String NAME_SET_SECURE = "secure"; + + private static final String NAME_SET_VERSION = "version"; + + /** + * Parses the given String to a CookieSetting + * + * @param cookieSetting The cookie setting as String to parse. + * @return the CookieSetting parsed from the String + * @throws IllegalArgumentException Thrown if the String cannot be parsed as CookieSetting. + */ + public static CookieSetting read(String cookieSetting) throws IllegalArgumentException { + CookieSettingReader cr = new CookieSettingReader(cookieSetting); + + try { + return cr.readValue(); + } catch (IOException e) { + throw new IllegalArgumentException("Could not read the cookie setting", e); + } + } + + /** The cached pair. Used by the readPair() method. */ + private volatile Parameter cachedPair; + + /** The global cookie specification version. */ + private final int globalVersion; + + /** + * Constructor. + * + * @param header The header to read. + */ + public CookieSettingReader(String header) { + super(header); + this.cachedPair = null; + this.globalVersion = -1; + } + + /** + * Reads the next pair as a parameter. + * + * @return The next pair as a parameter. + * @throws IOException + */ + private Parameter readPair() throws IOException { + if (this.cachedPair != null) { + Parameter pair = this.cachedPair; + this.cachedPair = null; + return pair; + } + + Parameter result = null; + + boolean readingName = true; + StringBuilder nameBuffer = new StringBuilder(); + StringBuilder valueBuffer = new StringBuilder(); + int nextChar = 0; + + while ((result == null) && (nextChar != -1)) { + nextChar = read(); + + if (readingName) { + if (nameBuffer.isEmpty() && HeaderUtils.isSpace(nextChar)) { + // Skip spaces + } else if ((nextChar == -1) || (nextChar == ';') || (nextChar == ',')) { + if (!nameBuffer.isEmpty()) { + // End of a pair with no value + result = Parameter.create(nameBuffer, null); + } else if (nextChar == -1) { + // Do nothing and return a null preference + } else { + throw new IOException( + "Empty cookie name detected. Please check your cookies"); + } + } else if (nextChar == '=') { + readingName = false; + } else if (HeaderUtils.isTokenChar(nextChar) || (this.globalVersion < 1)) { + nameBuffer.append((char) nextChar); + } else { + throw new IOException( + "Separator and control characters are not allowed within a token. Please check your cookie header"); + } + } else { + // reading value + if (valueBuffer.isEmpty() && HeaderUtils.isSpace(nextChar)) { + // Skip spaces + } else if ((nextChar == -1) || (nextChar == ';')) { + // End of pair + result = Parameter.create(nameBuffer, valueBuffer); + } else if ((nextChar == '"') && (valueBuffer.isEmpty())) { + // Step back + unread(); + valueBuffer.append(readQuotedString()); + } else if (HeaderUtils.isTokenChar(nextChar) || (this.globalVersion < 1)) { + valueBuffer.append((char) nextChar); + } else { + throw new IOException( + "Separator and control characters are not allowed within a token. Please check your cookie header"); + } + } + } + + return result; + } + + @Override + public CookieSetting readValue() throws IOException { + Parameter pair; + + // Unexpected special attributes + // Silently ignore it as it may have been introduced by new specifications + do { + if ((pair = readPair()) == null) { + return null; + } + } while (pair.getName().charAt(0) == '$'); + + // Set the cookie name and value + CookieSetting result = new CookieSetting(pair.getName(), pair.getValue()); + + while ((pair = readPair()) != null) { + if (NAME_SET_PATH.equalsIgnoreCase(pair.getName())) { + result.setPath(pair.getValue()); + } else if (NAME_SET_DOMAIN.equalsIgnoreCase(pair.getName())) { + result.setDomain(pair.getValue()); + } else if (NAME_SET_COMMENT.equalsIgnoreCase(pair.getName())) { + result.setComment(pair.getValue()); + } else if (NAME_SET_DISCARD.equalsIgnoreCase(pair.getName())) { + result.setMaxAge(-1); + } else if (NAME_SET_EXPIRES.equalsIgnoreCase(pair.getName())) { + Date expires = parseExpiresDate(pair); + if (expires == null) { + // Ignore the "expires" header + Context.getCurrentLogger() + .warning( + "Ignoring cookie setting expiration date. Unable to parse the date: " + + pair.getValue()); + } else { + result.setMaxAge(fromExpirationDateToMaxAge(expires)); + } + } else if (NAME_SET_MAX_AGE.equalsIgnoreCase(pair.getName())) { + try { + result.setMaxAge(Integer.parseInt(pair.getValue())); + } catch (NumberFormatException numberFormatException) { + result.setMaxAge(Integer.MAX_VALUE); + Context.getCurrentLogger() + .warning( + "Unable to parse the cookie setting max-age value \"" + + pair.getValue() + + "\", used Integer.MAX_VALUE instead: " + + Integer.MAX_VALUE); + } + } else if (NAME_SET_SECURE.equalsIgnoreCase(pair.getName())) { + if (StringUtils.isNullOrEmpty(pair.getValue())) { + result.setSecure(true); + } + } else if (NAME_SET_ACCESS_RESTRICTED.equalsIgnoreCase(pair.getName())) { + if (StringUtils.isNullOrEmpty(pair.getValue())) { + result.setAccessRestricted(true); + } + } else if (NAME_SET_VERSION.equalsIgnoreCase(pair.getName())) { + result.setVersion(Integer.parseInt(pair.getValue())); + } else { + // Silently ignore the parameter as it may have been introduced by new + // specifications + Context.getCurrentLogger() + .log( + Level.FINE, + "The \"{0}\" attribute is not supported yet for cookie settings. Ignoring it.", + pair.getName()); + } + } + + return result; + } + + private static int fromExpirationDateToMaxAge(final Date expires) { + final Date current = new Date(System.currentTimeMillis()); + + return DateUtils.after(current, expires) + ? (int) ((expires.getTime() - current.getTime()) / 1000) + : 0; + } + + private static Date parseExpiresDate(final Parameter pair) { + Date expires = DateUtils.parse(pair.getValue(), FORMAT_RFC_1036); + if (expires == null) { + expires = DateUtils.parse(pair.getValue(), FORMAT_RFC_1123); + } + if (expires == null) { + expires = DateUtils.parse(pair.getValue(), FORMAT_ASC_TIME); + } + return expires; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java index 0b11bb34ac..2ca6d6dc3d 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java @@ -1,154 +1,150 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.CookieSetting; -import org.restlet.engine.util.DateUtils; - import java.util.Date; import java.util.List; +import org.restlet.data.CookieSetting; +import org.restlet.engine.util.DateUtils; /** * Cookie setting header writer. - * + * * @author Jerome Louvel */ public class CookieSettingWriter extends HeaderWriter { - /** - * Writes a cookie setting. - * - * @param cookieSetting The cookie setting to format. - * @return The formatted cookie setting. - */ - public static String write(CookieSetting cookieSetting) { - return new CookieSettingWriter().append(cookieSetting).toString(); - } - - /** - * Writes a list of cookie settings. - * - * @param cookieSettings The cookie settings to write. - * @return The formatted cookie setting. - */ - public static String write(List cookieSettings) { - return new CookieSettingWriter().append(cookieSettings).toString(); - } - - @Override - public CookieSettingWriter append(CookieSetting cookieSetting) throws IllegalArgumentException { - String name = cookieSetting.getName(); - String value = cookieSetting.getValue(); - int version = cookieSetting.getVersion(); - - if ((name == null) || (name.isEmpty())) { - throw new IllegalArgumentException("Can't write cookie. Invalid name detected"); - } - - append(name).append('='); - - // Append the value - if ((value != null) && (!value.isEmpty())) { - appendValue(value, version); - } - - // Append the version - if (version > 0) { - append("; Version="); - appendValue(Integer.toString(version), version); - } - - // Append the path - String path = cookieSetting.getPath(); - - if ((path != null) && (!path.isEmpty())) { - append("; Path="); - - if (version == 0) { - append(path); - } else { - appendQuotedString(path); - } - } - - // Append the expiration date - int maxAge = cookieSetting.getMaxAge(); - - if (maxAge >= 0) { - if (version == 0) { - long currentTime = System.currentTimeMillis(); - long maxTime = (maxAge * 1000L); - long expiresTime = currentTime + maxTime; - Date expires = new Date(expiresTime); - - append("; Expires="); - appendValue(DateUtils.format(expires, DateUtils.FORMAT_RFC_1123.get(0)), version); - } else { - append("; Max-Age="); - appendValue(Integer.toString(cookieSetting.getMaxAge()), version); - } - } else if ((maxAge == -1) && (version > 0)) { - // Discard the cookie at the end of the user's session (RFC - // 2965) - append("; Discard"); - } else { - // NetScape cookies automatically expire at the end of the - // user's session - } - - // Append the domain - String domain = cookieSetting.getDomain(); - - if ((domain != null) && (!domain.isEmpty())) { - append("; Domain="); - appendValue(domain.toLowerCase(), version); - } - - // Append the secure flag - if (cookieSetting.isSecure()) { - append("; Secure"); - } - - // Append the secure flag - if (cookieSetting.isAccessRestricted()) { - append("; HttpOnly"); - } - - // Append the comment - if (version > 0) { - String comment = cookieSetting.getComment(); - - if ((comment != null) && (!comment.isEmpty())) { - append("; Comment="); - appendValue(comment, version); - } - } - - return this; - } - - /** - * Appends a source string as an HTTP comment. - * - * @param value The source string to format. - * @param version The cookie version. - * @return This writer. - */ - public CookieSettingWriter appendValue(String value, int version) { - if (version == 0) { - append(value); - } else { - appendQuotedString(value); - } - - return this; - } - + /** + * Writes a cookie setting. + * + * @param cookieSetting The cookie setting to format. + * @return The formatted cookie setting. + */ + public static String write(CookieSetting cookieSetting) { + return new CookieSettingWriter().append(cookieSetting).toString(); + } + + /** + * Writes a list of cookie settings. + * + * @param cookieSettings The cookie settings to write. + * @return The formatted cookie setting. + */ + public static String write(List cookieSettings) { + return new CookieSettingWriter().append(cookieSettings).toString(); + } + + @Override + public CookieSettingWriter append(CookieSetting cookieSetting) throws IllegalArgumentException { + String name = cookieSetting.getName(); + String value = cookieSetting.getValue(); + int version = cookieSetting.getVersion(); + + if ((name == null) || (name.isEmpty())) { + throw new IllegalArgumentException("Can't write cookie. Invalid name detected"); + } + + append(name).append('='); + + // Append the value + if ((value != null) && (!value.isEmpty())) { + appendValue(value, version); + } + + // Append the version + if (version > 0) { + append("; Version="); + appendValue(Integer.toString(version), version); + } + + // Append the path + String path = cookieSetting.getPath(); + + if ((path != null) && (!path.isEmpty())) { + append("; Path="); + + if (version == 0) { + append(path); + } else { + appendQuotedString(path); + } + } + + // Append the expiration date + int maxAge = cookieSetting.getMaxAge(); + + if (maxAge >= 0) { + if (version == 0) { + long currentTime = System.currentTimeMillis(); + long maxTime = (maxAge * 1000L); + long expiresTime = currentTime + maxTime; + Date expires = new Date(expiresTime); + + append("; Expires="); + appendValue( + DateUtils.format(expires, DateUtils.FORMAT_RFC_1123.getFirst()), version); + } else { + append("; Max-Age="); + appendValue(Integer.toString(cookieSetting.getMaxAge()), version); + } + } else if ((maxAge == -1) && (version > 0)) { + // Discard the cookie at the end of the user's session (RFC 2965) + append("; Discard"); + } else { + // NetScape cookies automatically expire at the end of the user's session + } + + // Append the domain + String domain = cookieSetting.getDomain(); + + if ((domain != null) && (!domain.isEmpty())) { + append("; Domain="); + appendValue(domain.toLowerCase(), version); + } + + // Append the secure flag + if (cookieSetting.isSecure()) { + append("; Secure"); + } + + // Append the secure flag + if (cookieSetting.isAccessRestricted()) { + append("; HttpOnly"); + } + + // Append the comment + if (version > 0) { + String comment = cookieSetting.getComment(); + + if ((comment != null) && (!comment.isEmpty())) { + append("; Comment="); + appendValue(comment, version); + } + } + + return this; + } + + /** + * Appends a source string as an HTTP comment. + * + * @param value The source string to format. + * @param version The cookie version. + * @return This writer. + */ + public CookieSettingWriter appendValue(String value, int version) { + if (version == 0) { + append(value); + } else { + appendQuotedString(value); + } + + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/CookieWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/CookieWriter.java index e812e42ba7..ec8c968299 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/CookieWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/CookieWriter.java @@ -1,148 +1,144 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Cookie; - -import java.util.Iterator; import java.util.List; import java.util.Map; +import org.restlet.data.Cookie; /** * Cookie header writer. - * + * * @author Jerome Louvel */ public class CookieWriter extends HeaderWriter { - /** - * Gets the cookies whose name is a key in the given map. If a matching cookie - * is found, its value is put in the map. - * - * @param source The source list of cookies. - * @param destination The cookies map controlling the reading. - */ - public static void getCookies(List source, Map destination) { - Cookie cookie; - - for (final Iterator iter = source.iterator(); iter.hasNext();) { - cookie = iter.next(); - - if (destination.containsKey(cookie.getName())) { - destination.put(cookie.getName(), cookie); - } - } - } - - /** - * Writes a cookie. - * - * @param cookie The cookie to format. - * @return The formatted cookie. - * @throws IllegalArgumentException If the Cookie contains illegal values. - */ - public static String write(Cookie cookie) throws IllegalArgumentException { - return new CookieWriter().append(cookie).toString(); - } - - /** - * Writes a cookie. - * - * @param cookies The cookies to format. - * @return The formatted cookie. - */ - public static String write(List cookies) { - return new CookieWriter().append(cookies).toString(); - } - - @Override - public CookieWriter append(Cookie cookie) throws IllegalArgumentException { - String name = cookie.getName(); - String value = cookie.getValue(); - int version = cookie.getVersion(); - - if ((name == null) || (name.isEmpty())) { - throw new IllegalArgumentException("Can't write cookie. Invalid name detected"); - } - - appendValue(name, 0).append('='); - - // Append the value - if ((value != null) && (!value.isEmpty())) { - appendValue(value, version); - } - - if (version > 0) { - // Append the path - String path = cookie.getPath(); - - if ((path != null) && (!path.isEmpty())) { - append("; $Path="); - appendQuotedString(path); - } - - // Append the domain - String domain = cookie.getDomain(); - - if ((domain != null) && (!domain.isEmpty())) { - append("; $Domain="); - appendQuotedString(domain); - } - } - - return this; - } - - /** - * Appends a list of cookies as an HTTP header. - * - * @param cookies The list of cookies to format. - * @return This writer. - */ - public CookieWriter append(List cookies) { - if ((cookies != null) && !cookies.isEmpty()) { - Cookie cookie; - - for (int i = 0; i < cookies.size(); i++) { - cookie = cookies.get(i); - - if (i == 0) { - if (cookie.getVersion() > 0) { - append("$Version=\"").append(cookie.getVersion()).append("\"; "); - } - } else { - append("; "); - } - - append(cookie); - } - } - - return this; - } - - /** - * Appends a source string as an HTTP comment. - * - * @param value The source string to format. - * @param version The cookie version. - * @return This writer. - */ - public CookieWriter appendValue(String value, int version) { - if (version == 0) { - append(value); - } else { - appendQuotedString(value); - } - - return this; - } - + /** + * Gets the cookies whose name is a key in the given map. If a matching cookie is found, its + * value is put on the map. + * + * @param source The source list of cookies. + * @param destination The cookies map controlling the reading. + */ + public static void getCookies(List source, Map destination) { + Cookie cookie; + + for (final Cookie value : source) { + cookie = value; + + if (destination.containsKey(cookie.getName())) { + destination.put(cookie.getName(), cookie); + } + } + } + + /** + * Writes a cookie. + * + * @param cookie The cookie to format. + * @return The formatted cookie. + * @throws IllegalArgumentException If the Cookie contains illegal values. + */ + public static String write(Cookie cookie) throws IllegalArgumentException { + return new CookieWriter().append(cookie).toString(); + } + + /** + * Writes a cookie. + * + * @param cookies The cookies to format. + * @return The formatted cookie. + */ + public static String write(List cookies) { + return new CookieWriter().append(cookies).toString(); + } + + @Override + public CookieWriter append(Cookie cookie) throws IllegalArgumentException { + String name = cookie.getName(); + String value = cookie.getValue(); + int version = cookie.getVersion(); + + if ((name == null) || (name.isEmpty())) { + throw new IllegalArgumentException("Can't write cookie. Invalid name detected"); + } + + appendValue(name, 0).append('='); + + // Append the value + if ((value != null) && (!value.isEmpty())) { + appendValue(value, version); + } + + if (version > 0) { + // Append the path + String path = cookie.getPath(); + + if ((path != null) && (!path.isEmpty())) { + append("; $Path="); + appendQuotedString(path); + } + + // Append the domain + String domain = cookie.getDomain(); + + if ((domain != null) && (!domain.isEmpty())) { + append("; $Domain="); + appendQuotedString(domain); + } + } + + return this; + } + + /** + * Appends a list of cookies as an HTTP header. + * + * @param cookies The list of cookies to format. + * @return This writer. + */ + public CookieWriter append(List cookies) { + if ((cookies != null) && !cookies.isEmpty()) { + Cookie cookie; + + for (int i = 0; i < cookies.size(); i++) { + cookie = cookies.get(i); + + if (i == 0) { + if (cookie.getVersion() > 0) { + append("$Version=\"").append(cookie.getVersion()).append("\"; "); + } + } else { + append("; "); + } + + append(cookie); + } + } + + return this; + } + + /** + * Appends a source string as an HTTP comment. + * + * @param value The source string to format. + * @param version The cookie version. + * @return This writer. + */ + public CookieWriter appendValue(String value, int version) { + if (version == 0) { + append(value); + } else { + appendQuotedString(value); + } + + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/DateWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/DateWriter.java index f6467c4527..8c7abcf3d8 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/DateWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/DateWriter.java @@ -1,48 +1,45 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.engine.util.DateUtils; - import java.util.Date; +import org.restlet.engine.util.DateUtils; /** * Date header writer. - * + * * @author Jerome Louvel */ public class DateWriter { - /** - * Writes a date header. - * - * @param date The date to write. - * @return The formatted date. - */ - public static String write(Date date) { - return write(date, false); - } - - /** - * Writes a date header. - * - * @param date The date to write. - * @param cookie Indicates if the date should be in the cookie format. - * @return The formatted date. - */ - public static String write(Date date, boolean cookie) { - if (cookie) { - return DateUtils.format(date, DateUtils.FORMAT_RFC_1036.get(0)); - } - - return DateUtils.format(date); - } - + /** + * Writes a date header. + * + * @param date The date to write. + * @return The formatted date. + */ + public static String write(Date date) { + return write(date, false); + } + + /** + * Writes a date header. + * + * @param date The date to write. + * @param cookie Indicates if the date should be in the cookie format. + * @return The formatted date. + */ + public static String write(Date date, boolean cookie) { + if (cookie) { + return DateUtils.format(date, DateUtils.FORMAT_RFC_1036.getFirst()); + } + + return DateUtils.format(date); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/DimensionReader.java b/org.restlet/src/main/java/org/restlet/engine/header/DimensionReader.java index fb5e64f57d..4c0ef53ae6 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/DimensionReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/DimensionReader.java @@ -1,71 +1,68 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Dimension; -import org.restlet.data.Header; - import java.io.IOException; import java.util.Collection; +import org.restlet.data.Dimension; +import org.restlet.data.Header; /** * Dimension header reader. - * + * * @author Jerome Louvel */ public class DimensionReader extends HeaderReader { - /** - * Adds values to the given collection. - * - * @param header The header to read. - * @param collection The collection to update. - */ - public static void addValues(Header header, Collection collection) { - new DimensionReader(header.getValue()).addValues(collection); - } - - /** - * Constructor. - * - * @param header The header to read. - */ - public DimensionReader(String header) { - super(header); - } + /** + * Adds values to the given collection. + * + * @param header The header to read. + * @param collection The collection to update. + */ + public static void addValues(Header header, Collection collection) { + new DimensionReader(header.getValue()).addValues(collection); + } - @Override - public Dimension readValue() throws IOException { - Dimension result = null; - String value = readRawValue(); + /** + * Constructor. + * + * @param header The header to read. + */ + public DimensionReader(String header) { + super(header); + } - if (value != null) { - if (value.equalsIgnoreCase(HeaderConstants.HEADER_ACCEPT)) { - result = Dimension.MEDIA_TYPE; - } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_ACCEPT_CHARSET)) { - result = Dimension.CHARACTER_SET; - } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_ACCEPT_ENCODING)) { - result = Dimension.ENCODING; - } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_ACCEPT_LANGUAGE)) { - result = Dimension.LANGUAGE; - } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_AUTHORIZATION)) { - result = Dimension.AUTHORIZATION; - } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_USER_AGENT)) { - result = Dimension.CLIENT_AGENT; - } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_ORIGIN)) { - result = Dimension.ORIGIN; - } else if (value.equals("*")) { - result = Dimension.UNSPECIFIED; - } - } + @Override + public Dimension readValue() throws IOException { + Dimension result = null; + String value = readRawValue(); - return result; - } + if (value != null) { + if (value.equalsIgnoreCase(HeaderConstants.HEADER_ACCEPT)) { + result = Dimension.MEDIA_TYPE; + } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_ACCEPT_CHARSET)) { + result = Dimension.CHARACTER_SET; + } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_ACCEPT_ENCODING)) { + result = Dimension.ENCODING; + } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_ACCEPT_LANGUAGE)) { + result = Dimension.LANGUAGE; + } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_AUTHORIZATION)) { + result = Dimension.AUTHORIZATION; + } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_USER_AGENT)) { + result = Dimension.CLIENT_AGENT; + } else if (value.equalsIgnoreCase(HeaderConstants.HEADER_ORIGIN)) { + result = Dimension.ORIGIN; + } else if (value.equals("*")) { + result = Dimension.UNSPECIFIED; + } + } + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/DimensionWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/DimensionWriter.java index 80c7d47f54..19d1d771b5 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/DimensionWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/DimensionWriter.java @@ -1,85 +1,83 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Dimension; - import java.util.Collection; +import org.restlet.data.Dimension; /** * Dimension header writer. - * + * * @author Thierry Boileau */ public class DimensionWriter extends HeaderWriter { - /** - * Creates a vary header from the given dimensions. - * - * @param dimensions The dimensions to copy to the response. - * @return Returns the Vary header or null, if dimensions is null or empty. - */ - public static String write(Collection dimensions) { - return new DimensionWriter().append(dimensions).toString(); - } - - /** - * Appends a collection of dimensions as a header. - * - * @param dimensions The dimensions to format. - * @return This writer. - */ - public DimensionWriter append(Collection dimensions) { - if ((dimensions != null) && !dimensions.isEmpty()) { - if (dimensions.contains(Dimension.CLIENT_ADDRESS) || dimensions.contains(Dimension.TIME) - || dimensions.contains(Dimension.UNSPECIFIED)) { - // From an HTTP point of view the representations can - // vary in unspecified ways - append("*"); - } else { - boolean first = true; + /** + * Creates a "vary" header from the given dimensions. + * + * @param dimensions The dimensions to copy to the response. + * @return Returns the "Vary" header or null if dimensions are null or empty. + */ + public static String write(Collection dimensions) { + return new DimensionWriter().append(dimensions).toString(); + } - for (Dimension dimension : dimensions) { - if (first) { - first = false; - } else { - append(", "); - } + /** + * Appends a collection of dimensions as a header. + * + * @param dimensions The dimensions to format. + * @return This writer. + */ + @Override + public DimensionWriter append(Collection dimensions) { + if ((dimensions != null) && !dimensions.isEmpty()) { + if (dimensions.contains(Dimension.CLIENT_ADDRESS) + || dimensions.contains(Dimension.TIME) + || dimensions.contains(Dimension.UNSPECIFIED)) { + // From an HTTP point of view, the representations can vary in unspecified ways + append("*"); + } else { + boolean first = true; - append(dimension); - } - } - } + for (Dimension dimension : dimensions) { + if (first) { + first = false; + } else { + append(", "); + } - return this; - } + append(dimension); + } + } + } - @Override - public HeaderWriter append(Dimension dimension) { - if (dimension == Dimension.CHARACTER_SET) { - append(HeaderConstants.HEADER_ACCEPT_CHARSET); - } else if (dimension == Dimension.CLIENT_AGENT) { - append(HeaderConstants.HEADER_USER_AGENT); - } else if (dimension == Dimension.ENCODING) { - append(HeaderConstants.HEADER_ACCEPT_ENCODING); - } else if (dimension == Dimension.LANGUAGE) { - append(HeaderConstants.HEADER_ACCEPT_LANGUAGE); - } else if (dimension == Dimension.MEDIA_TYPE) { - append(HeaderConstants.HEADER_ACCEPT); - } else if (dimension == Dimension.AUTHORIZATION) { - append(HeaderConstants.HEADER_AUTHORIZATION); - } else if (dimension == Dimension.ORIGIN) { - append(HeaderConstants.HEADER_ORIGIN); - } + return this; + } - return this; - } + @Override + public HeaderWriter append(Dimension dimension) { + if (dimension == Dimension.CHARACTER_SET) { + append(HeaderConstants.HEADER_ACCEPT_CHARSET); + } else if (dimension == Dimension.CLIENT_AGENT) { + append(HeaderConstants.HEADER_USER_AGENT); + } else if (dimension == Dimension.ENCODING) { + append(HeaderConstants.HEADER_ACCEPT_ENCODING); + } else if (dimension == Dimension.LANGUAGE) { + append(HeaderConstants.HEADER_ACCEPT_LANGUAGE); + } else if (dimension == Dimension.MEDIA_TYPE) { + append(HeaderConstants.HEADER_ACCEPT); + } else if (dimension == Dimension.AUTHORIZATION) { + append(HeaderConstants.HEADER_AUTHORIZATION); + } else if (dimension == Dimension.ORIGIN) { + append(HeaderConstants.HEADER_ORIGIN); + } + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/DispositionReader.java b/org.restlet/src/main/java/org/restlet/engine/header/DispositionReader.java index e23228807a..d38eb763cd 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/DispositionReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/DispositionReader.java @@ -1,60 +1,57 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.io.IOException; import org.restlet.data.Disposition; import org.restlet.data.Parameter; -import java.io.IOException; - /** * Disposition header reader. - * + * * @author Thierry Boileau */ public class DispositionReader extends HeaderReader { - /** - * Constructor. - * - * @param header The header to read. - */ - public DispositionReader(String header) { - super(header); - } - - @Override - public Disposition readValue() throws IOException { - Disposition result = null; - String type = readToken(); - - if (!type.isEmpty()) { - result = new Disposition(); - result.setType(type); - - if (skipParameterSeparator()) { - Parameter param = readParameter(); - - while (param != null) { - result.getParameters().add(param); - - if (skipParameterSeparator()) { - param = readParameter(); - } else { - param = null; - } - } - } - } - - return result; - } - + /** + * Constructor. + * + * @param header The header to read. + */ + public DispositionReader(String header) { + super(header); + } + + @Override + public Disposition readValue() throws IOException { + Disposition result = null; + String type = readToken(); + + if (!type.isEmpty()) { + result = new Disposition(); + result.setType(type); + + if (skipParameterSeparator()) { + Parameter param = readParameter(); + + while (param != null) { + result.getParameters().add(param); + + if (skipParameterSeparator()) { + param = readParameter(); + } else { + param = null; + } + } + } + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/DispositionWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/DispositionWriter.java index b5d19a1e0d..e5a740466f 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/DispositionWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/DispositionWriter.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; import org.restlet.data.Disposition; @@ -14,42 +13,41 @@ /** * Disposition header writer. - * + * * @author Thierry Boileau */ public class DispositionWriter extends HeaderWriter { - /** - * Formats a disposition. - * - * @param disposition The disposition to format. - * @return The formatted disposition. - */ - public static String write(Disposition disposition) { - return new DispositionWriter().append(disposition).toString(); - } - - @Override - public DispositionWriter append(Disposition disposition) { - if (Disposition.TYPE_NONE.equals(disposition.getType()) || disposition.getType() == null) { - return this; - } - - append(disposition.getType()); - - for (Parameter parameter : disposition.getParameters()) { - append("; "); - append(parameter.getName()); - append("="); - - if (HeaderUtils.isToken(parameter.getValue())) { - append(parameter.getValue()); - } else { - appendQuotedString(parameter.getValue()); - } - } - - return this; - } - + /** + * Formats a disposition. + * + * @param disposition The disposition to format. + * @return The formatted disposition. + */ + public static String write(Disposition disposition) { + return new DispositionWriter().append(disposition).toString(); + } + + @Override + public DispositionWriter append(Disposition disposition) { + if (Disposition.TYPE_NONE.equals(disposition.getType()) || disposition.getType() == null) { + return this; + } + + append(disposition.getType()); + + for (Parameter parameter : disposition.getParameters()) { + append("; "); + append(parameter.getName()); + append("="); + + if (HeaderUtils.isToken(parameter.getValue())) { + append(parameter.getValue()); + } else { + appendQuotedString(parameter.getValue()); + } + } + + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/EncodingReader.java b/org.restlet/src/main/java/org/restlet/engine/header/EncodingReader.java index 1afbc0e976..6992d7235b 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/EncodingReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/EncodingReader.java @@ -1,43 +1,40 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Encoding; - import java.io.IOException; import java.util.Collection; +import org.restlet.data.Encoding; /** * Encoding header reader. - * + * * @author Jerome Louvel */ public class EncodingReader extends HeaderReader { - /** - * Constructor. - * - * @param header The header to read. - */ - public EncodingReader(String header) { - super(header); - } - - @Override - protected boolean canAdd(Encoding value, Collection values) { - return value != null && !Encoding.IDENTITY.equals(value); - } - - @Override - public Encoding readValue() throws IOException { - return Encoding.valueOf(readToken()); - } - + /** + * Constructor. + * + * @param header The header to read. + */ + public EncodingReader(String header) { + super(header); + } + + @Override + protected boolean canAdd(Encoding value, Collection values) { + return value != null && !Encoding.IDENTITY.equals(value); + } + + @Override + public Encoding readValue() throws IOException { + return Encoding.valueOf(readToken()); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/EncodingWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/EncodingWriter.java index 38c37c4d71..ec6fa6fa7b 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/EncodingWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/EncodingWriter.java @@ -1,37 +1,35 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Encoding; - import java.util.List; +import org.restlet.data.Encoding; /** * Encoding header writer. - * + * * @author Jerome Louvel */ public class EncodingWriter extends MetadataWriter { - /** - * Writes a list of encodings. - * - * @param encodings The encodings to write. - * @return This writer. - */ - public static String write(List encodings) { - return new EncodingWriter().append(encodings).toString(); - } + /** + * Writes a list of encodings. + * + * @param encodings The encodings to write. + * @return This writer. + */ + public static String write(List encodings) { + return new EncodingWriter().append(encodings).toString(); + } - @Override - protected boolean canWrite(Encoding encoding) { - return !encoding.equals(Encoding.IDENTITY); - } + @Override + protected boolean canWrite(Encoding encoding) { + return !encoding.equals(Encoding.IDENTITY); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/ExpectationReader.java b/org.restlet/src/main/java/org/restlet/engine/header/ExpectationReader.java index 8e69fc05e4..79180b00bc 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/ExpectationReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/ExpectationReader.java @@ -1,55 +1,52 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.io.IOException; import org.restlet.data.ClientInfo; import org.restlet.data.Expectation; -import java.io.IOException; - /** * Expectation header reader. - * + * * @author Jerome Louvel */ public class ExpectationReader extends HeaderReader { - /** - * Adds values to the given collection. - * - * @param header The header to read. - * @param clientInfo The client info to update. - */ - public static void addValues(String header, ClientInfo clientInfo) { - if (header != null) { - new ExpectationReader(header).addValues(clientInfo.getExpectations()); - } - } - - /** - * Constructor. - * - * @param header The header to read. - */ - public ExpectationReader(String header) { - super(header); - } - - @Override - public Expectation readValue() throws IOException { - Expectation result = readNamedValue(Expectation.class); - - while (skipParameterSeparator()) { - result.getParameters().add(readParameter()); - } - - return result; - } - + /** + * Adds values to the given collection. + * + * @param header The header to read. + * @param clientInfo The client info to update. + */ + public static void addValues(String header, ClientInfo clientInfo) { + if (header != null) { + new ExpectationReader(header).addValues(clientInfo.getExpectations()); + } + } + + /** + * Constructor. + * + * @param header The header to read. + */ + public ExpectationReader(String header) { + super(header); + } + + @Override + public Expectation readValue() throws IOException { + Expectation result = readNamedValue(Expectation.class); + + while (skipParameterSeparator()) { + result.getParameters().add(readParameter()); + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/ExpectationWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/ExpectationWriter.java index 1b200bb3d0..2488416d54 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/ExpectationWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/ExpectationWriter.java @@ -1,50 +1,47 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.util.List; import org.restlet.data.Expectation; import org.restlet.data.Parameter; -import java.util.List; - /** * Expectation header writer. - * + * * @author Jerome Louvel */ public class ExpectationWriter extends HeaderWriter { - /** - * Writes a list of expectations with a comma separator. - * - * @param expectations The list of expectations. - * @return The formatted list of expectations. - */ - public static String write(List expectations) { - return new ExpectationWriter().append(expectations).toString(); - } - - @Override - public ExpectationWriter append(Expectation expectation) { - if ((expectation.getName() != null) && (!expectation.getName().isEmpty())) { - appendExtension(expectation); - - if (!expectation.getParameters().isEmpty()) { - for (Parameter param : expectation.getParameters()) { - appendParameterSeparator(); - appendExtension(param); - } - } - } - - return this; - } - + /** + * Writes a list of expectations with a comma separator. + * + * @param expectations The list of expectations. + * @return The formatted list of expectations. + */ + public static String write(List expectations) { + return new ExpectationWriter().append(expectations).toString(); + } + + @Override + public ExpectationWriter append(Expectation expectation) { + if ((expectation.getName() != null) && (!expectation.getName().isEmpty())) { + appendExtension(expectation); + + if (!expectation.getParameters().isEmpty()) { + for (Parameter param : expectation.getParameters()) { + appendParameterSeparator(); + appendExtension(param); + } + } + } + + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/HeaderConstants.java b/org.restlet/src/main/java/org/restlet/engine/header/HeaderConstants.java index e784a80851..e02064219f 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/HeaderConstants.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/HeaderConstants.java @@ -1,202 +1,208 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; /** * Constants related to the HTTP protocol. - * + * * @author Jerome Louvel */ public final class HeaderConstants { + private HeaderConstants() { + /* This utility class should not be instantiated */ + } - // -------------------- - // --- Expectations --- - // -------------------- + // -------------------- + // --- Expectations --- + // -------------------- - public static final String EXPECT_CONTINUE = "100-continue"; + public static final String EXPECT_CONTINUE = "100-continue"; - // ------------------------ - // --- Cache directives --- - // ------------------------ + // ------------------------ + // --- Cache directives --- + // ------------------------ - public static final String CACHE_NO_CACHE = "no-cache"; + public static final String CACHE_NO_CACHE = "no-cache"; - public static final String CACHE_NO_STORE = "no-store"; + public static final String CACHE_NO_STORE = "no-store"; - public static final String CACHE_MAX_AGE = "max-age"; + public static final String CACHE_MAX_AGE = "max-age"; - public static final String CACHE_MAX_STALE = "max-stale"; + public static final String CACHE_MAX_STALE = "max-stale"; - public static final String CACHE_MIN_FRESH = "min-fresh"; + public static final String CACHE_MIN_FRESH = "min-fresh"; - public static final String CACHE_NO_TRANSFORM = "no-transform"; + public static final String CACHE_NO_TRANSFORM = "no-transform"; - public static final String CACHE_ONLY_IF_CACHED = "only-if-cached"; + public static final String CACHE_ONLY_IF_CACHED = "only-if-cached"; - public static final String CACHE_PUBLIC = "public"; + public static final String CACHE_PUBLIC = "public"; - public static final String CACHE_PRIVATE = "private"; + public static final String CACHE_PRIVATE = "private"; - public static final String CACHE_MUST_REVALIDATE = "must-revalidate"; + public static final String CACHE_MUST_REVALIDATE = "must-revalidate"; - public static final String CACHE_PROXY_MUST_REVALIDATE = "proxy-revalidate"; + public static final String CACHE_PROXY_MUST_REVALIDATE = "proxy-revalidate"; - public static final String CACHE_SHARED_MAX_AGE = "s-maxage"; + public static final String CACHE_SHARED_MAX_AGE = "s-maxage"; - // --------------------- - // --- Header names --- - // --------------------- + // --------------------- + // --- Header names --- + // --------------------- - public static final String HEADER_ACCEPT = "Accept"; + public static final String HEADER_ACCEPT = "Accept"; - public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset"; + public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset"; - public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; + public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; - public static final String HEADER_ACCEPT_LANGUAGE = "Accept-Language"; + public static final String HEADER_ACCEPT_LANGUAGE = "Accept-Language"; - public static final String HEADER_ACCEPT_PATCH = "Accept-Patch"; + public static final String HEADER_ACCEPT_PATCH = "Accept-Patch"; - public static final String HEADER_ACCEPT_RANGES = "Accept-Ranges"; + public static final String HEADER_ACCEPT_RANGES = "Accept-Ranges"; - public static final String HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + public static final String HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS = + "Access-Control-Allow-Credentials"; - public static final String HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + public static final String HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; - public static final String HEADER_ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + public static final String HEADER_ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; - public static final String HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + public static final String HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; - public static final String HEADER_ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + public static final String HEADER_ACCESS_CONTROL_EXPOSE_HEADERS = + "Access-Control-Expose-Headers"; - public static final String HEADER_ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + public static final String HEADER_ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; - public static final String HEADER_ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + public static final String HEADER_ACCESS_CONTROL_REQUEST_HEADERS = + "Access-Control-Request-Headers"; - public static final String HEADER_ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; + public static final String HEADER_ACCESS_CONTROL_REQUEST_METHOD = + "Access-Control-Request-Method"; - public static final String HEADER_AGE = "Age"; + public static final String HEADER_AGE = "Age"; - public static final String HEADER_ALLOW = "Allow"; + public static final String HEADER_ALLOW = "Allow"; - public static final String HEADER_AUTHENTICATION_INFO = "Authentication-Info"; + public static final String HEADER_AUTHENTICATION_INFO = "Authentication-Info"; - public static final String HEADER_AUTHORIZATION = "Authorization"; + public static final String HEADER_AUTHORIZATION = "Authorization"; - public static final String HEADER_CACHE_CONTROL = "Cache-Control"; + public static final String HEADER_CACHE_CONTROL = "Cache-Control"; - public static final String HEADER_CONNECTION = "Connection"; + public static final String HEADER_CONNECTION = "Connection"; - public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition"; + public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition"; - public static final String HEADER_CONTENT_ENCODING = "Content-Encoding"; + public static final String HEADER_CONTENT_ENCODING = "Content-Encoding"; - public static final String HEADER_CONTENT_LANGUAGE = "Content-Language"; + public static final String HEADER_CONTENT_LANGUAGE = "Content-Language"; - public static final String HEADER_CONTENT_LENGTH = "Content-Length"; + public static final String HEADER_CONTENT_LENGTH = "Content-Length"; - public static final String HEADER_CONTENT_LOCATION = "Content-Location"; + public static final String HEADER_CONTENT_LOCATION = "Content-Location"; - public static final String HEADER_CONTENT_MD5 = "Content-MD5"; + public static final String HEADER_CONTENT_MD5 = "Content-MD5"; - public static final String HEADER_CONTENT_RANGE = "Content-Range"; + public static final String HEADER_CONTENT_RANGE = "Content-Range"; - public static final String HEADER_CONTENT_TYPE = "Content-Type"; + public static final String HEADER_CONTENT_TYPE = "Content-Type"; - public static final String HEADER_COOKIE = "Cookie"; + public static final String HEADER_COOKIE = "Cookie"; - public static final String HEADER_DATE = "Date"; + public static final String HEADER_DATE = "Date"; - public static final String HEADER_ETAG = "ETag"; + public static final String HEADER_ETAG = "ETag"; - public static final String HEADER_EXPECT = "Expect"; + public static final String HEADER_EXPECT = "Expect"; - public static final String HEADER_EXPIRES = "Expires"; + public static final String HEADER_EXPIRES = "Expires"; - public static final String HEADER_FROM = "From"; + public static final String HEADER_FROM = "From"; - public static final String HEADER_HOST = "Host"; + public static final String HEADER_HOST = "Host"; - public static final String HEADER_IF_MATCH = "If-Match"; + public static final String HEADER_IF_MATCH = "If-Match"; - public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since"; + public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since"; - public static final String HEADER_IF_NONE_MATCH = "If-None-Match"; + public static final String HEADER_IF_NONE_MATCH = "If-None-Match"; - public static final String HEADER_IF_RANGE = "If-Range"; + public static final String HEADER_IF_RANGE = "If-Range"; - public static final String HEADER_IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + public static final String HEADER_IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; - public static final String HEADER_LAST_MODIFIED = "Last-Modified"; + public static final String HEADER_LAST_MODIFIED = "Last-Modified"; - public static final String HEADER_LOCATION = "Location"; + public static final String HEADER_LOCATION = "Location"; - public static final String HEADER_MAX_FORWARDS = "Max-Forwards"; + public static final String HEADER_MAX_FORWARDS = "Max-Forwards"; - public static final String HEADER_ORIGIN = "Origin"; + public static final String HEADER_ORIGIN = "Origin"; - public static final String HEADER_PRAGMA = "Pragma"; + public static final String HEADER_PRAGMA = "Pragma"; - public static final String HEADER_PROXY_AUTHENTICATE = "Proxy-Authenticate"; + public static final String HEADER_PROXY_AUTHENTICATE = "Proxy-Authenticate"; - public static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; + public static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; - public static final String HEADER_RANGE = "Range"; + public static final String HEADER_RANGE = "Range"; - public static final String HEADER_REFERRER = "Referer"; + public static final String HEADER_REFERRER = "Referer"; - public static final String HEADER_RETRY_AFTER = "Retry-After"; + public static final String HEADER_RETRY_AFTER = "Retry-After"; - public static final String HEADER_SERVER = "Server"; + public static final String HEADER_SERVER = "Server"; - public static final String HEADER_SET_COOKIE = "Set-Cookie"; + public static final String HEADER_SET_COOKIE = "Set-Cookie"; - public static final String HEADER_SET_COOKIE2 = "Set-Cookie2"; + public static final String HEADER_SET_COOKIE2 = "Set-Cookie2"; - public static final String HEADER_SLUG = "Slug"; + public static final String HEADER_SLUG = "Slug"; - public static final String HEADER_TRAILER = "Trailer"; + public static final String HEADER_TRAILER = "Trailer"; - public static final String HEADER_TRANSFER_ENCODING = "Transfer-Encoding"; + public static final String HEADER_TRANSFER_ENCODING = "Transfer-Encoding"; - public static final String HEADER_TRANSFER_EXTENSION = "TE"; + public static final String HEADER_TRANSFER_EXTENSION = "TE"; - public static final String HEADER_UPGRADE = "Upgrade"; + public static final String HEADER_UPGRADE = "Upgrade"; - public static final String HEADER_USER_AGENT = "User-Agent"; + public static final String HEADER_USER_AGENT = "User-Agent"; - public static final String HEADER_VARY = "Vary"; + public static final String HEADER_VARY = "Vary"; - public static final String HEADER_VIA = "Via"; + public static final String HEADER_VIA = "Via"; - public static final String HEADER_WARNING = "Warning"; + public static final String HEADER_WARNING = "Warning"; - public static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; + public static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - public static final String HEADER_X_FORWARDED_FOR = "X-Forwarded-For"; + public static final String HEADER_X_FORWARDED_FOR = "X-Forwarded-For"; - public static final String HEADER_X_FORWARDED_PORT = "X-Forwarded-Port"; + public static final String HEADER_X_FORWARDED_PORT = "X-Forwarded-Port"; - public static final String HEADER_X_FORWARDED_PROTO = "X-Forwarded-Proto"; + public static final String HEADER_X_FORWARDED_PROTO = "X-Forwarded-Proto"; - public static final String HEADER_X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override"; + public static final String HEADER_X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override"; - // ------------------------- - // --- Attribute names --- - // ------------------------- + // ------------------------- + // --- Attribute names --- + // ------------------------- - public static final String ATTRIBUTE_HEADERS = "org.restlet.http.headers"; + public static final String ATTRIBUTE_HEADERS = "org.restlet.http.headers"; - public static final String ATTRIBUTE_VERSION = "org.restlet.http.version"; + public static final String ATTRIBUTE_VERSION = "org.restlet.http.version"; - public static final String ATTRIBUTE_HTTPS_KEY_SIZE = "org.restlet.https.keySize"; + public static final String ATTRIBUTE_HTTPS_KEY_SIZE = "org.restlet.https.keySize"; - public static final String ATTRIBUTE_HTTPS_SSL_SESSION_ID = "org.restlet.https.sslSessionId"; + public static final String ATTRIBUTE_HTTPS_SSL_SESSION_ID = "org.restlet.https.sslSessionId"; } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/HeaderReader.java b/org.restlet/src/main/java/org/restlet/engine/header/HeaderReader.java index c5b2794fae..02eba3f670 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/HeaderReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/HeaderReader.java @@ -1,20 +1,24 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.Context; -import org.restlet.data.Encoding; -import org.restlet.data.Header; -import org.restlet.data.Parameter; -import org.restlet.engine.util.DateUtils; -import org.restlet.util.NamedValue; +import static org.restlet.engine.header.HeaderUtils.isCarriageReturn; +import static org.restlet.engine.header.HeaderUtils.isComma; +import static org.restlet.engine.header.HeaderUtils.isCommentText; +import static org.restlet.engine.header.HeaderUtils.isDoubleQuote; +import static org.restlet.engine.header.HeaderUtils.isLineFeed; +import static org.restlet.engine.header.HeaderUtils.isLinearWhiteSpace; +import static org.restlet.engine.header.HeaderUtils.isQuoteCharacter; +import static org.restlet.engine.header.HeaderUtils.isQuotedText; +import static org.restlet.engine.header.HeaderUtils.isSemiColon; +import static org.restlet.engine.header.HeaderUtils.isSpace; +import static org.restlet.engine.header.HeaderUtils.isTokenChar; import java.io.IOException; import java.io.InputStream; @@ -23,417 +27,425 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; - -import static org.restlet.engine.header.HeaderUtils.*; +import org.restlet.Context; +import org.restlet.data.Encoding; +import org.restlet.data.Header; +import org.restlet.data.Parameter; +import org.restlet.engine.util.DateUtils; +import org.restlet.util.NamedValue; /** * HTTP-style header reader. - * - * @param The header value target type. There can be multiple values for a - * single header. + * + * @param The header value target type. There can be multiple values for a single header. * @author Jerome Louvel */ public class HeaderReader { - /** - * Creates a new named value with a null value. - * - * @param name The name. - * @param resultClass The named value class to return. - * @return The new named value. - */ - private static > NV createNamedValue(Class resultClass, String name) { - return createNamedValue(resultClass, name, null); - } - - /** - * Creates a new named value. - * - * @param name The name. - * @param value The value or null. - * @param resultClass The named value class to return. - * @return The new named value. - */ - private static > NV createNamedValue(Class resultClass, String name, - String value) { - try { - return resultClass.getConstructor(String.class, String.class).newInstance(name, value); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to create named value", e); - return null; - } - } - - /** - * Parses a date string. - * - * @param date The date string to parse. - * @param cookie Indicates if the date is in the cookie format. - * @return The parsed date. - */ - public static Date readDate(String date, boolean cookie) { - if (cookie) { - return DateUtils.parse(date, DateUtils.FORMAT_RFC_1036); - } - - return DateUtils.parse(date, DateUtils.FORMAT_RFC_1123); - } - - /** - * Read a header. Return null if the last header was already read. - * - * @param header The header line to parse. - * @return The header read or null. - * @throws IOException - */ - public static Header readHeader(CharSequence header) throws IOException { - Header result = null; - - if (!header.isEmpty()) { - // Detect the end of headers - int start = 0; - int index = 0; - int next = header.charAt(index++); - - if (isCarriageReturn(next)) { - next = header.charAt(index++); - - if (!isLineFeed(next)) { - throw new IOException("Invalid end of headers. Line feed missing after the carriage return."); - } - } else { - result = new Header(); - - // Parse the header name - while ((index < header.length()) && (next != ':')) { - next = header.charAt(index++); - } - - if (next != ':') { - // Colon character is mandatory - throw new IOException("Unable to parse the header name. End of line reached too early."); - } - - result.setName(header.subSequence(start, index - 1).toString()); - - if (index == header.length()) { - // No more content to read. - return result; - } - next = header.charAt(index++); - - while ((index < header.length()) && isSpace(next)) { - // Skip any separator space between colon and header value - next = header.charAt(index++); - } - if (index < header.length()) { - start = index - 1; - - // Parse the header value - result.setValue(header.subSequence(start, header.length()).toString()); - } - - } - } - - return result; - } - - /** - * Read a header. Return null if the last header was already read. - * - * @param is The message input stream. - * @param sb The string builder to reuse. - * @return The header read or null. - * @throws IOException - */ - public static Header readHeader(InputStream is, StringBuilder sb) throws IOException { - Header result = null; - - // Detect the end of headers - int next = is.read(); - - if (isCarriageReturn(next)) { - next = is.read(); - - if (!isLineFeed(next)) { - throw new IOException("Invalid end of headers. Line feed missing after the carriage return."); - } - } else { - result = new Header(); - - // Parse the header name - while ((next != -1) && (next != ':')) { - sb.append((char) next); - next = is.read(); - } - - if (next == -1) { - throw new IOException("Unable to parse the header name. End of stream reached too early."); - } - - result.setName(sb.toString()); - sb.delete(0, sb.length()); - next = is.read(); - - while (isSpace(next)) { - // Skip any separator space between colon and header value - next = is.read(); - } - - // Parse the header value - while ((next != -1) && (!isCarriageReturn(next))) { - sb.append((char) next); - next = is.read(); - } - - if (next == -1) { - throw new IOException("Unable to parse the header value. End of stream reached too early."); - } - - next = is.read(); - - if (isLineFeed(next)) { - result.setValue(sb.toString()); - sb.delete(0, sb.length()); - } else { - throw new IOException( - "Unable to parse the HTTP header value. The carriage return must be followed by a line feed."); - } - } - - return result; - } - - /** The header to read. */ - private final String header; - - /** The current read index (or -1 if not reading anymore). */ - private volatile int index; - - /** The current mark. */ - private volatile int mark; - - /** - * Constructor. - * - * @param header The header to read. - */ - public HeaderReader(String header) { - this.header = header; - this.index = ((header == null) || (header.isEmpty())) ? -1 : 0; - this.mark = index; - } - - /** - * Adds values to the given list. - * - * @param values The list of values to update. - */ - public void addValues(Collection values) { - try { - // Skip leading spaces - skipSpaces(); - boolean cont = true; - do { - int i = index; - - // Read the first value - V nextValue = readValue(); - if (canAdd(nextValue, values)) { - // Add the value to the list - values.add(nextValue); - } - - // Attempt to skip the value separator - skipValueSeparator(); - if (index == -1) { - cont = false; - } else if (i == index) { - // Infinite loop - throw new IOException("The reading of one header initiates an infinite loop"); - } - } while (cont); - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.INFO, "Unable to read a header", ioe); - } - } - - /** - * Indicates if the value can be added the the list. Useful to prevent the - * addition of {@link Encoding#IDENTITY} constants for example. By default it - * returns true for non null values. - * - * @param value The value to add. - * - * @param values The target collection. - * @return True if the value can be added. - */ - protected boolean canAdd(V value, Collection values) { - return value != null && !values.contains(value); - } - - /** - * Creates a new parameter with a null value. Can be overridden. - * - * @param name The parameter name. - * @return The new parameter. - */ - protected final Parameter createParameter(String name) { - return createParameter(name, null); - } - - /** - * Creates a new parameter. Can be overridden. - * - * @param name The parameter name. - * @param value The parameter value or null. - * @return The new parameter. - */ - protected Parameter createParameter(String name, String value) { - return new Parameter(name, value); - } - - /** - * Marks the current position in this reader. A subsequent call to the - * reset method repositions this reader at the last marked - * position. - */ - public void mark() { - mark = index; - } - - /** - * Reads the next character without moving the reader index. - * - * @return The next character. - */ - public int peek() { - int result = -1; - - if (this.index != -1) { - result = this.header.charAt(this.index); - } - - return result; - } - - /** - * Reads the next character. - * - * @return The next character. - */ - public int read() { - int result = -1; - - if (this.index >= 0) { - result = this.header.charAt(this.index++); - - if (this.index >= this.header.length()) { - this.index = -1; - } - } - - return result; - } - - /** - * Reads a parameter value which is either a token or a quoted string. - * - * @return A parameter value. - * @throws IOException - */ - public String readActualNamedValue() throws IOException { - String result = null; - - // Discard any leading space - skipSpaces(); - - // Detect if quoted string or token available - int nextChar = peek(); - - if (isDoubleQuote(nextChar)) { - result = readQuotedString(); - } else if (isTokenChar(nextChar)) { - result = readToken(); - } - - return result; - } - - /** - * Reads the next comment. The first character must be a parenthesis. - * - * @return The next comment. - * @throws IOException - */ - public String readComment() throws IOException { - String result = null; - int next = read(); - - // First character must be a parenthesis - if (next == '(') { - StringBuilder buffer = new StringBuilder(); - - while (result == null) { - next = read(); - - if (isCommentText(next)) { - buffer.append((char) next); - } else if (isQuoteCharacter(next)) { - // Start of a quoted pair (escape sequence) - buffer.append((char) read()); - } else if (next == '(') { - // Nested comment - buffer.append('(').append(readComment()).append(')'); - } else if (next == ')') { - // End of comment - result = buffer.toString(); - } else if (next == -1) { - throw new IOException("Unexpected end of comment. Please check your value"); - } else { - throw new IOException( - "Invalid character \"" + next + "\" detected in comment. Please check your value"); - } - } - } else { - throw new IOException("A comment must start with a parenthesis"); - } - - return result; - } - - /** - * Reads the next digits. - * - * @return The next digits. - */ - public String readDigits() { - StringBuilder sb = new StringBuilder(); - int next = read(); - - while (isTokenChar(next)) { - sb.append((char) next); - next = read(); - } - - // Unread the last character (separator or end marker) - unread(); - - return sb.toString(); - } - - /** - * Reads the next pair as a parameter. - * - * @param resultClass The named value class to return. - * @return The next pair as a parameter. - * @throws IOException - */ - public > NV readNamedValue(Class resultClass) throws IOException { - NV result = null; - String name = readToken(); - int nextChar = read(); + /** + * Creates a new NamedValue with a null value. + * + * @param name The name. + * @param resultClass The NamedValue class to return. + * @return The new NamedValue. + */ + private static > N createNamedValue( + Class resultClass, String name) { + return createNamedValue(resultClass, name, null); + } + + /** + * Creates a new named value. + * + * @param name The name. + * @param value The value or null. + * @param resultClass The named value class to return. + * @return The new named value. + */ + private static > N createNamedValue( + Class resultClass, String name, String value) { + try { + return resultClass.getConstructor(String.class, String.class).newInstance(name, value); + } catch (Exception e) { + Context.getCurrentLogger().log(Level.WARNING, "Unable to create named value", e); + return null; + } + } + + /** + * Parses a date string. + * + * @param date The date string to parse. + * @param cookie Indicates if the date is in the cookie format. + * @return The parsed date. + */ + public static Date readDate(String date, boolean cookie) { + if (cookie) { + return DateUtils.parse(date, DateUtils.FORMAT_RFC_1036); + } + + return DateUtils.parse(date, DateUtils.FORMAT_RFC_1123); + } + + /** + * Read a header. Return null if the last header was already read. + * + * @param header The header line to parse. + * @return The header read or null. + * @throws IOException + */ + public static Header readHeader(CharSequence header) throws IOException { + if (header.isEmpty()) { + return null; + } + + int index = 0; + int next = header.charAt(index++); + + if (isCarriageReturn(next)) { // Detect the end of headers + next = header.charAt(index); + + if (isLineFeed(next)) { + return null; + } else { + throw new IOException( + "Invalid end of headers. Line feed missing after the carriage return."); + } + } + + final Header result = new Header(); + + // Parse the header name + while ((index < header.length()) && (next != ':')) { + next = header.charAt(index++); + } + + if (next != ':') { + // Colon character is mandatory + throw new IOException( + "Unable to parse the header name. End of line reached too early."); + } + + int start = 0; + result.setName(header.subSequence(start, index - 1).toString()); + + if (index == header.length()) { + // No more content to read. + return result; + } + next = header.charAt(index++); + + while ((index < header.length()) && isSpace(next)) { + // Skip any separator space between colon and header value + next = header.charAt(index++); + } + if (index < header.length()) { + start = index - 1; + + // Parse the header value + result.setValue(header.subSequence(start, header.length()).toString()); + } + + return result; + } + + /** + * Read a header. Return null if the last header was already read. + * + * @param is The message input stream. + * @param sb The string builder to reuse. + * @return The header read or null. + * @throws IOException + */ + public static Header readHeader(InputStream is, StringBuilder sb) throws IOException { + + int next = is.read(); + + if (isCarriageReturn(next)) { // Detect the end of headers + next = is.read(); + + if (isLineFeed(next)) { + return null; + } else { + throw new IOException( + "Invalid end of headers. Line feed missing after the carriage return."); + } + } + + final Header result = new Header(); + + // Parse the header name + while ((next != -1) && (next != ':')) { + sb.append((char) next); + next = is.read(); + } + + if (next == -1) { + throw new IOException( + "Unable to parse the header name. End of stream reached too early."); + } + + result.setName(sb.toString()); + sb.delete(0, sb.length()); + next = is.read(); + + while (isSpace(next)) { // Skip any separator space between colon and header value + next = is.read(); + } + + // Parse the header value + while ((next != -1) && (!isCarriageReturn(next))) { + sb.append((char) next); + next = is.read(); + } + + if (next == -1) { + throw new IOException( + "Unable to parse the header value. End of stream reached too early."); + } + + next = is.read(); + + if (isLineFeed(next)) { + result.setValue(sb.toString()); + sb.delete(0, sb.length()); + } else { + throw new IOException( + "Unable to parse the HTTP header value. The carriage return must be followed by a line feed."); + } + + return result; + } + + /** The header to read. */ + private final String header; + + /** The current read index (or -1 if not reading anymore). */ + private volatile int index; + + /** The current mark. */ + private volatile int mark; + + /** + * Constructor. + * + * @param header The header to read. + */ + public HeaderReader(String header) { + this.header = header; + this.index = ((header == null) || (header.isEmpty())) ? -1 : 0; + this.mark = index; + } + + /** + * Adds values to the given list. + * + * @param values The list of values to update. + */ + public void addValues(Collection values) { + try { + // Skip leading spaces + skipSpaces(); + boolean cont = true; + do { + int i = index; + + // Read the first value + V nextValue = readValue(); + if (canAdd(nextValue, values)) { + // Add the value to the list + values.add(nextValue); + } + + // Attempt to skip the value separator + skipValueSeparator(); + if (index == -1) { + cont = false; + } else if (i == index) { + // Infinite loop + throw new IOException("The reading of one header initiates an infinite loop"); + } + } while (cont); + } catch (IOException ioe) { + Context.getCurrentLogger().log(Level.INFO, "Unable to read a header", ioe); + } + } + + /** + * Indicates if the value can be added to the list. Useful to prevent the addition of {@link + * Encoding#IDENTITY} constants for example. By default, it returns true for non-null values. + * + * @param value The value to add. + * @param values The target collection. + * @return True if the value can be added. + */ + protected boolean canAdd(V value, Collection values) { + return value != null && !values.contains(value); + } + + /** + * Creates a new parameter with a null value. Can be overridden. + * + * @param name The parameter name. + * @return The new parameter. + */ + protected final Parameter createParameter(String name) { + return createParameter(name, null); + } + + /** + * Creates a new parameter. Can be overridden. + * + * @param name The parameter name. + * @param value The parameter value or null. + * @return The new parameter. + */ + protected Parameter createParameter(String name, String value) { + return new Parameter(name, value); + } + + /** + * Marks the current position in this reader. A later call to the reset method + * repositions this reader at the last marked position. + */ + public void mark() { + mark = index; + } + + /** + * Reads the next character without moving the reader index. + * + * @return The next character. + */ + public int peek() { + int result = -1; + + if (this.index != -1) { + result = this.header.charAt(this.index); + } + + return result; + } + + /** + * Reads the next character. + * + * @return The next character. + */ + public int read() { + int result = -1; + + if (this.index >= 0) { + result = this.header.charAt(this.index++); + + if (this.index >= this.header.length()) { + this.index = -1; + } + } + + return result; + } + + /** + * Reads a parameter value, which is either a token or a quoted string. + * + * @return A parameter value. + * @throws IOException + */ + public String readActualNamedValue() throws IOException { + String result = null; + + // Discard any leading space + skipSpaces(); + + // Detect if quoted string or token available + int nextChar = peek(); + + if (isDoubleQuote(nextChar)) { + result = readQuotedString(); + } else if (isTokenChar(nextChar)) { + result = readToken(); + } + + return result; + } + + /** + * Reads the next comment. The first character must be a parenthesis. + * + * @return The next comment. + * @throws IOException + */ + public String readComment() throws IOException { + String result = null; + int next = read(); + + // The first character must be a parenthesis + if (next == '(') { + StringBuilder buffer = new StringBuilder(); + + while (result == null) { + next = read(); + + if (isCommentText(next)) { + buffer.append((char) next); + } else if (isQuoteCharacter(next)) { + // Start of a quoted pair (escape sequence) + buffer.append((char) read()); + } else if (next == '(') { + // Nested comment + buffer.append('(').append(readComment()).append(')'); + } else if (next == ')') { + // End of comment + result = buffer.toString(); + } else if (next == -1) { + throw new IOException("Unexpected end of comment. Please check your value"); + } else { + throw new IOException( + "Invalid character \"" + + next + + "\" detected in comment. Please check your value"); + } + } + } else { + throw new IOException("A comment must start with a parenthesis"); + } + + return result; + } + + /** + * Reads the next digits. + * + * @return The next digits. + */ + public String readDigits() { + StringBuilder sb = new StringBuilder(); + int next = read(); + + while (isTokenChar(next)) { + sb.append((char) next); + next = read(); + } + + // Unread the last character (separator or end marker) + unread(); + + return sb.toString(); + } + + /** + * Reads the next pair as a parameter. + * + * @param resultClass The named value class to return. + * @return The next pair as a parameter. + * @throws IOException + */ + public > N readNamedValue(Class resultClass) + throws IOException { + N result = null; + String name = readToken(); + int nextChar = read(); if (name.isEmpty()) { throw new IOException("Parameter or extension has no name. Please check your value"); @@ -449,251 +461,247 @@ public > NV readNamedValue(Class resultClass) } return result; - } - - /** - * Reads the next pair as a parameter. - * - * @return The next pair as a parameter. - * @throws IOException - */ - public Parameter readParameter() throws IOException { - return readNamedValue(Parameter.class); - } - - /** - * Reads the next quoted string. The first character must be a double quote. - * - * @return The next quoted string. - * @throws IOException - */ - public String readQuotedString() throws IOException { - String result = null; - int next = read(); - - // First character must be a double quote - if (isDoubleQuote(next)) { - StringBuilder buffer = new StringBuilder(); - - while (result == null) { - next = read(); - - if (isQuotedText(next)) { - buffer.append((char) next); - } else if (isQuoteCharacter(next)) { - // Start of a quoted pair (escape sequence) - buffer.append((char) read()); - } else if (isDoubleQuote(next)) { - // End of quoted string - result = buffer.toString(); - } else if (next == -1) { - throw new IOException("Unexpected end of quoted string. Please check your value"); - } else { - throw new IOException( - "Invalid character \"" + next + "\" detected in quoted string. Please check your value"); - } - } - } else { - throw new IOException("A quoted string must start with a double quote"); - } - - return result; - } - - /** - * Read the next text until a space separator is reached. - * - * @return The next text. - */ - public String readRawText() { - // Read value until end or space - StringBuilder sb = null; - int next = read(); - - while ((next != -1) && !isSpace(next) && !isComma(next)) { - if (sb == null) { - sb = new StringBuilder(); - } - - sb.append((char) next); - next = read(); - } - - // Unread the separator - if (isSpace(next) || isComma(next)) { - unread(); - } - - return (sb == null) ? null : sb.toString(); - } - - /** - * Read the next header value of a multi-value header. It skips leading and - * trailing spaces. - * - * @see HTTP - * parsing rule - * - * @return The next header value or null. - */ - public String readRawValue() { - // Skip leading spaces - skipSpaces(); - - // Read value until end or comma - StringBuilder sb = null; - int next = read(); - - while ((next != -1) && !isComma(next)) { - if (sb == null) { - sb = new StringBuilder(); - } - - sb.append((char) next); - next = read(); - } - - // Remove trailing spaces - if (sb != null) { - for (int i = sb.length() - 1; (i >= 0) && isLinearWhiteSpace(sb.charAt(i)); i--) { - sb.deleteCharAt(i); - } - } - - // Unread the separator - if (isComma(next)) { - unread(); - } - - return (sb == null) ? null : sb.toString(); - } - - /** - * Reads the next token. - * - * @return The next token. - */ - public String readToken() { - StringBuilder sb = new StringBuilder(); - int next = read(); - - while (isTokenChar(next)) { - sb.append((char) next); - next = read(); - } - - // Unread the last character (separator or end marker) - unread(); - - return sb.toString(); - } - - /** - * Read the next value. There can be multiple values for a single header. - * Returns null by default. - * - * @return The next value. - */ - public V readValue() throws IOException { - return null; - } - - /** - * Returns a new list with all values added. - * - * @return A new list with all values added. - */ - public List readValues() { - List result = new CopyOnWriteArrayList(); - addValues(result); - return result; - } - - /** - * Repositions this stream to the position at the time the mark - * method was last called on this input stream. - */ - public void reset() { - index = mark; - } - - /** - * Skips the next parameter separator (semi-colon) including leading and - * trailing spaces. - * - * @return True if a separator was effectively skipped. - */ - public boolean skipParameterSeparator() { - boolean result = false; - - // Skip leading spaces - skipSpaces(); - - // Check if next character is a parameter separator - if (isSemiColon(read())) { - result = true; - - // Skip trailing spaces - skipSpaces(); - } else { - // Probably reached the end of the header - unread(); - } - - return result; - } - - /** - * Skips the next spaces. - * - * @return True if spaces were skipped. - */ - public boolean skipSpaces() { - boolean result = false; - int next = peek(); - - while (isLinearWhiteSpace(next) && (next != -1)) { - result = result || isLinearWhiteSpace(next); - read(); - next = peek(); - } - - return result; - } - - /** - * Skips the next value separator (comma) including leading and trailing spaces. - * - * @return True if a separator was effectively skipped. - */ - public boolean skipValueSeparator() { - boolean result = false; - - // Skip leading spaces - skipSpaces(); - - // Check if next character is a value separator - if (isComma(read())) { - result = true; - - // Skip trailing spaces - skipSpaces(); - } else { - // Probably reached the end of the header - unread(); - } - - return result; - } - - /** - * Unreads the last character. - */ - public void unread() { - if (this.index > 0) { - this.index--; - } - } + } + + /** + * Reads the next pair as a parameter. + * + * @return The next pair as a parameter. + * @throws IOException + */ + public Parameter readParameter() throws IOException { + return readNamedValue(Parameter.class); + } + + /** + * Reads the next quoted string. The first character must be a double quote. + * + * @return The next quoted string. + * @throws IOException + */ + public String readQuotedString() throws IOException { + String result = null; + int next = read(); + + // The first character must be a double quote + if (isDoubleQuote(next)) { + StringBuilder buffer = new StringBuilder(); + + while (result == null) { + next = read(); + + if (isQuotedText(next)) { + buffer.append((char) next); + } else if (isQuoteCharacter(next)) { + // Start of a quoted pair (escape sequence) + buffer.append((char) read()); + } else if (isDoubleQuote(next)) { + // End of quoted string + result = buffer.toString(); + } else if (next == -1) { + throw new IOException( + "Unexpected end of quoted string. Please check your value"); + } else { + throw new IOException( + "Invalid character \"" + + next + + "\" detected in quoted string. Please check your value"); + } + } + } else { + throw new IOException("A quoted string must start with a double quote"); + } + + return result; + } + + /** + * Read the next text until a space separator is reached. + * + * @return The next text. + */ + public String readRawText() { + // Read value until end or space + StringBuilder sb = null; + int next = read(); + + while ((next != -1) && !isSpace(next) && !isComma(next)) { + if (sb == null) { + sb = new StringBuilder(); + } + sb.append((char) next); + next = read(); + } + + // Unread the separator + if (isSpace(next) || isComma(next)) { + unread(); + } + + return (sb == null) ? null : sb.toString(); + } + + /** + * Read the next header value of a multi-value header. It skips leading and trailing spaces. + * + * @see HTTP parsing + * rule + * @return The next header value or null. + */ + public String readRawValue() { + // Skip leading spaces + skipSpaces(); + + // Read value until end or comma + StringBuilder sb = null; + int next = read(); + + while ((next != -1) && !isComma(next)) { + if (sb == null) { + sb = new StringBuilder(); + } + + sb.append((char) next); + next = read(); + } + + // Remove trailing spaces + if (sb != null) { + for (int i = sb.length() - 1; (i >= 0) && isLinearWhiteSpace(sb.charAt(i)); i--) { + sb.deleteCharAt(i); + } + } + + // Unread the separator + if (isComma(next)) { + unread(); + } + + return (sb == null) ? null : sb.toString(); + } + + /** + * Reads the next token. + * + * @return The next token. + */ + public String readToken() { + StringBuilder sb = new StringBuilder(); + int next = read(); + + while (isTokenChar(next)) { + sb.append((char) next); + next = read(); + } + + // Unread the last character (separator or end marker) + unread(); + + return sb.toString(); + } + + /** + * Read the next value. There can be multiple values for a single header. Returns null by + * default. + * + * @return The next value. + */ + public V readValue() throws IOException { + return null; + } + + /** + * Returns a new list with all values added. + * + * @return A new list with all values added. + */ + public List readValues() { + List result = new CopyOnWriteArrayList<>(); + addValues(result); + return result; + } + + /** + * Repositions this stream to the position at the time the mark method was last + * called on this input stream. + */ + public void reset() { + index = mark; + } + + /** + * Skips the next parameter separator (semicolon) including leading and trailing spaces. + * + * @return True if a separator was effectively skipped. + */ + public boolean skipParameterSeparator() { + boolean result = false; + + // Skip leading spaces + skipSpaces(); + + // Check if next character is a parameter separator + if (isSemiColon(read())) { + result = true; + + // Skip trailing spaces + skipSpaces(); + } else { + // Probably reached the end of the header + unread(); + } + + return result; + } + + /** + * Skips the next spaces. + * + * @return True if spaces were skipped. + */ + public boolean skipSpaces() { + boolean result = false; + int next = peek(); + + while (isLinearWhiteSpace(next) && (next != -1)) { + result = result || isLinearWhiteSpace(next); + read(); + next = peek(); + } + + return result; + } + + /** + * Skips the next value separator (comma) including leading and trailing spaces. + * + * @return True if a separator was effectively skipped. + */ + public boolean skipValueSeparator() { + boolean result = false; + + // Skip leading spaces + skipSpaces(); + + // Check if the next character is a value separator + if (isComma(read())) { + result = true; + + // Skip trailing spaces + skipSpaces(); + } else { + // Probably reached the end of the header + unread(); + } + + return result; + } + + /** Unread the last character. */ + public void unread() { + if (this.index > 0) { + this.index--; + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/HeaderUtils.java b/org.restlet/src/main/java/org/restlet/engine/header/HeaderUtils.java index 83b73fd68c..ea42d28a84 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/HeaderUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/HeaderUtils.java @@ -1,20 +1,110 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import static java.lang.Boolean.parseBoolean; +import static java.util.logging.Level.WARNING; +import static org.restlet.data.Digest.ALGORITHM_MD5; +import static org.restlet.data.Disposition.TYPE_NONE; +import static org.restlet.data.Method.OPTIONS; +import static org.restlet.data.Range.RANGE_BYTES_UNIT; +import static org.restlet.data.Range.isBytesRange; +import static org.restlet.data.Status.CLIENT_ERROR_METHOD_NOT_ALLOWED; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCEPT; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCEPT_CHARSET; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCEPT_ENCODING; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCEPT_LANGUAGE; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCEPT_PATCH; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCEPT_RANGES; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_HEADERS; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_METHODS; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_ORIGIN; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCESS_CONTROL_EXPOSE_HEADERS; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCESS_CONTROL_MAX_AGE; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCESS_CONTROL_REQUEST_HEADERS; +import static org.restlet.engine.header.HeaderConstants.HEADER_ACCESS_CONTROL_REQUEST_METHOD; +import static org.restlet.engine.header.HeaderConstants.HEADER_AGE; +import static org.restlet.engine.header.HeaderConstants.HEADER_ALLOW; +import static org.restlet.engine.header.HeaderConstants.HEADER_AUTHENTICATION_INFO; +import static org.restlet.engine.header.HeaderConstants.HEADER_AUTHORIZATION; +import static org.restlet.engine.header.HeaderConstants.HEADER_CACHE_CONTROL; +import static org.restlet.engine.header.HeaderConstants.HEADER_CONNECTION; +import static org.restlet.engine.header.HeaderConstants.HEADER_CONTENT_DISPOSITION; +import static org.restlet.engine.header.HeaderConstants.HEADER_CONTENT_ENCODING; +import static org.restlet.engine.header.HeaderConstants.HEADER_CONTENT_LANGUAGE; +import static org.restlet.engine.header.HeaderConstants.HEADER_CONTENT_LENGTH; +import static org.restlet.engine.header.HeaderConstants.HEADER_CONTENT_LOCATION; +import static org.restlet.engine.header.HeaderConstants.HEADER_CONTENT_MD5; +import static org.restlet.engine.header.HeaderConstants.HEADER_CONTENT_RANGE; +import static org.restlet.engine.header.HeaderConstants.HEADER_CONTENT_TYPE; +import static org.restlet.engine.header.HeaderConstants.HEADER_COOKIE; +import static org.restlet.engine.header.HeaderConstants.HEADER_DATE; +import static org.restlet.engine.header.HeaderConstants.HEADER_ETAG; +import static org.restlet.engine.header.HeaderConstants.HEADER_EXPECT; +import static org.restlet.engine.header.HeaderConstants.HEADER_EXPIRES; +import static org.restlet.engine.header.HeaderConstants.HEADER_FROM; +import static org.restlet.engine.header.HeaderConstants.HEADER_HOST; +import static org.restlet.engine.header.HeaderConstants.HEADER_IF_MATCH; +import static org.restlet.engine.header.HeaderConstants.HEADER_IF_MODIFIED_SINCE; +import static org.restlet.engine.header.HeaderConstants.HEADER_IF_NONE_MATCH; +import static org.restlet.engine.header.HeaderConstants.HEADER_IF_RANGE; +import static org.restlet.engine.header.HeaderConstants.HEADER_IF_UNMODIFIED_SINCE; +import static org.restlet.engine.header.HeaderConstants.HEADER_LAST_MODIFIED; +import static org.restlet.engine.header.HeaderConstants.HEADER_LOCATION; +import static org.restlet.engine.header.HeaderConstants.HEADER_MAX_FORWARDS; +import static org.restlet.engine.header.HeaderConstants.HEADER_PRAGMA; +import static org.restlet.engine.header.HeaderConstants.HEADER_PROXY_AUTHENTICATE; +import static org.restlet.engine.header.HeaderConstants.HEADER_PROXY_AUTHORIZATION; +import static org.restlet.engine.header.HeaderConstants.HEADER_RANGE; +import static org.restlet.engine.header.HeaderConstants.HEADER_REFERRER; +import static org.restlet.engine.header.HeaderConstants.HEADER_RETRY_AFTER; +import static org.restlet.engine.header.HeaderConstants.HEADER_SERVER; +import static org.restlet.engine.header.HeaderConstants.HEADER_SET_COOKIE; +import static org.restlet.engine.header.HeaderConstants.HEADER_SET_COOKIE2; +import static org.restlet.engine.header.HeaderConstants.HEADER_TRAILER; +import static org.restlet.engine.header.HeaderConstants.HEADER_TRANSFER_ENCODING; +import static org.restlet.engine.header.HeaderConstants.HEADER_TRANSFER_EXTENSION; +import static org.restlet.engine.header.HeaderConstants.HEADER_UPGRADE; +import static org.restlet.engine.header.HeaderConstants.HEADER_USER_AGENT; +import static org.restlet.engine.header.HeaderConstants.HEADER_VARY; +import static org.restlet.engine.header.HeaderConstants.HEADER_VIA; +import static org.restlet.engine.header.HeaderConstants.HEADER_WARNING; +import static org.restlet.engine.header.HeaderConstants.HEADER_WWW_AUTHENTICATE; +import static org.restlet.engine.util.StringUtils.isNullOrEmpty; +import static org.restlet.representation.Representation.UNKNOWN_SIZE; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Message; import org.restlet.Request; import org.restlet.Response; -import org.restlet.data.*; +import org.restlet.data.AuthenticationInfo; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ClientInfo; +import org.restlet.data.Conditions; +import org.restlet.data.CookieSetting; +import org.restlet.data.Header; +import org.restlet.data.MediaType; +import org.restlet.data.Range; +import org.restlet.data.Reference; +import org.restlet.data.Tag; import org.restlet.engine.Engine; +import org.restlet.engine.security.AuthenticatorUtils; import org.restlet.engine.util.CaseInsensitiveHashSet; import org.restlet.engine.util.DateUtils; import org.restlet.engine.util.StringUtils; @@ -22,1023 +112,1167 @@ import org.restlet.representation.Representation; import org.restlet.util.Series; -import java.io.IOException; -import java.io.OutputStream; -import java.util.*; -import java.util.logging.Level; - -import static java.lang.Boolean.parseBoolean; -import static java.util.logging.Level.WARNING; -import static org.restlet.data.Digest.ALGORITHM_MD5; -import static org.restlet.data.Disposition.TYPE_NONE; -import static org.restlet.data.Method.OPTIONS; -import static org.restlet.data.Range.RANGE_BYTES_UNIT; -import static org.restlet.data.Range.isBytesRange; -import static org.restlet.data.Status.CLIENT_ERROR_METHOD_NOT_ALLOWED; -import static org.restlet.engine.header.HeaderConstants.*; -import static org.restlet.engine.util.StringUtils.isNullOrEmpty; -import static org.restlet.representation.Representation.UNKNOWN_SIZE; - /** * HTTP-style header utilities. - * + * * @author Jerome Louvel */ public class HeaderUtils { - /** - * Standard set of headers which cannot be modified. - */ - private static final Set STANDARD_HEADERS = Collections - .unmodifiableSet(new CaseInsensitiveHashSet(Arrays.asList(HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS, - HEADER_ACCESS_CONTROL_ALLOW_HEADERS, HEADER_ACCESS_CONTROL_ALLOW_METHODS, - HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, - HEADER_ACCESS_CONTROL_MAX_AGE, HEADER_ACCESS_CONTROL_REQUEST_HEADERS, - HEADER_ACCESS_CONTROL_REQUEST_METHOD, HEADER_ACCEPT, HEADER_ACCEPT_CHARSET, HEADER_ACCEPT_ENCODING, - HEADER_ACCEPT_LANGUAGE, HEADER_ACCEPT_PATCH, HEADER_ACCEPT_RANGES, HEADER_AGE, HEADER_ALLOW, - HEADER_AUTHENTICATION_INFO, HEADER_AUTHORIZATION, HEADER_CACHE_CONTROL, HEADER_CONNECTION, - HEADER_CONTENT_DISPOSITION, HEADER_CONTENT_ENCODING, HEADER_CONTENT_LANGUAGE, HEADER_CONTENT_LENGTH, - HEADER_CONTENT_LOCATION, HEADER_CONTENT_MD5, HEADER_CONTENT_RANGE, HEADER_CONTENT_TYPE, - HEADER_COOKIE, HEADER_DATE, HEADER_ETAG, HEADER_EXPECT, HEADER_EXPIRES, HEADER_FROM, HEADER_HOST, - HEADER_IF_MATCH, HEADER_IF_MODIFIED_SINCE, HEADER_IF_NONE_MATCH, HEADER_IF_RANGE, - HEADER_IF_UNMODIFIED_SINCE, HEADER_LAST_MODIFIED, HEADER_LOCATION, HEADER_MAX_FORWARDS, - HEADER_PROXY_AUTHENTICATE, HEADER_PROXY_AUTHORIZATION, HEADER_RANGE, HEADER_REFERRER, - HEADER_RETRY_AFTER, HEADER_SERVER, HEADER_SET_COOKIE, HEADER_SET_COOKIE2, HEADER_USER_AGENT, - HEADER_VARY, HEADER_VIA, HEADER_WARNING, HEADER_WWW_AUTHENTICATE))); - - /** - * Set of unsupported headers that will be covered in future versions. - */ - private static final Set UNSUPPORTED_STANDARD_HEADERS = Collections - .unmodifiableSet(new CaseInsensitiveHashSet(Arrays.asList(HEADER_PRAGMA, HEADER_TRAILER, - HEADER_TRANSFER_ENCODING, HEADER_TRANSFER_EXTENSION, HEADER_UPGRADE))); - - /** - * Adds the entity headers based on the {@link Representation} to the - * {@link Series}. - * - * @param entity The source entity {@link Representation}. - * @param headers The target headers {@link Series}. - */ - public static void addEntityHeaders(Representation entity, Series

headers) { - if (entity == null || !entity.isAvailable()) { - addHeader(HEADER_CONTENT_LENGTH, "0", headers); - } else if (entity.getAvailableSize() != UNKNOWN_SIZE) { - addHeader(HEADER_CONTENT_LENGTH, Long.toString(entity.getAvailableSize()), headers); - } - - if (entity != null) { - addHeader(HEADER_CONTENT_ENCODING, EncodingWriter.write(entity.getEncodings()), headers); - addHeader(HEADER_CONTENT_LANGUAGE, LanguageWriter.write(entity.getLanguages()), headers); - - if (entity.getLocationRef() != null) { - addHeader(HEADER_CONTENT_LOCATION, entity.getLocationRef().getTargetRef().toString(), headers); - } - - if (entity.getDigest() != null && ALGORITHM_MD5.equals(entity.getDigest().getAlgorithm())) { - addHeader(HEADER_CONTENT_MD5, - new String(java.util.Base64.getEncoder().encode(entity.getDigest().getValue())), headers); - } - - if (entity.getRange() != null) { - Range range = entity.getRange(); - if (isBytesRange(range)) { - addHeader(HEADER_CONTENT_RANGE, RangeWriter.write(range, entity.getSize()), headers); - } else { - addHeader(HEADER_CONTENT_RANGE, RangeWriter.write(range, range.getInstanceSize()), headers); - } - } - - if (entity.getMediaType() != null) { - addHeader(HEADER_CONTENT_TYPE, ContentType.writeHeader(entity), headers); - } - - if (entity.getExpirationDate() != null) { - addHeader(HEADER_EXPIRES, DateWriter.write(entity.getExpirationDate()), headers); - } - - if (entity.getModificationDate() != null) { - addHeader(HEADER_LAST_MODIFIED, DateWriter.write(entity.getModificationDate()), headers); - } - - if (entity.getTag() != null) { - addHeader(HEADER_ETAG, TagWriter.write(entity.getTag()), headers); - } - - if (entity.getDisposition() != null && !TYPE_NONE.equals(entity.getDisposition().getType())) { - addHeader(HEADER_CONTENT_DISPOSITION, DispositionWriter.write(entity.getDisposition()), headers); - } - } - } - - /** - * Adds extension headers if they are non-standard headers. - * - * @param existingHeaders The headers to update. - * @param additionalHeaders The headers to add. - */ - public static void addExtensionHeaders(Series
existingHeaders, Series
additionalHeaders) { - if (additionalHeaders != null) { - for (Header param : additionalHeaders) { - if (STANDARD_HEADERS.contains(param.getName())) { - // Standard headers that can't be overridden - Context.getCurrentLogger().warning("Addition of the standard header \"" + param.getName() - + "\" is not allowed. Please use the equivalent property in the Restlet API."); - } else if (UNSUPPORTED_STANDARD_HEADERS.contains(param.getName())) { - Context.getCurrentLogger().warning("Addition of the standard header \"" + param.getName() - + "\" is discouraged as a future version of the Restlet API will directly support it."); - existingHeaders.add(param); - } else { - existingHeaders.add(param); - } - } - } - } - - /** - * Adds the general headers from the {@link Message} to the {@link Series}. - * - * @param message The source {@link Message}. - * @param headers The target headers {@link Series}. - */ - public static void addGeneralHeaders(Message message, Series
headers) { - addHeader(HEADER_CACHE_CONTROL, CacheDirectiveWriter.write(message.getCacheDirectives()), headers); - - if (message.getDate() == null) { - message.setDate(new Date()); - } - - addHeader(HEADER_DATE, DateWriter.write(message.getDate()), headers); - - addHeader(HEADER_VIA, RecipientInfoWriter.write(message.getRecipientsInfo()), headers); - - addHeader(HEADER_WARNING, WarningWriter.write(message.getWarnings()), headers); - } - - /** - * Adds a header to the given list. Checks for exceptions and logs them. - * - * @param headerName The header name. - * @param headerValue The header value. - * @param headers The headers list. - */ - public static void addHeader(String headerName, String headerValue, Series
headers) { - if (headerName != null && !isNullOrEmpty(headerValue)) { - try { - headers.add(headerName, headerValue); - } catch (Throwable t) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to format the " + headerName + " header", t); - } - } - } - - /** - * Adds the entity headers based on the {@link Representation} to the - * {@link Series} when a 304 (Not Modified) status is returned. - * - * @param entity The source entity {@link Representation}. - * @param headers The target headers {@link Series}. - */ - public static void addNotModifiedEntityHeaders(Representation entity, Series
headers) { - if (entity != null) { - if (entity.getTag() != null) { - addHeader(HEADER_ETAG, TagWriter.write(entity.getTag()), headers); - } - - if (entity.getLocationRef() != null) { - addHeader(HEADER_CONTENT_LOCATION, entity.getLocationRef().getTargetRef().toString(), headers); - } - } - } - - /** - * Adds the headers based on the {@link Request} to the given {@link Series} . - * - * @param request The {@link Request} to copy the headers from. - * @param headers The {@link Series} to copy the headers to. - */ - public static void addRequestHeaders(Request request, Series
headers) { - ClientInfo clientInfo = request.getClientInfo(); - - if (!clientInfo.getAcceptedMediaTypes().isEmpty()) { - addHeader(HEADER_ACCEPT, PreferenceWriter.write(clientInfo.getAcceptedMediaTypes()), headers); - } else { - addHeader(HEADER_ACCEPT, MediaType.ALL.getName(), headers); - } - - if (!clientInfo.getAcceptedCharacterSets().isEmpty()) { - addHeader(HEADER_ACCEPT_CHARSET, PreferenceWriter.write(clientInfo.getAcceptedCharacterSets()), headers); - } - - if (!clientInfo.getAcceptedEncodings().isEmpty()) { - addHeader(HEADER_ACCEPT_ENCODING, PreferenceWriter.write(clientInfo.getAcceptedEncodings()), headers); - } - - if (!clientInfo.getAcceptedLanguages().isEmpty()) { - addHeader(HEADER_ACCEPT_LANGUAGE, PreferenceWriter.write(clientInfo.getAcceptedLanguages()), headers); - } - - if (!clientInfo.getAcceptedPatches().isEmpty()) { - addHeader(HEADER_ACCEPT_PATCH, PreferenceWriter.write(clientInfo.getAcceptedPatches()), headers); - } - - if (!clientInfo.getExpectations().isEmpty()) { - addHeader(HEADER_EXPECT, ExpectationWriter.write(clientInfo.getExpectations()), headers); - } - - if (clientInfo.getFrom() != null) { - addHeader(HEADER_FROM, request.getClientInfo().getFrom(), headers); - } - - // Manually add the host name and port when it is potentially - // different from the one specified in the target resource reference. - Reference hostRef = (request.getResourceRef().getBaseRef() != null) ? request.getResourceRef().getBaseRef() - : request.getResourceRef(); - - if (hostRef.getHostDomain() != null) { - String host = hostRef.getHostDomain(); - int hostRefPortValue = hostRef.getHostPort(); - - if ((hostRefPortValue != -1) && (hostRefPortValue != request.getProtocol().getDefaultPort())) { - host = host + ':' + hostRefPortValue; - } - - addHeader(HEADER_HOST, host, headers); - } - - Conditions conditions = request.getConditions(); - addHeader(HEADER_IF_MATCH, TagWriter.write(conditions.getMatch()), headers); - addHeader(HEADER_IF_NONE_MATCH, TagWriter.write(conditions.getNoneMatch()), headers); - - if (conditions.getModifiedSince() != null) { - addHeader(HEADER_IF_MODIFIED_SINCE, DateWriter.write(conditions.getModifiedSince()), headers); - } - - if (conditions.getRangeTag() != null && conditions.getRangeDate() != null) { - Context.getCurrentLogger().log(WARNING, - "Unable to format the HTTP If-Range header due to the presence of both entity tag and modification date."); - } else if (conditions.getRangeTag() != null) { - addHeader(HEADER_IF_RANGE, TagWriter.write(conditions.getRangeTag()), headers); - } else if (conditions.getRangeDate() != null) { - addHeader(HEADER_IF_RANGE, DateWriter.write(conditions.getRangeDate()), headers); - } - - if (conditions.getUnmodifiedSince() != null) { - addHeader(HEADER_IF_UNMODIFIED_SINCE, DateWriter.write(conditions.getUnmodifiedSince()), headers); - } - - if (request.getMaxForwards() > -1) { - addHeader(HEADER_MAX_FORWARDS, Integer.toString(request.getMaxForwards()), headers); - } - - if (!request.getRanges().isEmpty()) { - addHeader(HEADER_RANGE, RangeWriter.write(request.getRanges()), headers); - } - - if (request.getReferrerRef() != null) { - addHeader(HEADER_REFERRER, request.getReferrerRef().toString(), headers); - } - - if (request.getClientInfo().getAgent() != null) { - addHeader(HEADER_USER_AGENT, request.getClientInfo().getAgent(), headers); - } else { - addHeader(HEADER_USER_AGENT, Engine.VERSION_HEADER, headers); - } - - if (clientInfo.getExpectations().size() > 0) { - addHeader(HEADER_ACCEPT_ENCODING, PreferenceWriter.write(clientInfo.getAcceptedEncodings()), headers); - } - - // CORS headers - - if (request.getAccessControlRequestHeaders() != null) { - addHeader(HEADER_ACCESS_CONTROL_REQUEST_HEADERS, - StringWriter.write(request.getAccessControlRequestHeaders()), headers); - } - - if (request.getAccessControlRequestMethod() != null) { - addHeader(HEADER_ACCESS_CONTROL_REQUEST_METHOD, request.getAccessControlRequestMethod().getName(), headers); - } - - // ---------------------------------- - // 3) Add supported extension headers - // ---------------------------------- - - if (!request.getCookies().isEmpty()) { - addHeader(HEADER_COOKIE, CookieWriter.write(request.getCookies()), headers); - } - - // ------------------------------------- - // 4) Add user-defined extension headers - // ------------------------------------- - - Series
additionalHeaders = request.getHeaders(); - addExtensionHeaders(headers, additionalHeaders); - - // --------------------------------------- - // 5) Add authorization headers at the end - // --------------------------------------- - - // Add the security headers. NOTE: This must stay at the end because - // the AWS challenge scheme requires access to all HTTP headers - ChallengeResponse challengeResponse = request.getChallengeResponse(); - - if (challengeResponse != null) { - String authHeader = org.restlet.engine.security.AuthenticatorUtils.formatResponse(challengeResponse, - request, headers); - - if (authHeader != null) { - addHeader(HEADER_AUTHORIZATION, authHeader, headers); - } - } - - ChallengeResponse proxyChallengeResponse = request.getProxyChallengeResponse(); - - if (proxyChallengeResponse != null) { - String authHeader = org.restlet.engine.security.AuthenticatorUtils.formatResponse(proxyChallengeResponse, - request, headers); - - if (authHeader != null) { - addHeader(HEADER_PROXY_AUTHORIZATION, authHeader, headers); - } - } - } - - /** - * Adds the headers based on the {@link Response} to the given {@link Series}. - * - * @param response The {@link Response} to copy the headers from. - * @param headers The {@link Series} to copy the headers to. - */ - public static void addResponseHeaders(Response response, Series
headers) { - if (response.getServerInfo().isAcceptingRanges()) { - addHeader(HEADER_ACCEPT_RANGES, RANGE_BYTES_UNIT, headers); - } - - if (response.getAge() > 0) { - addHeader(HEADER_AGE, Integer.toString(response.getAge()), headers); - } - - if (CLIENT_ERROR_METHOD_NOT_ALLOWED.equals(response.getStatus()) - || OPTIONS.equals(response.getRequest().getMethod())) { - addHeader(HEADER_ALLOW, MethodWriter.write(response.getAllowedMethods()), headers); - } - - if (response.getLocationRef() != null) { - // The location header must contain an absolute URI. - addHeader(HEADER_LOCATION, response.getLocationRef().getTargetRef().toString(), headers); - } - - if (response.getProxyChallengeRequests() != null) { - for (ChallengeRequest challengeRequest : response.getProxyChallengeRequests()) { - addHeader(HEADER_PROXY_AUTHENTICATE, org.restlet.engine.security.AuthenticatorUtils - .formatRequest(challengeRequest, response, headers), headers); - } - } - - if (response.getRetryAfter() != null) { - addHeader(HEADER_RETRY_AFTER, DateWriter.write(response.getRetryAfter()), headers); - } - - if ((response.getServerInfo() != null) && (response.getServerInfo().getAgent() != null)) { - addHeader(HEADER_SERVER, response.getServerInfo().getAgent(), headers); - } else { - addHeader(HEADER_SERVER, Engine.VERSION_HEADER, headers); - } - - // Send the Vary header only to none-MSIE user agents as MSIE seems - // to support partially and badly this header (cf issue 261). - if (!((response.getRequest().getClientInfo().getAgent() != null) - && response.getRequest().getClientInfo().getAgent().contains("MSIE"))) { - // Add the Vary header if content negotiation was used - addHeader(HEADER_VARY, DimensionWriter.write(response.getDimensions()), headers); - } - - // Set the security data - if (response.getChallengeRequests() != null) { - for (ChallengeRequest challengeRequest : response.getChallengeRequests()) { - addHeader(HEADER_WWW_AUTHENTICATE, org.restlet.engine.security.AuthenticatorUtils - .formatRequest(challengeRequest, response, headers), headers); - } - } - - // CORS headers - - if (response.getAccessControlAllowCredentials() != null) { - addHeader(HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS, response.getAccessControlAllowCredentials().toString(), - headers); - } - - if (response.getAccessControlAllowHeaders() != null) { - addHeader(HEADER_ACCESS_CONTROL_ALLOW_HEADERS, StringWriter.write(response.getAccessControlAllowHeaders()), - headers); - } - if (response.getAccessControlAllowOrigin() != null) { - addHeader(HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, response.getAccessControlAllowOrigin(), headers); - } - - if (response.getAccessControlAllowMethods() != null) { - addHeader(HEADER_ACCESS_CONTROL_ALLOW_METHODS, MethodWriter.write(response.getAccessControlAllowMethods()), - headers); - } - if (response.getAccessControlExposeHeaders() != null) { - addHeader(HeaderConstants.HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, - StringWriter.write(response.getAccessControlExposeHeaders()), headers); - } - if (response.getAccessControlMaxAge() > 0) { - addHeader(HeaderConstants.HEADER_ACCESS_CONTROL_MAX_AGE, - Integer.toString(response.getAccessControlMaxAge()), headers); - } - - // ---------------------------------- - // 3) Add supported extension headers - // ---------------------------------- - - // Add the Authentication-Info header - if (response.getAuthenticationInfo() != null) { - addHeader(HEADER_AUTHENTICATION_INFO, org.restlet.engine.security.AuthenticatorUtils - .formatAuthenticationInfo(response.getAuthenticationInfo()), headers); - } - - // Cookies settings should be written in a single header, but Web - // browsers does not seem to support it. - for (CookieSetting cookieSetting : response.getCookieSettings()) { - addHeader(HEADER_SET_COOKIE, CookieSettingWriter.write(cookieSetting), headers); - } - - // ------------------------------------- - // 4) Add user-defined extension headers - // ------------------------------------- - - Series
additionalHeaders = response.getHeaders(); - addExtensionHeaders(headers, additionalHeaders); - } - - /** - * Remove the headers that are mapped to the framework's API from the given - * message's list of headers. - * - * @param message The message to update. - */ - public static void keepExtensionHeadersOnly(Message message) { - Series
headers = message.getHeaders(); - Series
extensionHeaders = new Series
(Header.class); - for (Header header : headers) { - if (!STANDARD_HEADERS.contains(header.getName())) { - extensionHeaders.add(header); - } - } - message.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, extensionHeaders); - } - - /** - * Copies extension headers into a request or a response. - * - * @param headers The headers to copy. - * @param message The message to update. - */ - public static void copyExtensionHeaders(Series
headers, Message message) { - if (headers != null) { - Series
extensionHeaders = message.getHeaders(); - for (Header header : headers) { - if (!STANDARD_HEADERS.contains(header.getName())) { - extensionHeaders.add(header); - } - } - } - } - - /** - * Copies headers into a response. - * - * @param headers The headers to copy. - * @param response The response to update. - */ - public static void copyResponseTransportHeaders(Series
headers, Response response) { - if (headers != null) { - for (Header header : headers) { - if (HEADER_LOCATION.equalsIgnoreCase(header.getName())) { - response.setLocationRef(header.getValue()); - } else if (HEADER_AGE.equalsIgnoreCase(header.getName())) { - try { - response.setAge(Integer.parseInt(header.getValue())); - } catch (NumberFormatException nfe) { - Context.getCurrentLogger().log(Level.WARNING, - "Error during Age header parsing. Header: " + header.getValue(), nfe); - } - } else if (HEADER_DATE.equalsIgnoreCase(header.getName())) { - Date date = DateUtils.parse(header.getValue()); - - if (date == null) { - date = new Date(); - } - - response.setDate(date); - } else if (HEADER_RETRY_AFTER.equalsIgnoreCase(header.getName())) { - Date retryAfter = DateUtils.parse(header.getValue()); - - if (retryAfter == null) { - // The date might be expressed as a number of seconds - try { - int retryAfterSecs = (int) Double.parseDouble(header.getValue()); - java.util.Calendar calendar = java.util.Calendar.getInstance(); - calendar.add(java.util.Calendar.SECOND, retryAfterSecs); - retryAfter = calendar.getTime(); - } catch (NumberFormatException nfe) { - Context.getCurrentLogger().log(Level.WARNING, - "Error during Retry-After header parsing. Header: " + header.getValue(), nfe); - } - } - - response.setRetryAfter(retryAfter); - } else if (HEADER_SET_COOKIE.equalsIgnoreCase(header.getName()) - || HEADER_SET_COOKIE2.equalsIgnoreCase(header.getName())) { - try { - CookieSettingReader cr = new CookieSettingReader(header.getValue()); - response.getCookieSettings().add(cr.readValue()); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, - "Error during cookie setting parsing. Header: " + header.getValue(), e); - } - } else if (HEADER_WWW_AUTHENTICATE.equalsIgnoreCase(header.getName())) { - List crs = org.restlet.engine.security.AuthenticatorUtils.parseRequest(response, - header.getValue(), headers); - response.getChallengeRequests().addAll(crs); - } else if (HEADER_PROXY_AUTHENTICATE.equalsIgnoreCase(header.getName())) { - List crs = org.restlet.engine.security.AuthenticatorUtils.parseRequest(response, - header.getValue(), headers); - response.getProxyChallengeRequests().addAll(crs); - } else if (HEADER_AUTHENTICATION_INFO.equalsIgnoreCase(header.getName())) { - AuthenticationInfo authenticationInfo = org.restlet.engine.security.AuthenticatorUtils - .parseAuthenticationInfo(header.getValue()); - response.setAuthenticationInfo(authenticationInfo); - } else if (HEADER_SERVER.equalsIgnoreCase(header.getName())) { - response.getServerInfo().setAgent(header.getValue()); - } else if (HEADER_ALLOW.equalsIgnoreCase(header.getName())) { - MethodReader.addValues(header, response.getAllowedMethods()); - } else if (HEADER_VARY.equalsIgnoreCase(header.getName())) { - DimensionReader.addValues(header, response.getDimensions()); - } else if (HEADER_VIA.equalsIgnoreCase(header.getName())) { - RecipientInfoReader.addValues(header, response.getRecipientsInfo()); - } else if (HEADER_WARNING.equalsIgnoreCase(header.getName())) { - WarningReader.addValues(header, response.getWarnings()); - } else if (HEADER_CACHE_CONTROL.equalsIgnoreCase(header.getName())) { - CacheDirectiveReader.addValues(header, response.getCacheDirectives()); - } else if (HEADER_ACCEPT_RANGES.equalsIgnoreCase(header.getName())) { - TokenReader tr = new TokenReader(header.getValue()); - response.getServerInfo().setAcceptingRanges(tr.readValues().contains(RANGE_BYTES_UNIT)); - } else if (HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS.equalsIgnoreCase(header.getName())) { - response.setAccessControlAllowCredentials(parseBoolean(header.getValue())); - StringReader.addValues(header, response.getAccessControlAllowHeaders()); - } else if (HEADER_ACCESS_CONTROL_ALLOW_ORIGIN.equalsIgnoreCase(header.getName())) { - response.setAccessControlAllowOrigin(header.getValue()); - } else if (HEADER_ACCESS_CONTROL_ALLOW_METHODS.equalsIgnoreCase(header.getName())) { - MethodReader.addValues(header, response.getAccessControlAllowMethods()); - } else if (HEADER_ACCESS_CONTROL_MAX_AGE.equalsIgnoreCase(header.getName())) { - response.setAccessControlMaxAge(Integer.parseInt(header.getValue())); - } - } - } - } - - /** - * Extracts entity headers and updates a given representation or create an empty - * one when at least one entity header is present. - * - * @param headers The headers to copy. - * @param representation The representation to update or null. - * @return a representation updated with the given entity headers. - * @throws NumberFormatException - * @see HeaderUtils#copyResponseTransportHeaders(Series, Response) - */ - public static Representation extractEntityHeaders(Iterable
headers, Representation representation) - throws NumberFormatException { - Representation result = (representation == null) ? new EmptyRepresentation() : representation; - boolean entityHeaderFound = false; - - if (headers != null) { - for (Header header : headers) { - if (HEADER_CONTENT_TYPE.equalsIgnoreCase(header.getName())) { - ContentType contentType = new ContentType(header.getValue()); - result.setMediaType(contentType.getMediaType()); - - if ((result.getCharacterSet() == null) || (contentType.getCharacterSet() != null)) { - result.setCharacterSet(contentType.getCharacterSet()); - } - - entityHeaderFound = true; - } else if (HEADER_CONTENT_LENGTH.equalsIgnoreCase(header.getName())) { - entityHeaderFound = true; - } else if (HEADER_EXPIRES.equalsIgnoreCase(header.getName())) { - result.setExpirationDate(HeaderReader.readDate(header.getValue(), false)); - entityHeaderFound = true; - } else if (HEADER_CONTENT_ENCODING.equalsIgnoreCase(header.getName())) { - new EncodingReader(header.getValue()).addValues(result.getEncodings()); - entityHeaderFound = true; - } else if (HEADER_CONTENT_LANGUAGE.equalsIgnoreCase(header.getName())) { - new LanguageReader(header.getValue()).addValues(result.getLanguages()); - entityHeaderFound = true; - } else if (HEADER_LAST_MODIFIED.equalsIgnoreCase(header.getName())) { - result.setModificationDate(HeaderReader.readDate(header.getValue(), false)); - entityHeaderFound = true; - } else if (HEADER_ETAG.equalsIgnoreCase(header.getName())) { - result.setTag(Tag.parse(header.getValue())); - entityHeaderFound = true; - } else if (HEADER_CONTENT_LOCATION.equalsIgnoreCase(header.getName())) { - result.setLocationRef(header.getValue()); - entityHeaderFound = true; - } else if (HEADER_CONTENT_DISPOSITION.equalsIgnoreCase(header.getName())) { - try { - result.setDisposition(new DispositionReader(header.getValue()).readValue()); - entityHeaderFound = true; - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.WARNING, - "Error during Content-Disposition header parsing. Header: " + header.getValue(), ioe); - } - } else if (HEADER_CONTENT_RANGE.equalsIgnoreCase(header.getName())) { - org.restlet.engine.header.RangeReader.update(header.getValue(), result); - entityHeaderFound = true; - } else if (HEADER_CONTENT_MD5.equalsIgnoreCase(header.getName())) { - // Since an MD5 hash is 128 bits long, its base64 encoding - // is 22 bytes if unpadded, or 24 bytes if padded. If the - // header value is unpadded, append two base64 padding - // characters ("==") before passing the value to - // Base64.decode(), which requires its input argument's - // length to be a multiple of four. - String base64hash = header.getValue(); - if (base64hash.length() == 22) { - base64hash += "=="; - } - result.setDigest(new org.restlet.data.Digest(org.restlet.data.Digest.ALGORITHM_MD5, - java.util.Base64.getDecoder().decode(base64hash))); - entityHeaderFound = true; - } - } - } - - // If no representation was initially expected and no entity header - // is found, then do not return any representation - if ((representation == null) && !entityHeaderFound) { - result = null; - } - - return result; - } - - /** - * Returns the content length of the request entity if know, - * {@link Representation#UNKNOWN_SIZE} otherwise. - * - * @return The request content length. - */ - public static long getContentLength(Series
headers) { - long contentLength = UNKNOWN_SIZE; - - if (headers != null) { - // Extract the content length header - for (Header header : headers) { - if (HEADER_CONTENT_LENGTH.equalsIgnoreCase(header.getName())) { - try { - contentLength = Long.parseLong(header.getValue()); - } catch (NumberFormatException e) { - contentLength = UNKNOWN_SIZE; - } - } - } - } - - return contentLength; - } - - /** - * Indicates if the given character is alphabetical (a-z or A-Z). - * - * @param character The character to test. - * @return True if the given character is alphabetical (a-z or A-Z). - */ - public static boolean isAlpha(int character) { - return isUpperCase(character) || isLowerCase(character); - } - - /** - * Indicates if the given character is in ASCII range. - * - * @param character The character to test. - * @return True if the given character is in ASCII range. - */ - public static boolean isAsciiChar(int character) { - return (character >= 0) && (character <= 127); - } - - /** - * Indicates if the given character is a carriage return. - * - * @param character The character to test. - * @return True if the given character is a carriage return. - */ - public static boolean isCarriageReturn(int character) { - return (character == 13); - } - - /** - * Indicates if the entity is chunked. - * - * @return True if the entity is chunked. - */ - public static boolean isChunkedEncoding(Series
headers) { - boolean result = false; - - if (headers != null) { - final String header = headers.getFirstValue(HeaderConstants.HEADER_TRANSFER_ENCODING, true); - result = "chunked".equalsIgnoreCase(header); - } - - return result; - } - - /** - * Indicates if the given character is a comma, the character used as header - * value separator. - * - * @param character The character to test. - * @return True if the given character is a comma. - */ - public static boolean isComma(int character) { - return (character == ','); - } - - /** - * Indicates if the given character is a comment text. It means - * {@link #isText(int)} returns true and the character is not '(' or ')'. - * - * @param character The character to test. - * @return True if the given character is a quoted text. - */ - public static boolean isCommentText(int character) { - return isText(character) && (character != '(') && (character != ')'); - } - - /** - * Indicates if the connection must be closed. - * - * @param headers The headers to test. - * @return True if the connection must be closed. - */ - public static boolean isConnectionClose(Series
headers) { - boolean result = false; - - if (headers != null) { - String header = headers.getFirstValue(HeaderConstants.HEADER_CONNECTION, true); - result = "close".equalsIgnoreCase(header); - } - - return result; - } - - /** - * Indicates if the given character is a control character. - * - * @param character The character to test. - * @return True if the given character is a control character. - */ - public static boolean isControlChar(int character) { - return ((character >= 0) && (character <= 31)) || (character == 127); - } - - /** - * Indicates if the given character is a digit (0-9). - * - * @param character The character to test. - * @return True if the given character is a digit (0-9). - */ - public static boolean isDigit(int character) { - return (character >= '0') && (character <= '9'); - } - - /** - * Indicates if the given character is a double quote. - * - * @param character The character to test. - * @return True if the given character is a double quote. - */ - public static boolean isDoubleQuote(int character) { - return (character == 34); - } - - /** - * Indicates if the given character is an horizontal tab. - * - * @param character The character to test. - * @return True if the given character is an horizontal tab. - */ - public static boolean isHorizontalTab(int character) { - return (character == 9); - } - - /** - * Indicates if the given character is in ISO Latin 1 (8859-1) range. Note that - * this range is a superset of ASCII and a subrange of Unicode (UTF-8). - * - * @param character The character to test. - * @return True if the given character is in ISO Latin 1 range. - */ - public static boolean isLatin1Char(int character) { - return (character >= 0) && (character <= 255); - } - - /** - * Indicates if the given character is a value separator. - * - * @param character The character to test. - * @return True if the given character is a value separator. - */ - public static boolean isLinearWhiteSpace(int character) { - return (isCarriageReturn(character) || isSpace(character) || isLineFeed(character) - || HeaderUtils.isHorizontalTab(character)); - } - - /** - * Indicates if the given character is a line feed. - * - * @param character The character to test. - * @return True if the given character is a line feed. - */ - public static boolean isLineFeed(int character) { - return (character == 10); - } - - /** - * Indicates if the given character is lower case (a-z). - * - * @param character The character to test. - * @return True if the given character is lower case (a-z). - */ - public static boolean isLowerCase(int character) { - return (character >= 'a') && (character <= 'z'); - } - - /** - * Indicates if the given character marks the start of a quoted pair. - * - * @param character The character to test. - * @return True if the given character marks the start of a quoted pair. - */ - public static boolean isQuoteCharacter(int character) { - return (character == '\\'); - } - - /** - * Indicates if the given character is a quoted text. It means - * {@link #isText(int)} returns true and {@link #isDoubleQuote(int)} returns - * false. - * - * @param character The character to test. - * @return True if the given character is a quoted text. - */ - public static boolean isQuotedText(int character) { - return isText(character) && !isDoubleQuote(character); - } - - /** - * Indicates if the given character is a semicolon, the character used as header - * parameter separator. - * - * @param character The character to test. - * @return True if the given character is a semicolon. - */ - public static boolean isSemiColon(int character) { - return (character == ';'); - } - - /** - * Indicates if the given character is a separator. - * - * @param character The character to test. - * @return True if the given character is a separator. - */ - public static boolean isSeparator(int character) { - switch (character) { - case '(': - case ')': - case '<': - case '>': - case '@': - case ',': - case ';': - case ':': - case '\\': - case '"': - case '/': - case '[': - case ']': - case '?': - case '=': - case '{': - case '}': - case ' ': - case '\t': - return true; - - default: - return false; - } - } - - /** - * Indicates if the given character is a space. - * - * @param character The character to test. - * @return True if the given character is a space. - */ - public static boolean isSpace(int character) { - return (character == 32); - } - - /** - * Indicates if the given character is textual (ISO Latin 1 and not a control - * character). - * - * @param character The character to test. - * @return True if the given character is textual. - */ - public static boolean isText(int character) { - return isLatin1Char(character) && !isControlChar(character); - } - - /** - * Indicates if the token is valid.
- * Only contains valid token characters. - * - * @param token The token to check - * @return True if the token is valid. - */ - public static boolean isToken(CharSequence token) { - for (int i = 0; i < token.length(); i++) { - if (!isTokenChar(token.charAt(i))) { - return false; - } - } - - return true; - } - - /** - * Indicates if the given character is a token character (text and not a - * separator). - * - * @param character The character to test. - * @return True if the given character is a token character (text and not a - * separator). - */ - public static boolean isTokenChar(int character) { - return isAsciiChar(character) && !isSeparator(character); - } - - /** - * Indicates if the given character is upper case (A-Z). - * - * @param character The character to test. - * @return True if the given character is upper case (A-Z). - */ - public static boolean isUpperCase(int character) { - return (character >= 'A') && (character <= 'Z'); - } - - /** - * Writes a new line. - * - * @param os The output stream. - * @throws IOException - */ - public static void writeCRLF(OutputStream os) throws IOException { - os.write(13); // CR - os.write(10); // LF - } - - /** - * Writes a header line. - * - * @param header The header to write. - * @param os The output stream. - * @throws IOException - */ - public static void writeHeaderLine(Header header, OutputStream os) throws IOException { - os.write(StringUtils.getAsciiBytes(header.getName())); - os.write(':'); - os.write(' '); - - if (header.getValue() != null) { - os.write(StringUtils.getLatin1Bytes(header.getValue())); - } - - os.write(13); // CR - os.write(10); // LF - } - - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e. it isn't instantiable and extensible. - */ - private HeaderUtils() { - } + /** Standard set of headers which cannot be modified. */ + private static final Set STANDARD_HEADERS = + Collections.unmodifiableSet( + new CaseInsensitiveHashSet( + Arrays.asList( + HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS, + HEADER_ACCESS_CONTROL_ALLOW_HEADERS, + HEADER_ACCESS_CONTROL_ALLOW_METHODS, + HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, + HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, + HEADER_ACCESS_CONTROL_MAX_AGE, + HEADER_ACCESS_CONTROL_REQUEST_HEADERS, + HEADER_ACCESS_CONTROL_REQUEST_METHOD, + HEADER_ACCEPT, + HEADER_ACCEPT_CHARSET, + HEADER_ACCEPT_ENCODING, + HEADER_ACCEPT_LANGUAGE, + HEADER_ACCEPT_PATCH, + HEADER_ACCEPT_RANGES, + HEADER_AGE, + HEADER_ALLOW, + HEADER_AUTHENTICATION_INFO, + HEADER_AUTHORIZATION, + HEADER_CACHE_CONTROL, + HEADER_CONNECTION, + HEADER_CONTENT_DISPOSITION, + HEADER_CONTENT_ENCODING, + HEADER_CONTENT_LANGUAGE, + HEADER_CONTENT_LENGTH, + HEADER_CONTENT_LOCATION, + HEADER_CONTENT_MD5, + HEADER_CONTENT_RANGE, + HEADER_CONTENT_TYPE, + HEADER_COOKIE, + HEADER_DATE, + HEADER_ETAG, + HEADER_EXPECT, + HEADER_EXPIRES, + HEADER_FROM, + HEADER_HOST, + HEADER_IF_MATCH, + HEADER_IF_MODIFIED_SINCE, + HEADER_IF_NONE_MATCH, + HEADER_IF_RANGE, + HEADER_IF_UNMODIFIED_SINCE, + HEADER_LAST_MODIFIED, + HEADER_LOCATION, + HEADER_MAX_FORWARDS, + HEADER_PROXY_AUTHENTICATE, + HEADER_PROXY_AUTHORIZATION, + HEADER_RANGE, + HEADER_REFERRER, + HEADER_RETRY_AFTER, + HEADER_SERVER, + HEADER_SET_COOKIE, + HEADER_SET_COOKIE2, + HEADER_USER_AGENT, + HEADER_VARY, + HEADER_VIA, + HEADER_WARNING, + HEADER_WWW_AUTHENTICATE))); + + /** Set of unsupported headers that will be covered in future versions. */ + private static final Set UNSUPPORTED_STANDARD_HEADERS = + Collections.unmodifiableSet( + new CaseInsensitiveHashSet( + Arrays.asList( + HEADER_PRAGMA, + HEADER_TRAILER, + HEADER_TRANSFER_ENCODING, + HEADER_TRANSFER_EXTENSION, + HEADER_UPGRADE))); + + /** + * Adds the entity headers based on the {@link Representation} to the {@link Series}. + * + * @param entity The source entity {@link Representation}. + * @param headers The target headers {@link Series}. + */ + public static void addEntityHeaders(Representation entity, Series
headers) { + if (entity == null || !entity.isAvailable()) { + addHeader(HEADER_CONTENT_LENGTH, "0", headers); + } else if (entity.getAvailableSize() != UNKNOWN_SIZE) { + addHeader(HEADER_CONTENT_LENGTH, Long.toString(entity.getAvailableSize()), headers); + } + + if (entity != null) { + addHeader( + HEADER_CONTENT_ENCODING, EncodingWriter.write(entity.getEncodings()), headers); + addHeader( + HEADER_CONTENT_LANGUAGE, LanguageWriter.write(entity.getLanguages()), headers); + + if (entity.getLocationRef() != null) { + addHeader( + HEADER_CONTENT_LOCATION, + entity.getLocationRef().getTargetRef().toString(), + headers); + } + + if (entity.getDigest() != null + && ALGORITHM_MD5.equals(entity.getDigest().getAlgorithm())) { + addHeader( + HEADER_CONTENT_MD5, + new String( + java.util.Base64.getEncoder() + .encode(entity.getDigest().getValue())), + headers); + } + + if (entity.getRange() != null) { + Range range = entity.getRange(); + if (isBytesRange(range)) { + addHeader( + HEADER_CONTENT_RANGE, + RangeWriter.write(range, entity.getSize()), + headers); + } else { + addHeader( + HEADER_CONTENT_RANGE, + RangeWriter.write(range, range.getInstanceSize()), + headers); + } + } + + if (entity.getMediaType() != null) { + addHeader(HEADER_CONTENT_TYPE, ContentType.writeHeader(entity), headers); + } + + if (entity.getExpirationDate() != null) { + addHeader(HEADER_EXPIRES, DateWriter.write(entity.getExpirationDate()), headers); + } + + if (entity.getModificationDate() != null) { + addHeader( + HEADER_LAST_MODIFIED, + DateWriter.write(entity.getModificationDate()), + headers); + } + + if (entity.getTag() != null) { + addHeader(HEADER_ETAG, TagWriter.write(entity.getTag()), headers); + } + + if (entity.getDisposition() != null + && !TYPE_NONE.equals(entity.getDisposition().getType())) { + addHeader( + HEADER_CONTENT_DISPOSITION, + DispositionWriter.write(entity.getDisposition()), + headers); + } + } + } + + /** + * Adds extension headers if they are non-standard headers. + * + * @param existingHeaders The headers to update. + * @param additionalHeaders The headers to add. + */ + public static void addExtensionHeaders( + Series
existingHeaders, Series
additionalHeaders) { + if (additionalHeaders != null) { + for (Header param : additionalHeaders) { + if (STANDARD_HEADERS.contains(param.getName())) { + // Standard headers that can't be overridden + Context.getCurrentLogger() + .warning( + "Addition of the standard header \"" + + param.getName() + + "\" is not allowed. Please use the equivalent property in the Restlet API."); + } else if (UNSUPPORTED_STANDARD_HEADERS.contains(param.getName())) { + Context.getCurrentLogger() + .warning( + "Addition of the standard header \"" + + param.getName() + + "\" is discouraged as a future version of the Restlet API will directly support it."); + existingHeaders.add(param); + } else { + existingHeaders.add(param); + } + } + } + } + + /** + * Adds the general headers from the {@link Message} to the {@link Series}. + * + * @param message The source {@link Message}. + * @param headers The target headers {@link Series}. + */ + public static void addGeneralHeaders(Message message, Series
headers) { + addHeader( + HEADER_CACHE_CONTROL, + CacheDirectiveWriter.write(message.getCacheDirectives()), + headers); + + if (message.getDate() == null) { + message.setDate(new Date()); + } + + addHeader(HEADER_DATE, DateWriter.write(message.getDate()), headers); + + addHeader(HEADER_VIA, RecipientInfoWriter.write(message.getRecipientsInfo()), headers); + + addHeader(HEADER_WARNING, WarningWriter.write(message.getWarnings()), headers); + } + + /** + * Adds a header to the given list. Checks for exceptions and logs them. + * + * @param headerName The header name. + * @param headerValue The header value. + * @param headers The headers list. + */ + public static void addHeader(String headerName, String headerValue, Series
headers) { + if (headerName != null && !isNullOrEmpty(headerValue)) { + try { + headers.add(headerName, headerValue); + } catch (Exception exception) { + Context.getCurrentLogger() + .log( + Level.WARNING, + exception, + () -> "Unable to format the " + headerName + " header"); + } + } + } + + /** + * Adds the entity headers based on the {@link Representation} to the {@link Series} when a 304 + * (Not Modified) status is returned. + * + * @param entity The source entity {@link Representation}. + * @param headers The target headers {@link Series}. + */ + public static void addNotModifiedEntityHeaders(Representation entity, Series
headers) { + if (entity != null) { + if (entity.getTag() != null) { + addHeader(HEADER_ETAG, TagWriter.write(entity.getTag()), headers); + } + + if (entity.getLocationRef() != null) { + addHeader( + HEADER_CONTENT_LOCATION, + entity.getLocationRef().getTargetRef().toString(), + headers); + } + } + } + + /** + * Adds the headers based on the {@link Request} to the given {@link Series} . + * + * @param request The {@link Request} to copy the headers from. + * @param headers The {@link Series} to copy the headers to. + */ + public static void addRequestHeaders(Request request, Series
headers) { + ClientInfo clientInfo = request.getClientInfo(); + + if (!clientInfo.getAcceptedMediaTypes().isEmpty()) { + addHeader( + HEADER_ACCEPT, + PreferenceWriter.write(clientInfo.getAcceptedMediaTypes()), + headers); + } else { + addHeader(HEADER_ACCEPT, MediaType.ALL.getName(), headers); + } + + if (!clientInfo.getAcceptedCharacterSets().isEmpty()) { + addHeader( + HEADER_ACCEPT_CHARSET, + PreferenceWriter.write(clientInfo.getAcceptedCharacterSets()), + headers); + } + + if (!clientInfo.getAcceptedEncodings().isEmpty()) { + addHeader( + HEADER_ACCEPT_ENCODING, + PreferenceWriter.write(clientInfo.getAcceptedEncodings()), + headers); + } + + if (!clientInfo.getAcceptedLanguages().isEmpty()) { + addHeader( + HEADER_ACCEPT_LANGUAGE, + PreferenceWriter.write(clientInfo.getAcceptedLanguages()), + headers); + } + + if (!clientInfo.getAcceptedPatches().isEmpty()) { + addHeader( + HEADER_ACCEPT_PATCH, + PreferenceWriter.write(clientInfo.getAcceptedPatches()), + headers); + } + + if (!clientInfo.getExpectations().isEmpty()) { + addHeader( + HEADER_EXPECT, ExpectationWriter.write(clientInfo.getExpectations()), headers); + } + + if (clientInfo.getFrom() != null) { + addHeader(HEADER_FROM, request.getClientInfo().getFrom(), headers); + } + + // Manually add the host name and port when it is potentially + // different from the one specified in the target resource reference. + Reference hostRef = + (request.getResourceRef().getBaseRef() != null) + ? request.getResourceRef().getBaseRef() + : request.getResourceRef(); + + if (hostRef.getHostDomain() != null) { + String host = hostRef.getHostDomain(); + int hostRefPortValue = hostRef.getHostPort(); + + if ((hostRefPortValue != -1) + && (hostRefPortValue != request.getProtocol().getDefaultPort())) { + host = host + ':' + hostRefPortValue; + } + + addHeader(HEADER_HOST, host, headers); + } + + Conditions conditions = request.getConditions(); + addHeader(HEADER_IF_MATCH, TagWriter.write(conditions.getMatch()), headers); + addHeader(HEADER_IF_NONE_MATCH, TagWriter.write(conditions.getNoneMatch()), headers); + + if (conditions.getModifiedSince() != null) { + addHeader( + HEADER_IF_MODIFIED_SINCE, + DateWriter.write(conditions.getModifiedSince()), + headers); + } + + if (conditions.getRangeTag() != null && conditions.getRangeDate() != null) { + Context.getCurrentLogger() + .log( + WARNING, + "Unable to format the HTTP If-Range header due to the presence of both entity tag and modification date."); + } else if (conditions.getRangeTag() != null) { + addHeader(HEADER_IF_RANGE, TagWriter.write(conditions.getRangeTag()), headers); + } else if (conditions.getRangeDate() != null) { + addHeader(HEADER_IF_RANGE, DateWriter.write(conditions.getRangeDate()), headers); + } + + if (conditions.getUnmodifiedSince() != null) { + addHeader( + HEADER_IF_UNMODIFIED_SINCE, + DateWriter.write(conditions.getUnmodifiedSince()), + headers); + } + + if (request.getMaxForwards() > -1) { + addHeader(HEADER_MAX_FORWARDS, Integer.toString(request.getMaxForwards()), headers); + } + + if (!request.getRanges().isEmpty()) { + addHeader(HEADER_RANGE, RangeWriter.write(request.getRanges()), headers); + } + + if (request.getReferrerRef() != null) { + addHeader(HEADER_REFERRER, request.getReferrerRef().toString(), headers); + } + + if (request.getClientInfo().getAgent() != null) { + addHeader(HEADER_USER_AGENT, request.getClientInfo().getAgent(), headers); + } else { + addHeader(HEADER_USER_AGENT, Engine.VERSION_HEADER, headers); + } + + if (!clientInfo.getExpectations().isEmpty()) { + addHeader( + HEADER_ACCEPT_ENCODING, + PreferenceWriter.write(clientInfo.getAcceptedEncodings()), + headers); + } + + // CORS headers + + if (request.getAccessControlRequestHeaders() != null) { + addHeader( + HEADER_ACCESS_CONTROL_REQUEST_HEADERS, + StringWriter.write(request.getAccessControlRequestHeaders()), + headers); + } + + if (request.getAccessControlRequestMethod() != null) { + addHeader( + HEADER_ACCESS_CONTROL_REQUEST_METHOD, + request.getAccessControlRequestMethod().getName(), + headers); + } + + // ---------------------------------- + // 3) Add supported extension headers + // ---------------------------------- + + if (!request.getCookies().isEmpty()) { + addHeader(HEADER_COOKIE, CookieWriter.write(request.getCookies()), headers); + } + + // ------------------------------------- + // 4) Add user-defined extension headers + // ------------------------------------- + + Series
additionalHeaders = request.getHeaders(); + addExtensionHeaders(headers, additionalHeaders); + + // --------------------------------------- + // 5) Add authorization headers at the end + // --------------------------------------- + + // Add the security headers. NOTE: This must stay at the end because + // the AWS challenge scheme requires access to all HTTP headers + ChallengeResponse challengeResponse = request.getChallengeResponse(); + + if (challengeResponse != null) { + String authHeader = + AuthenticatorUtils.formatResponse(challengeResponse, request, headers); + + if (authHeader != null) { + addHeader(HEADER_AUTHORIZATION, authHeader, headers); + } + } + + ChallengeResponse proxyChallengeResponse = request.getProxyChallengeResponse(); + + if (proxyChallengeResponse != null) { + String authHeader = + AuthenticatorUtils.formatResponse(proxyChallengeResponse, request, headers); + + if (authHeader != null) { + addHeader(HEADER_PROXY_AUTHORIZATION, authHeader, headers); + } + } + } + + /** + * Adds the headers based on the {@link Response} to the given {@link Series}. + * + * @param response The {@link Response} to copy the headers from. + * @param headers The {@link Series} to copy the headers to. + */ + public static void addResponseHeaders(Response response, Series
headers) { + if (response.getServerInfo().isAcceptingRanges()) { + addHeader(HEADER_ACCEPT_RANGES, RANGE_BYTES_UNIT, headers); + } + + if (response.getAge() > 0) { + addHeader(HEADER_AGE, Integer.toString(response.getAge()), headers); + } + + if (CLIENT_ERROR_METHOD_NOT_ALLOWED.equals(response.getStatus()) + || OPTIONS.equals(response.getRequest().getMethod())) { + addHeader(HEADER_ALLOW, MethodWriter.write(response.getAllowedMethods()), headers); + } + + if (response.getLocationRef() != null) { + // The location header must contain an absolute URI. + addHeader( + HEADER_LOCATION, response.getLocationRef().getTargetRef().toString(), headers); + } + + if (response.getProxyChallengeRequests() != null) { + for (ChallengeRequest challengeRequest : response.getProxyChallengeRequests()) { + addHeader( + HEADER_PROXY_AUTHENTICATE, + AuthenticatorUtils.formatRequest(challengeRequest, response, headers), + headers); + } + } + + if (response.getRetryAfter() != null) { + addHeader(HEADER_RETRY_AFTER, DateWriter.write(response.getRetryAfter()), headers); + } + + if ((response.getServerInfo() != null) && (response.getServerInfo().getAgent() != null)) { + addHeader(HEADER_SERVER, response.getServerInfo().getAgent(), headers); + } else { + addHeader(HEADER_SERVER, Engine.VERSION_HEADER, headers); + } + + // Send the "Vary" header only to none-MSIE user agents as MSIE seems + // to partially and badly support this header (cf. issue 261). + if (!((response.getRequest().getClientInfo().getAgent() != null) + && response.getRequest().getClientInfo().getAgent().contains("MSIE"))) { + // Add the "Vary" header if content negotiation was used + addHeader(HEADER_VARY, DimensionWriter.write(response.getDimensions()), headers); + } + + // Set the security data + if (response.getChallengeRequests() != null) { + for (ChallengeRequest challengeRequest : response.getChallengeRequests()) { + addHeader( + HEADER_WWW_AUTHENTICATE, + AuthenticatorUtils.formatRequest(challengeRequest, response, headers), + headers); + } + } + + // CORS headers + + if (response.getAccessControlAllowCredentials() != null) { + addHeader( + HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS, + response.getAccessControlAllowCredentials().toString(), + headers); + } + + if (response.getAccessControlAllowHeaders() != null) { + addHeader( + HEADER_ACCESS_CONTROL_ALLOW_HEADERS, + StringWriter.write(response.getAccessControlAllowHeaders()), + headers); + } + if (response.getAccessControlAllowOrigin() != null) { + addHeader( + HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, + response.getAccessControlAllowOrigin(), + headers); + } + + if (response.getAccessControlAllowMethods() != null) { + addHeader( + HEADER_ACCESS_CONTROL_ALLOW_METHODS, + MethodWriter.write(response.getAccessControlAllowMethods()), + headers); + } + if (response.getAccessControlExposeHeaders() != null) { + addHeader( + HeaderConstants.HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, + StringWriter.write(response.getAccessControlExposeHeaders()), + headers); + } + if (response.getAccessControlMaxAge() > 0) { + addHeader( + HeaderConstants.HEADER_ACCESS_CONTROL_MAX_AGE, + Integer.toString(response.getAccessControlMaxAge()), + headers); + } + + // ---------------------------------- + // 3) Add supported extension headers + // ---------------------------------- + + // Add the Authentication-Info header + if (response.getAuthenticationInfo() != null) { + addHeader( + HEADER_AUTHENTICATION_INFO, + AuthenticatorUtils.formatAuthenticationInfo(response.getAuthenticationInfo()), + headers); + } + + // Cookies settings should be written in a single header, but Web + // browsers do not seem to support it. + for (CookieSetting cookieSetting : response.getCookieSettings()) { + addHeader(HEADER_SET_COOKIE, CookieSettingWriter.write(cookieSetting), headers); + } + + // ------------------------------------- + // 4) Add user-defined extension headers + // ------------------------------------- + + Series
additionalHeaders = response.getHeaders(); + addExtensionHeaders(headers, additionalHeaders); + } + + /** + * Remove the headers that are mapped to the framework's API from the given message's list of + * headers. + * + * @param message The message to update. + */ + public static void keepExtensionHeadersOnly(Message message) { + Series
headers = message.getHeaders(); + Series
extensionHeaders = new Series<>(Header.class); + for (Header header : headers) { + if (!STANDARD_HEADERS.contains(header.getName())) { + extensionHeaders.add(header); + } + } + message.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, extensionHeaders); + } + + /** + * Copies extension headers into a request or a response. + * + * @param headers The headers to copy. + * @param message The message to update. + */ + public static void copyExtensionHeaders(Series
headers, Message message) { + if (headers != null) { + Series
extensionHeaders = message.getHeaders(); + for (Header header : headers) { + if (!STANDARD_HEADERS.contains(header.getName())) { + extensionHeaders.add(header); + } + } + } + } + + /** + * Copies headers into a response. + * + * @param headers The headers to copy. + * @param response The response to update. + */ + public static void copyResponseTransportHeaders(Series
headers, Response response) { + if (headers != null) { + for (Header header : headers) { + if (HEADER_LOCATION.equalsIgnoreCase(header.getName())) { + response.setLocationRef(header.getValue()); + } else if (HEADER_AGE.equalsIgnoreCase(header.getName())) { + try { + response.setAge(Integer.parseInt(header.getValue())); + } catch (NumberFormatException nfe) { + Context.getCurrentLogger() + .log( + Level.WARNING, + nfe, + () -> + "Error during Age header parsing. Header: " + + header.getValue()); + } + } else if (HEADER_DATE.equalsIgnoreCase(header.getName())) { + Date date = DateUtils.parse(header.getValue()); + + if (date == null) { + date = new Date(); + } + + response.setDate(date); + } else if (HEADER_RETRY_AFTER.equalsIgnoreCase(header.getName())) { + Date retryAfter = DateUtils.parse(header.getValue()); + + if (retryAfter == null) { + // The date might be expressed as a number of seconds + try { + int retryAfterSecs = (int) Double.parseDouble(header.getValue()); + java.util.Calendar calendar = java.util.Calendar.getInstance(); + calendar.add(java.util.Calendar.SECOND, retryAfterSecs); + retryAfter = calendar.getTime(); + } catch (NumberFormatException nfe) { + Context.getCurrentLogger() + .log( + Level.WARNING, + nfe, + () -> + "Error during Retry-After header parsing. Header: " + + header.getValue()); + } + } + + response.setRetryAfter(retryAfter); + } else if (HEADER_SET_COOKIE.equalsIgnoreCase(header.getName()) + || HEADER_SET_COOKIE2.equalsIgnoreCase(header.getName())) { + try { + CookieSettingReader cr = new CookieSettingReader(header.getValue()); + response.getCookieSettings().add(cr.readValue()); + } catch (Exception e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + e, + () -> + "Error during cookie setting parsing. Header: " + + header.getValue()); + } + } else if (HEADER_WWW_AUTHENTICATE.equalsIgnoreCase(header.getName())) { + List crs = + AuthenticatorUtils.parseRequest(response, header.getValue(), headers); + response.getChallengeRequests().addAll(crs); + } else if (HEADER_PROXY_AUTHENTICATE.equalsIgnoreCase(header.getName())) { + List crs = + AuthenticatorUtils.parseRequest(response, header.getValue(), headers); + response.getProxyChallengeRequests().addAll(crs); + } else if (HEADER_AUTHENTICATION_INFO.equalsIgnoreCase(header.getName())) { + AuthenticationInfo authenticationInfo = + AuthenticatorUtils.parseAuthenticationInfo(header.getValue()); + response.setAuthenticationInfo(authenticationInfo); + } else if (HEADER_SERVER.equalsIgnoreCase(header.getName())) { + response.getServerInfo().setAgent(header.getValue()); + } else if (HEADER_ALLOW.equalsIgnoreCase(header.getName())) { + MethodReader.addValues(header, response.getAllowedMethods()); + } else if (HEADER_VARY.equalsIgnoreCase(header.getName())) { + DimensionReader.addValues(header, response.getDimensions()); + } else if (HEADER_VIA.equalsIgnoreCase(header.getName())) { + RecipientInfoReader.addValues(header, response.getRecipientsInfo()); + } else if (HEADER_WARNING.equalsIgnoreCase(header.getName())) { + WarningReader.addValues(header, response.getWarnings()); + } else if (HEADER_CACHE_CONTROL.equalsIgnoreCase(header.getName())) { + CacheDirectiveReader.addValues(header, response.getCacheDirectives()); + } else if (HEADER_ACCEPT_RANGES.equalsIgnoreCase(header.getName())) { + TokenReader tr = new TokenReader(header.getValue()); + response.getServerInfo() + .setAcceptingRanges(tr.readValues().contains(RANGE_BYTES_UNIT)); + } else if (HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS.equalsIgnoreCase( + header.getName())) { + response.setAccessControlAllowCredentials(parseBoolean(header.getValue())); + StringReader.addValues(header, response.getAccessControlAllowHeaders()); + } else if (HEADER_ACCESS_CONTROL_ALLOW_ORIGIN.equalsIgnoreCase(header.getName())) { + response.setAccessControlAllowOrigin(header.getValue()); + } else if (HEADER_ACCESS_CONTROL_ALLOW_METHODS.equalsIgnoreCase(header.getName())) { + MethodReader.addValues(header, response.getAccessControlAllowMethods()); + } else if (HEADER_ACCESS_CONTROL_MAX_AGE.equalsIgnoreCase(header.getName())) { + response.setAccessControlMaxAge(Integer.parseInt(header.getValue())); + } + } + } + } + + /** + * Extracts entity headers and updates a given representation or create an empty one when at + * least one entity header is present. + * + * @param headers The headers to copy. + * @param representation The representation to update or null. + * @return a representation updated with the given entity headers. + * @throws NumberFormatException + * @see HeaderUtils#copyResponseTransportHeaders(Series, Response) + */ + public static Representation extractEntityHeaders( + Iterable
headers, Representation representation) throws NumberFormatException { + Representation result = + (representation == null) ? new EmptyRepresentation() : representation; + boolean entityHeaderFound = false; + + if (headers != null) { + for (Header header : headers) { + if (HEADER_CONTENT_TYPE.equalsIgnoreCase(header.getName())) { + ContentType contentType = new ContentType(header.getValue()); + result.setMediaType(contentType.getMediaType()); + + if ((result.getCharacterSet() == null) + || (contentType.getCharacterSet() != null)) { + result.setCharacterSet(contentType.getCharacterSet()); + } + + entityHeaderFound = true; + } else if (HEADER_CONTENT_LENGTH.equalsIgnoreCase(header.getName())) { + entityHeaderFound = true; + } else if (HEADER_EXPIRES.equalsIgnoreCase(header.getName())) { + result.setExpirationDate(HeaderReader.readDate(header.getValue(), false)); + entityHeaderFound = true; + } else if (HEADER_CONTENT_ENCODING.equalsIgnoreCase(header.getName())) { + new EncodingReader(header.getValue()).addValues(result.getEncodings()); + entityHeaderFound = true; + } else if (HEADER_CONTENT_LANGUAGE.equalsIgnoreCase(header.getName())) { + new LanguageReader(header.getValue()).addValues(result.getLanguages()); + entityHeaderFound = true; + } else if (HEADER_LAST_MODIFIED.equalsIgnoreCase(header.getName())) { + result.setModificationDate(HeaderReader.readDate(header.getValue(), false)); + entityHeaderFound = true; + } else if (HEADER_ETAG.equalsIgnoreCase(header.getName())) { + result.setTag(Tag.parse(header.getValue())); + entityHeaderFound = true; + } else if (HEADER_CONTENT_LOCATION.equalsIgnoreCase(header.getName())) { + result.setLocationRef(header.getValue()); + entityHeaderFound = true; + } else if (HEADER_CONTENT_DISPOSITION.equalsIgnoreCase(header.getName())) { + try { + result.setDisposition(new DispositionReader(header.getValue()).readValue()); + entityHeaderFound = true; + } catch (IOException ioe) { + Context.getCurrentLogger() + .log( + Level.WARNING, + ioe, + () -> + "Error during Content-Disposition header parsing. Header: " + + header.getValue()); + } + } else if (HEADER_CONTENT_RANGE.equalsIgnoreCase(header.getName())) { + org.restlet.engine.header.RangeReader.update(header.getValue(), result); + entityHeaderFound = true; + } else if (HEADER_CONTENT_MD5.equalsIgnoreCase(header.getName())) { + // Since an MD5 hash is 128 bits long, its base64 encoding + // is 22 bytes if unpadded, or 24 bytes if padded. If the + // header value is unpadded, append two base64 padding + // characters ("==") before passing the value to + // Base64.decode(), which requires its input argument's + // length to be a multiple of four. + String base64hash = header.getValue(); + if (base64hash.length() == 22) { + base64hash += "=="; + } + result.setDigest( + new org.restlet.data.Digest( + org.restlet.data.Digest.ALGORITHM_MD5, + java.util.Base64.getDecoder().decode(base64hash))); + entityHeaderFound = true; + } + } + } + + // If no representation was initially expected and no entity header + // is found, then do not return any representation + if ((representation == null) && !entityHeaderFound) { + result = null; + } + + return result; + } + + /** + * Returns the content length of the request entity if known, {@link + * Representation#UNKNOWN_SIZE} otherwise. + * + * @return The request content length. + */ + public static long getContentLength(Series
headers) { + long contentLength = UNKNOWN_SIZE; + + if (headers != null) { + // Extract the content length header + for (Header header : headers) { + if (HEADER_CONTENT_LENGTH.equalsIgnoreCase(header.getName())) { + try { + contentLength = Long.parseLong(header.getValue()); + } catch (NumberFormatException e) { + contentLength = UNKNOWN_SIZE; + } + } + } + } + + return contentLength; + } + + /** + * Indicates if the given character is alphabetical (a-z or A-Z). + * + * @param character The character to test. + * @return True if the given character is alphabetical (a-z or A-Z). + */ + public static boolean isAlpha(int character) { + return isUpperCase(character) || isLowerCase(character); + } + + /** + * Indicates if the given character is in the ASCII range. + * + * @param character The character to test. + * @return True if the given character is in the ASCII range. + */ + public static boolean isAsciiChar(int character) { + return (character >= 0) && (character <= 127); + } + + /** + * Indicates if the given character is a carriage return. + * + * @param character The character to test. + * @return True if the given character is a carriage return. + */ + public static boolean isCarriageReturn(int character) { + return (character == 13); + } + + /** + * Indicates if the entity is chunked. + * + * @return True if the entity is chunked. + */ + public static boolean isChunkedEncoding(Series
headers) { + boolean result = false; + + if (headers != null) { + final String header = + headers.getFirstValue(HeaderConstants.HEADER_TRANSFER_ENCODING, true); + result = "chunked".equalsIgnoreCase(header); + } + + return result; + } + + /** + * Indicates if the given character is a comma, the character used as header value separator. + * + * @param character The character to test. + * @return True if the given character is a comma. + */ + public static boolean isComma(int character) { + return (character == ','); + } + + /** + * Indicates if the given character is a comment text. It means {@link #isText(int)} returns + * true and the character is not '(' or ')'. + * + * @param character The character to test. + * @return True if the given character is a quoted text. + */ + public static boolean isCommentText(int character) { + return isText(character) && (character != '(') && (character != ')'); + } + + /** + * Indicates if the connection must be closed. + * + * @param headers The headers to test. + * @return True if the connection must be closed. + */ + public static boolean isConnectionClose(Series
headers) { + boolean result = false; + + if (headers != null) { + String header = headers.getFirstValue(HeaderConstants.HEADER_CONNECTION, true); + result = "close".equalsIgnoreCase(header); + } + + return result; + } + + /** + * Indicates if the given character is a control character. + * + * @param character The character to test. + * @return True if the given character is a control character. + */ + public static boolean isControlChar(int character) { + return ((character >= 0) && (character <= 31)) || (character == 127); + } + + /** + * Indicates if the given character is a digit (0-9). + * + * @param character The character to test. + * @return True if the given character is a digit (0-9). + */ + public static boolean isDigit(int character) { + return (character >= '0') && (character <= '9'); + } + + /** + * Indicates if the given character is a double quote. + * + * @param character The character to test. + * @return True if the given character is a double quote. + */ + public static boolean isDoubleQuote(int character) { + return (character == 34); + } + + /** + * Indicates if the given character is a horizontal tab. + * + * @param character The character to test. + * @return True if the given character is a horizontal tab. + */ + public static boolean isHorizontalTab(int character) { + return (character == 9); + } + + /** + * Indicates if the given character is in ISO Latin 1 (8859-1) range. Note that this range is a + * superset of ASCII and a subrange of Unicode (UTF-8). + * + * @param character The character to test. + * @return True if the given character is in ISO Latin 1 range. + */ + public static boolean isLatin1Char(int character) { + return (character >= 0) && (character <= 255); + } + + /** + * Indicates if the given character is a value separator. + * + * @param character The character to test. + * @return True if the given character is a value separator. + */ + public static boolean isLinearWhiteSpace(int character) { + return (isCarriageReturn(character) + || isSpace(character) + || isLineFeed(character) + || HeaderUtils.isHorizontalTab(character)); + } + + /** + * Indicates if the given character is a line feed. + * + * @param character The character to test. + * @return True if the given character is a line feed. + */ + public static boolean isLineFeed(int character) { + return (character == 10); + } + + /** + * Indicates if the given character is lower case (a-z). + * + * @param character The character to test. + * @return True if the given character is lower case (a-z). + */ + public static boolean isLowerCase(int character) { + return (character >= 'a') && (character <= 'z'); + } + + /** + * Indicates if the given character marks the start of a quoted pair. + * + * @param character The character to test. + * @return True if the given character marks the start of a quoted pair. + */ + public static boolean isQuoteCharacter(int character) { + return (character == '\\'); + } + + /** + * Indicates if the given character is a quoted text. It means {@link #isText(int)} returns true + * and {@link #isDoubleQuote(int)} returns false. + * + * @param character The character to test. + * @return True if the given character is a quoted text. + */ + public static boolean isQuotedText(int character) { + return isText(character) && !isDoubleQuote(character); + } + + /** + * Indicates if the given character is a semicolon, the character used as header parameter + * separator. + * + * @param character The character to test. + * @return True if the given character is a semicolon. + */ + public static boolean isSemiColon(int character) { + return (character == ';'); + } + + /** + * Indicates if the given character is a separator. + * + * @param character The character to test. + * @return True if the given character is a separator. + */ + public static boolean isSeparator(int character) { + return switch (character) { + case '(', + ')', + '<', + '>', + '@', + ',', + ';', + ':', + '\\', + '"', + '/', + '[', + ']', + '?', + '=', + '{', + '}', + ' ', + '\t' -> + true; + default -> false; + }; + } + + /** + * Indicates if the given character is a space. + * + * @param character The character to test. + * @return True if the given character is a space. + */ + public static boolean isSpace(int character) { + return (character == 32); + } + + /** + * Indicates if the given character is textual (ISO Latin 1 and not a control character). + * + * @param character The character to test. + * @return True if the given character is textual. + */ + public static boolean isText(int character) { + return isLatin1Char(character) && !isControlChar(character); + } + + /** + * Indicates if the token is valid.
+ * Only contains valid token characters. + * + * @param token The token to check + * @return True if the token is valid. + */ + public static boolean isToken(CharSequence token) { + for (int i = 0; i < token.length(); i++) { + if (!isTokenChar(token.charAt(i))) { + return false; + } + } + + return true; + } + + /** + * Indicates if the given character is a token character (text and not a separator). + * + * @param character The character to test. + * @return True if the given character is a token character (text and not a separator). + */ + public static boolean isTokenChar(int character) { + return isAsciiChar(character) && !isSeparator(character); + } + + /** + * Indicates if the given character is upper case (A-Z). + * + * @param character The character to test. + * @return True if the given character is upper case (A-Z). + */ + public static boolean isUpperCase(int character) { + return (character >= 'A') && (character <= 'Z'); + } + + /** + * Writes a new line. + * + * @param os The output stream. + * @throws IOException + */ + public static void writeCRLF(OutputStream os) throws IOException { + os.write(13); // CR + os.write(10); // LF + } + + /** + * Writes a header line. + * + * @param header The header to write. + * @param os The output stream. + * @throws IOException + */ + public static void writeHeaderLine(Header header, OutputStream os) throws IOException { + os.write(StringUtils.getAsciiBytes(header.getName())); + os.write(':'); + os.write(' '); + + if (header.getValue() != null) { + os.write(StringUtils.getLatin1Bytes(header.getValue())); + } + + os.write(13); // CR + os.write(10); // LF + } + + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private HeaderUtils() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/HeaderWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/HeaderWriter.java index c46c82332e..c868cb4c6f 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/HeaderWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/HeaderWriter.java @@ -1,292 +1,288 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.io.StringWriter; +import java.util.Collection; import org.restlet.data.CharacterSet; import org.restlet.data.Encoding; import org.restlet.data.Reference; import org.restlet.util.NamedValue; -import java.io.StringWriter; -import java.util.Collection; - /** * HTTP-style header writer. - * + * * @param The value type. * @author Jerome Louvel */ public abstract class HeaderWriter extends StringWriter { - @Override - public HeaderWriter append(char c) { - super.append(c); - return this; - } - - /** - * Appends an array of characters. - * - * @param cs The array of characters. - * @return This writer. - */ - public HeaderWriter append(char[] cs) { - if (cs != null) { - for (char c : cs) { - append(c); - } - } - - return this; - } - - @Override - public HeaderWriter append(CharSequence csq) { - super.append(csq); - return this; - } - - /** - * Appends a collection of values. - * - * @param values The collection of values to append. - * @return This writer. - */ - public HeaderWriter append(Collection values) { - if ((values != null) && !values.isEmpty()) { - boolean first = true; - - for (V value : values) { - if (canWrite(value)) { - if (first) { - first = false; - } else { - appendValueSeparator(); - } - - append(value); - } - } - } - - return this; - } - - /** - * Appends an integer. - * - * @param i The value to append. - * @return This writer. - */ - public HeaderWriter append(int i) { - return append(Integer.toString(i)); - } - - /** - * Appends a long. - * - * @param l The value to append. - * @return This writer. - */ - public HeaderWriter append(long l) { - return append(Long.toString(l)); - } - - /** - * Appends a value. - * - * @param value The value. - * @return This writer. - */ - public abstract HeaderWriter append(V value); - - /** - * Appends a string as an HTTP comment, surrounded by parenthesis and with - * quoted pairs if needed. - * - * @param content The comment to write. - * @return This writer. - */ - public HeaderWriter appendComment(String content) { - append('('); - char c; - - for (int i = 0; i < content.length(); i++) { - c = content.charAt(i); - - if (HeaderUtils.isCommentText(c)) { - append(c); - } else { - appendQuotedPair(c); - } - } - - return append(')'); - } - - /** - * Formats and appends a parameter as an extension. If the value is not a token, - * then it is quoted. - * - * @param extension The parameter to format as an extension. - * @return This writer. - */ - public HeaderWriter appendExtension(NamedValue extension) { - if (extension != null) { - return appendExtension(extension.getName(), extension.getValue()); - } else { - return this; - } - } - - /** - * Appends an extension. If the value is not a token, then it is quoted. - * - * @param name The extension name. - * @param value The extension value. - * @return This writer. - */ - public HeaderWriter appendExtension(String name, String value) { - if ((name != null) && (!name.isEmpty())) { - append(name); - - if ((value != null) && (!value.isEmpty())) { - append("="); - - if (HeaderUtils.isToken(value)) { - append(value); - } else { - appendQuotedString(value); - } - } - } - - return this; - } - - /** - * Appends a semicolon as a parameter separator. - * - * @return This writer. - */ - public HeaderWriter appendParameterSeparator() { - return append(";"); - } - - /** - * Appends a product description. - * - * @param name The product name token. - * @param version The product version token. - * @return This writer. - */ - public HeaderWriter appendProduct(String name, String version) { - appendToken(name); - - if (version != null) { - append('/').appendToken(version); - } - - return this; - } - - /** - * Appends a quoted character, prefixing it with a backslash. - * - * @param character The character to quote. - * @return This writer. - */ - public HeaderWriter appendQuotedPair(char character) { - return append('\\').append(character); - } - - /** - * Appends a quoted string. - * - * @param content The string to quote and write. - * @return This writer. - */ - public HeaderWriter appendQuotedString(String content) { - if ((content != null) && (!content.isEmpty())) { - append('"'); - char c; - - for (int i = 0; i < content.length(); i++) { - c = content.charAt(i); - - if (HeaderUtils.isQuotedText(c)) { - append(c); - } else { - appendQuotedPair(c); - } - } - - append('"'); - } - - return this; - } - - /** - * Appends a space character. - * - * @return This writer. - */ - public HeaderWriter appendSpace() { - return append(' '); - } - - /** - * Appends a token. - * - * @param token The token to write. - * @return This writer. - */ - public HeaderWriter appendToken(String token) { - if (HeaderUtils.isToken(token)) { - return append(token); - } else { - throw new IllegalArgumentException("Unexpected character found in token: " + token); - } - } - - /** - * Formats and appends a source string as an URI encoded string. - * - * @param source The source string to format. - * @param characterSet The supported character encoding. - * @return This writer. - */ - public HeaderWriter appendUriEncoded(CharSequence source, CharacterSet characterSet) { - return append(Reference.encode(source.toString(), characterSet)); - } - - /** - * Appends a comma as a value separator. - * - * @return This writer. - */ - public HeaderWriter appendValueSeparator() { - return append(", "); - } - - /** - * Indicates if the value can be written to the header. Useful to prevent the - * writing of {@link Encoding#IDENTITY} constants for example. By default it - * returns true for non null values. - * - * @param value The value to add. - * @return True if the value can be added. - */ - protected boolean canWrite(V value) { - return (value != null); - } - + @Override + public HeaderWriter append(char c) { + super.append(c); + return this; + } + + /** + * Appends an array of characters. + * + * @param cs The array of characters. + * @return This writer. + */ + public HeaderWriter append(char[] cs) { + if (cs != null) { + for (char c : cs) { + append(c); + } + } + + return this; + } + + @Override + public HeaderWriter append(CharSequence csq) { + super.append(csq); + return this; + } + + /** + * Appends a collection of values. + * + * @param values The collection of values to append. + * @return This writer. + */ + public HeaderWriter append(Collection values) { + if ((values != null) && !values.isEmpty()) { + boolean first = true; + + for (V value : values) { + if (canWrite(value)) { + if (first) { + first = false; + } else { + appendValueSeparator(); + } + + append(value); + } + } + } + + return this; + } + + /** + * Appends an integer. + * + * @param i The value to append. + * @return This writer. + */ + public HeaderWriter append(int i) { + return append(Integer.toString(i)); + } + + /** + * Appends a long value. + * + * @param l The value to append. + * @return This writer. + */ + public HeaderWriter append(long l) { + return append(Long.toString(l)); + } + + /** + * Appends a value. + * + * @param value The value. + * @return This writer. + */ + public abstract HeaderWriter append(V value); + + /** + * Appends a string as an HTTP comment, surrounded by parenthesis and with quoted pairs if + * needed. + * + * @param content The comment to write. + * @return This writer. + */ + public HeaderWriter appendComment(String content) { + append('('); + char c; + + for (int i = 0; i < content.length(); i++) { + c = content.charAt(i); + + if (HeaderUtils.isCommentText(c)) { + append(c); + } else { + appendQuotedPair(c); + } + } + + return append(')'); + } + + /** + * Formats and appends a parameter as an extension. If the value is not a token, then it is + * quoted. + * + * @param extension The parameter to format as an extension. + * @return This writer. + */ + public HeaderWriter appendExtension(NamedValue extension) { + if (extension != null) { + return appendExtension(extension.getName(), extension.getValue()); + } else { + return this; + } + } + + /** + * Appends an extension. If the value is not a token, then it is quoted. + * + * @param name The extension name. + * @param value The extension value. + * @return This writer. + */ + public HeaderWriter appendExtension(String name, String value) { + if ((name != null) && (!name.isEmpty())) { + append(name); + + if ((value != null) && (!value.isEmpty())) { + append("="); + + if (HeaderUtils.isToken(value)) { + append(value); + } else { + appendQuotedString(value); + } + } + } + + return this; + } + + /** + * Appends a semicolon as a parameter separator. + * + * @return This writer. + */ + public HeaderWriter appendParameterSeparator() { + return append(";"); + } + + /** + * Appends a product description. + * + * @param name The product name token. + * @param version The product version token. + * @return This writer. + */ + public HeaderWriter appendProduct(String name, String version) { + appendToken(name); + + if (version != null) { + append('/').appendToken(version); + } + + return this; + } + + /** + * Appends a quoted character, prefixing it with a backslash. + * + * @param character The character to quote. + * @return This writer. + */ + public HeaderWriter appendQuotedPair(char character) { + return append('\\').append(character); + } + + /** + * Appends a quoted string. + * + * @param content The string to quote and write. + * @return This writer. + */ + public HeaderWriter appendQuotedString(String content) { + if ((content != null) && (!content.isEmpty())) { + append('"'); + char c; + + for (int i = 0; i < content.length(); i++) { + c = content.charAt(i); + + if (HeaderUtils.isQuotedText(c)) { + append(c); + } else { + appendQuotedPair(c); + } + } + + append('"'); + } + + return this; + } + + /** + * Appends a space character. + * + * @return This writer. + */ + public HeaderWriter appendSpace() { + return append(' '); + } + + /** + * Appends a token. + * + * @param token The token to write. + * @return This writer. + */ + public HeaderWriter appendToken(String token) { + if (HeaderUtils.isToken(token)) { + return append(token); + } else { + throw new IllegalArgumentException("Unexpected character found in token: " + token); + } + } + + /** + * Formats and appends a source string as a URI encoded string. + * + * @param source The source string to format. + * @param characterSet The supported character encoding. + * @return This writer. + */ + public HeaderWriter appendUriEncoded(CharSequence source, CharacterSet characterSet) { + return append(Reference.encode(source.toString(), characterSet)); + } + + /** + * Appends a comma as a value separator. + * + * @return This writer. + */ + public HeaderWriter appendValueSeparator() { + return append(", "); + } + + /** + * Indicates if the value can be written to the header. Useful to prevent the writing of {@link + * Encoding#IDENTITY} constants for example. By default, it returns true for non-null values. + * + * @param value The value to add. + * @return True if the value can be added. + */ + protected boolean canWrite(V value) { + return (value != null); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/LanguageReader.java b/org.restlet/src/main/java/org/restlet/engine/header/LanguageReader.java index e0f2e3010a..0ca2c843a7 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/LanguageReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/LanguageReader.java @@ -1,37 +1,34 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Language; - import java.io.IOException; +import org.restlet.data.Language; /** * Language header reader. - * + * * @author Jerome Louvel */ public class LanguageReader extends HeaderReader { - /** - * Constructor. - * - * @param header The header to read. - */ - public LanguageReader(String header) { - super(header); - } - - @Override - public Language readValue() throws IOException { - return Language.valueOf(readRawValue()); - } + /** + * Constructor. + * + * @param header The header to read. + */ + public LanguageReader(String header) { + super(header); + } + @Override + public Language readValue() throws IOException { + return Language.valueOf(readRawValue()); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/LanguageWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/LanguageWriter.java index e52c90b86e..6a1790373b 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/LanguageWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/LanguageWriter.java @@ -1,33 +1,30 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Language; - import java.util.List; +import org.restlet.data.Language; /** * Language header writer. - * + * * @author Jerome Louvel */ public class LanguageWriter extends MetadataWriter { - /** - * Writes a list of languages. - * - * @param languages The languages to write. - * @return This writer. - */ - public static String write(List languages) { - return new LanguageWriter().append(languages).toString(); - } - + /** + * Writes a list of languages. + * + * @param languages The languages to write. + * @return This writer. + */ + public static String write(List languages) { + return new LanguageWriter().append(languages).toString(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/MetadataWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/MetadataWriter.java index bbd23cefc2..a89fecd24a 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/MetadataWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/MetadataWriter.java @@ -1,26 +1,24 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; import org.restlet.data.Metadata; /** * Metadata header writer. - * + * * @author Jerome Louvel */ public class MetadataWriter extends HeaderWriter { - @Override - public MetadataWriter append(M metadata) { - return (MetadataWriter) append(metadata.getName()); - } - + @Override + public MetadataWriter append(M metadata) { + return (MetadataWriter) append(metadata.getName()); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/MethodReader.java b/org.restlet/src/main/java/org/restlet/engine/header/MethodReader.java index 2c9e4d7a8c..6581a6b97e 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/MethodReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/MethodReader.java @@ -1,49 +1,46 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Header; -import org.restlet.data.Method; - import java.io.IOException; import java.util.Collection; +import org.restlet.data.Header; +import org.restlet.data.Method; /** * Method header reader. - * + * * @author Jerome Louvel */ public class MethodReader extends HeaderReader { - /** - * Adds values to the given collection. - * - * @param header The header to read. - * @param collection The collection to update. - */ - public static void addValues(Header header, Collection collection) { - new MethodReader(header.getValue()).addValues(collection); - } - - /** - * Constructor. - * - * @param header The header to read. - */ - public MethodReader(String header) { - super(header); - } - - @Override - public Method readValue() throws IOException { - return Method.valueOf(readToken()); - } - + /** + * Adds values to the given collection. + * + * @param header The header to read. + * @param collection The collection to update. + */ + public static void addValues(Header header, Collection collection) { + new MethodReader(header.getValue()).addValues(collection); + } + + /** + * Constructor. + * + * @param header The header to read. + */ + public MethodReader(String header) { + super(header); + } + + @Override + public Method readValue() throws IOException { + return Method.valueOf(readToken()); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/MethodWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/MethodWriter.java index 359e884101..5ca73904a0 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/MethodWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/MethodWriter.java @@ -1,38 +1,35 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Method; - import java.util.Set; +import org.restlet.data.Method; /** * Method header writer. - * + * * @author Jerome Louvel */ public class MethodWriter extends HeaderWriter { - /** - * Writes a set of methods with a comma separator. - * - * @param methods The set of methods. - * @return The formatted set of methods. - */ - public static String write(Set methods) { - return new MethodWriter().append(methods).toString(); - } - - @Override - public MethodWriter append(Method method) { - return (MethodWriter) appendToken(method.getName()); - } + /** + * Writes a set of methods with a comma separator. + * + * @param methods The set of methods. + * @return The formatted set of methods. + */ + public static String write(Set methods) { + return new MethodWriter().append(methods).toString(); + } + @Override + public MethodWriter append(Method method) { + return (MethodWriter) appendToken(method.getName()); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/PreferenceReader.java b/org.restlet/src/main/java/org/restlet/engine/header/PreferenceReader.java index a0a1602804..abf9131b99 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/PreferenceReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/PreferenceReader.java @@ -1,417 +1,440 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.*; -import org.restlet.util.Series; +import static org.restlet.engine.header.HeaderUtils.isComma; +import static org.restlet.engine.header.HeaderUtils.isDoubleQuote; +import static org.restlet.engine.header.HeaderUtils.isSpace; +import static org.restlet.engine.header.HeaderUtils.isText; +import static org.restlet.engine.header.HeaderUtils.isTokenChar; import java.io.IOException; import java.util.Iterator; - -import static org.restlet.engine.header.HeaderUtils.*; +import org.restlet.data.CharacterSet; +import org.restlet.data.ClientInfo; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; +import org.restlet.data.Parameter; +import org.restlet.data.Preference; +import org.restlet.util.Series; /** - * Preference header reader. Works for character sets, encodings, languages or - * media types. - * + * Preference header reader. Works for character sets, encodings, languages, or media types. + * * @author Jerome Louvel */ public class PreferenceReader extends HeaderReader> { - public static final int TYPE_CHARACTER_SET = 1; - - public static final int TYPE_ENCODING = 2; - - public static final int TYPE_LANGUAGE = 3; - - public static final int TYPE_MEDIA_TYPE = 4; - - public static final int TYPE_PATCH = 5; - - /** - * Parses character set preferences from a header. - * - * @param acceptCharsetHeader The header to parse. - * @param clientInfo The client info to update. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static void addCharacterSets(String acceptCharsetHeader, ClientInfo clientInfo) { - if (acceptCharsetHeader != null) { - // Implementation according to - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2 - if (acceptCharsetHeader.length() == 0) { - clientInfo.getAcceptedCharacterSets().add(new Preference(CharacterSet.ISO_8859_1)); - } else { - PreferenceReader pr = new PreferenceReader(PreferenceReader.TYPE_CHARACTER_SET, acceptCharsetHeader); - pr.addValues(clientInfo.getAcceptedCharacterSets()); - } - } else { - clientInfo.getAcceptedCharacterSets().add(new Preference(CharacterSet.ALL)); - } - } - - /** - * Parses encoding preferences from a header. - * - * @param acceptEncodingHeader The header to parse. - * @param clientInfo The client info to update. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static void addEncodings(String acceptEncodingHeader, ClientInfo clientInfo) { - if (acceptEncodingHeader != null) { - PreferenceReader pr = new PreferenceReader(PreferenceReader.TYPE_ENCODING, acceptEncodingHeader); - pr.addValues(clientInfo.getAcceptedEncodings()); - } else { - clientInfo.getAcceptedEncodings().add(new Preference(Encoding.IDENTITY)); - } - } - - /** - * Adds language preferences from a header. - * - * @param acceptLanguageHeader The header to parse. - * @param clientInfo The client info to update. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static void addLanguages(String acceptLanguageHeader, ClientInfo clientInfo) { - if (acceptLanguageHeader != null) { - PreferenceReader pr = new PreferenceReader(PreferenceReader.TYPE_LANGUAGE, acceptLanguageHeader); - pr.addValues(clientInfo.getAcceptedLanguages()); - } else { - clientInfo.getAcceptedLanguages().add(new Preference(Language.ALL)); - } - } - - /** - * Parses media type preferences from a header. - * - * @param acceptMediaTypeHeader The header to parse. - * @param clientInfo The client info to update. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static void addMediaTypes(String acceptMediaTypeHeader, ClientInfo clientInfo) { - if (acceptMediaTypeHeader != null) { - PreferenceReader pr = new PreferenceReader(PreferenceReader.TYPE_MEDIA_TYPE, acceptMediaTypeHeader); - pr.addValues(clientInfo.getAcceptedMediaTypes()); - } else { - clientInfo.getAcceptedMediaTypes().add(new Preference(MediaType.ALL)); - } - } - - /** - * Parses patch preferences from a header. - * - * @param acceptPatchHeader The header to parse. - * @param clientInfo The client info to update. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static void addPatches(String acceptPatchHeader, ClientInfo clientInfo) { - if (acceptPatchHeader != null) { - PreferenceReader pr = new PreferenceReader(PreferenceReader.TYPE_PATCH, acceptPatchHeader); - pr.addValues(clientInfo.getAcceptedPatches()); - } - } - - /** - * Parses a quality value.
- * If the quality is invalid, an IllegalArgumentException is thrown. - * - * @param quality The quality value as a string. - * @return The parsed quality value as a float. - */ - public static float readQuality(String quality) { - try { - float result = Float.valueOf(quality); - - if (PreferenceWriter.isValidQuality(result)) { - return result; - } - - throw new IllegalArgumentException("Invalid quality value detected. Value must be between 0 and 1."); - } catch (NumberFormatException nfe) { - throw new IllegalArgumentException("Invalid quality value detected. Value must be between 0 and 1."); - } - } - - /** The type of metadata read. */ - private volatile int type; - - /** - * Constructor. - * - * @param type The type of metadata read. - * @param header The header to read. - */ - public PreferenceReader(int type, String header) { - super(header); - this.type = type; - } - - /** - * Creates a new preference. - * - * @param metadata The metadata name. - * @param parameters The parameters list. - * @return The new preference. - */ - @SuppressWarnings("unchecked") - protected Preference createPreference(CharSequence metadata, Series parameters) { - Preference result; - - if (parameters == null) { - result = new Preference(); - - switch (this.type) { - case TYPE_CHARACTER_SET: - result.setMetadata((T) CharacterSet.valueOf(metadata.toString())); - break; - - case TYPE_ENCODING: - result.setMetadata((T) Encoding.valueOf(metadata.toString())); - break; - - case TYPE_LANGUAGE: - result.setMetadata((T) Language.valueOf(metadata.toString())); - break; - - case TYPE_MEDIA_TYPE: - case TYPE_PATCH: - result.setMetadata((T) MediaType.valueOf(metadata.toString())); - break; - } - } else { - final Series mediaParams = extractMediaParams(parameters); - final float quality = extractQuality(parameters); - result = new Preference(null, quality, parameters); - - switch (this.type) { - case TYPE_CHARACTER_SET: - result.setMetadata((T) new CharacterSet(metadata.toString())); - break; - - case TYPE_ENCODING: - result.setMetadata((T) new Encoding(metadata.toString())); - break; - - case TYPE_LANGUAGE: - result.setMetadata((T) new Language(metadata.toString())); - break; - - case TYPE_MEDIA_TYPE: - case TYPE_PATCH: - result.setMetadata((T) new MediaType(metadata.toString(), mediaParams)); - break; - } - } - - return result; - } - - /** - * Extract the media parameters. Only leave as the quality parameter if found. - * Modifies the parameters list. - * - * @param parameters All the preference parameters. - * @return The media parameters. - */ - protected Series extractMediaParams(Series parameters) { - Series result = null; - boolean qualityFound = false; - Parameter param = null; - - if (parameters != null) { - result = new Series(Parameter.class); - - for (final Iterator iter = parameters.iterator(); !qualityFound && iter.hasNext();) { - param = iter.next(); - - if (param.getName().equals("q")) { - qualityFound = true; - } else { - iter.remove(); - result.add(param); - } - } - } - - return result; - } - - /** - * Extract the quality value. If the value is not found, 1 is returned. - * - * @param parameters The preference parameters. - * @return The quality value. - */ - protected float extractQuality(Series parameters) { - float result = 1F; - boolean found = false; - - if (parameters != null) { - Parameter param = null; - - for (final Iterator iter = parameters.iterator(); !found && iter.hasNext();) { - param = iter.next(); - if (param.getName().equals("q")) { - result = readQuality(param.getValue()); - found = true; - - // Remove the quality parameter as we will directly store it - // in the Preference object - iter.remove(); - } - } - } - - return result; - } - - /** - * Read the next preference. - * - * @return The next preference. - */ - public Preference readValue() throws IOException { - Preference result = null; - - boolean readingMetadata = true; - boolean readingParamName = false; - boolean readingParamValue = false; - - StringBuilder metadataBuffer = new StringBuilder(); - StringBuilder paramNameBuffer = null; - StringBuilder paramValueBuffer = null; - - Series parameters = null; - int next = 0; - - while (result == null) { - next = read(); - - if (readingMetadata) { - if ((next == -1) || isComma(next)) { - if (metadataBuffer.length() > 0) { - // End of metadata section - // No parameters detected - result = createPreference(metadataBuffer, null); - } else { - // Ignore empty metadata name - break; - } - } else if (next == ';') { - if (metadataBuffer.length() > 0) { - // End of metadata section - // Parameters detected - readingMetadata = false; - readingParamName = true; - paramNameBuffer = new StringBuilder(); - parameters = new Series(Parameter.class); - } else { - throw new IOException("Empty metadata name detected."); - } - } else if (isSpace(next)) { - // Ignore spaces - } else if (isText(next)) { - metadataBuffer.append((char) next); - } else { - throw new IOException("Unexpected character \"" + (char) next + "\" detected."); - } - } else if (readingParamName) { - if (next == '=') { - if (paramNameBuffer.length() > 0) { - // End of parameter name section - readingParamName = false; - readingParamValue = true; - paramValueBuffer = new StringBuilder(); - } else { - throw new IOException("Empty parameter name detected."); - } - } else if ((next == -1) || isComma(next)) { - if (paramNameBuffer.length() > 0) { - // End of parameters section - parameters.add(Parameter.create(paramNameBuffer, null)); - result = createPreference(metadataBuffer, parameters); - } else { - throw new IOException("Empty parameter name detected."); - } - } else if (next == ';') { - // End of parameter - parameters.add(Parameter.create(paramNameBuffer, null)); - paramNameBuffer = new StringBuilder(); - readingParamName = true; - readingParamValue = false; - } else if (isSpace(next) && (paramNameBuffer.length() == 0)) { - // Ignore white spaces - } else if (isTokenChar(next)) { - paramNameBuffer.append((char) next); - } else { - throw new IOException("Unexpected character \"" + (char) next + "\" detected."); - } - } else if (readingParamValue) { - if ((next == -1) || isComma(next) || isSpace(next)) { - if (paramValueBuffer.length() > 0) { - // End of parameters section - parameters.add(Parameter.create(paramNameBuffer, paramValueBuffer)); - result = createPreference(metadataBuffer, parameters); - } else { - throw new IOException("Empty parameter value detected"); - } - } else if (next == ';') { - // End of parameter - parameters.add(Parameter.create(paramNameBuffer, paramValueBuffer)); - paramNameBuffer = new StringBuilder(); - readingParamName = true; - readingParamValue = false; - } else if ((next == '"') && (paramValueBuffer.length() == 0)) { - // Parse the quoted string - boolean done = false; - boolean quotedPair = false; - - while ((!done) && (next != -1)) { - next = read(); - - if (quotedPair) { - // End of quoted pair (escape sequence) - if (isText(next)) { - paramValueBuffer.append((char) next); - quotedPair = false; - } else { - throw new IOException( - "Invalid character detected in quoted string. Please check your value"); - } - } else if (isDoubleQuote(next)) { - // End of quoted string - done = true; - } else if (next == '\\') { - // Begin of quoted pair (escape sequence) - quotedPair = true; - } else if (isText(next)) { - paramValueBuffer.append((char) next); - } else { - throw new IOException( - "Invalid character detected in quoted string. Please check your value"); - } - } - } else if (isTokenChar(next)) { - paramValueBuffer.append((char) next); - } else { - throw new IOException("Unexpected character \"" + (char) next + "\" detected."); - } - } - } - - if (isComma(next)) { - // Unread character which isn't part of the value - unread(); - } - - return result; - } + public static final int TYPE_CHARACTER_SET = 1; + + public static final int TYPE_ENCODING = 2; + + public static final int TYPE_LANGUAGE = 3; + + public static final int TYPE_MEDIA_TYPE = 4; + + public static final int TYPE_PATCH = 5; + + /** + * Parses character set preferences from a header. + * + * @param acceptCharsetHeader The header to parse. + * @param clientInfo The client info to update. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void addCharacterSets(String acceptCharsetHeader, ClientInfo clientInfo) { + if (acceptCharsetHeader != null) { + // Implementation according to + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2 + if (acceptCharsetHeader.isEmpty()) { + clientInfo + .getAcceptedCharacterSets() + .add(new Preference<>(CharacterSet.ISO_8859_1)); + } else { + PreferenceReader pr = + new PreferenceReader( + PreferenceReader.TYPE_CHARACTER_SET, acceptCharsetHeader); + pr.addValues(clientInfo.getAcceptedCharacterSets()); + } + } else { + clientInfo.getAcceptedCharacterSets().add(new Preference(CharacterSet.ALL)); + } + } + + /** + * Parses encoding preferences from a header. + * + * @param acceptEncodingHeader The header to parse. + * @param clientInfo The client info to update. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void addEncodings(String acceptEncodingHeader, ClientInfo clientInfo) { + if (acceptEncodingHeader != null) { + PreferenceReader pr = + new PreferenceReader(PreferenceReader.TYPE_ENCODING, acceptEncodingHeader); + pr.addValues(clientInfo.getAcceptedEncodings()); + } else { + clientInfo.getAcceptedEncodings().add(new Preference(Encoding.IDENTITY)); + } + } + + /** + * Adds language preferences from a header. + * + * @param acceptLanguageHeader The header to parse. + * @param clientInfo The client info to update. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void addLanguages(String acceptLanguageHeader, ClientInfo clientInfo) { + if (acceptLanguageHeader != null) { + PreferenceReader pr = + new PreferenceReader(PreferenceReader.TYPE_LANGUAGE, acceptLanguageHeader); + pr.addValues(clientInfo.getAcceptedLanguages()); + } else { + clientInfo.getAcceptedLanguages().add(new Preference(Language.ALL)); + } + } + + /** + * Parses media type preferences from a header. + * + * @param acceptMediaTypeHeader The header to parse. + * @param clientInfo The client info to update. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void addMediaTypes(String acceptMediaTypeHeader, ClientInfo clientInfo) { + if (acceptMediaTypeHeader != null) { + PreferenceReader pr = + new PreferenceReader(PreferenceReader.TYPE_MEDIA_TYPE, acceptMediaTypeHeader); + pr.addValues(clientInfo.getAcceptedMediaTypes()); + } else { + clientInfo.getAcceptedMediaTypes().add(new Preference(MediaType.ALL)); + } + } + + /** + * Parses patch preferences from a header. + * + * @param acceptPatchHeader The header to parse. + * @param clientInfo The client info to update. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void addPatches(String acceptPatchHeader, ClientInfo clientInfo) { + if (acceptPatchHeader != null) { + PreferenceReader pr = + new PreferenceReader(PreferenceReader.TYPE_PATCH, acceptPatchHeader); + pr.addValues(clientInfo.getAcceptedPatches()); + } + } + + /** + * Parses a quality value.
+ * If the quality is invalid, an IllegalArgumentException is thrown. + * + * @param quality The quality value as a string. + * @return The parsed quality value as a float. + */ + public static float readQuality(String quality) { + try { + float result = Float.parseFloat(quality); + + if (PreferenceWriter.isValidQuality(result)) { + return result; + } + + throw new IllegalArgumentException( + "Invalid quality value detected. Value must be between 0 and 1."); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException( + "Invalid quality value detected. Value must be between 0 and 1."); + } + } + + /** The type of metadata read. */ + private final int type; + + /** + * Constructor. + * + * @param type The type of metadata read. + * @param header The header to read. + */ + public PreferenceReader(int type, String header) { + super(header); + this.type = type; + } + + /** + * Creates a new preference. + * + * @param metadata The metadata name. + * @param parameters The parameters list. + * @return The new preference. + */ + @SuppressWarnings("unchecked") + protected Preference createPreference(CharSequence metadata, Series parameters) { + Preference result; + + if (parameters == null) { + result = new Preference<>(); + + switch (this.type) { + case TYPE_CHARACTER_SET: + result.setMetadata((T) CharacterSet.valueOf(metadata.toString())); + break; + + case TYPE_ENCODING: + result.setMetadata((T) Encoding.valueOf(metadata.toString())); + break; + + case TYPE_LANGUAGE: + result.setMetadata((T) Language.valueOf(metadata.toString())); + break; + + case TYPE_MEDIA_TYPE, TYPE_PATCH: + result.setMetadata((T) MediaType.valueOf(metadata.toString())); + break; + default: + break; + } + } else { + final Series mediaParams = extractMediaParams(parameters); + final float quality = extractQuality(parameters); + result = new Preference<>(null, quality, parameters); + + switch (this.type) { + case TYPE_CHARACTER_SET: + result.setMetadata((T) new CharacterSet(metadata.toString())); + break; + + case TYPE_ENCODING: + result.setMetadata((T) new Encoding(metadata.toString())); + break; + + case TYPE_LANGUAGE: + result.setMetadata((T) new Language(metadata.toString())); + break; + + case TYPE_MEDIA_TYPE, TYPE_PATCH: + result.setMetadata((T) new MediaType(metadata.toString(), mediaParams)); + break; + default: + break; + } + } + + return result; + } + + /** + * Extract the media parameters. Only leave as the quality parameter if found. Modifies the + * parameters list. + * + * @param parameters All the preference parameters. + * @return The media parameters. + */ + protected Series extractMediaParams(Series parameters) { + Series result = null; + boolean qualityFound = false; + Parameter param = null; + + if (parameters != null) { + result = new Series<>(Parameter.class); + + for (final Iterator iter = parameters.iterator(); + !qualityFound && iter.hasNext(); ) { + param = iter.next(); + + if (param.getName().equals("q")) { + qualityFound = true; + } else { + iter.remove(); + result.add(param); + } + } + } + + return result; + } + + /** + * Extract the quality value. If the value is not found, 1 is returned. + * + * @param parameters The preference parameters. + * @return The quality value. + */ + protected float extractQuality(Series parameters) { + float result = 1F; + boolean found = false; + + if (parameters != null) { + Parameter param = null; + + for (final Iterator iter = parameters.iterator(); + !found && iter.hasNext(); ) { + param = iter.next(); + if (param.getName().equals("q")) { + result = readQuality(param.getValue()); + found = true; + + // Remove the quality parameter as we will directly store it + // in the Preference object + iter.remove(); + } + } + } + + return result; + } + + /** + * Read the next preference. + * + * @return The next preference. + */ + @Override + public Preference readValue() throws IOException { + Preference result = null; + + boolean readingMetadata = true; + boolean readingParamName = false; + boolean readingParamValue = false; + + StringBuilder metadataBuffer = new StringBuilder(); + StringBuilder paramNameBuffer = null; + StringBuilder paramValueBuffer = null; + + Series parameters = null; + int next = 0; + + while (result == null) { + next = read(); + + if (readingMetadata) { + if ((next == -1) || isComma(next)) { + if (metadataBuffer.isEmpty()) { + // Ignore empty metadata name + break; + } else { + // End of metadata section + // No parameters detected + result = createPreference(metadataBuffer, null); + } + } else if (next == ';') { + if (metadataBuffer.isEmpty()) { + throw new IOException("Empty metadata name detected."); + } else { + // End of metadata section + // Parameters detected + readingMetadata = false; + readingParamName = true; + paramNameBuffer = new StringBuilder(); + parameters = new Series<>(Parameter.class); + } + } else if (isSpace(next)) { + // Ignore spaces + } else if (isText(next)) { + metadataBuffer.append((char) next); + } else { + throw new IOException("Unexpected character \"" + (char) next + "\" detected."); + } + } else if (readingParamName) { + if (next == '=') { + if (paramNameBuffer.isEmpty()) { + throw new IOException("Empty parameter name detected."); + } else { + // End of a parameter name section + readingParamName = false; + readingParamValue = true; + paramValueBuffer = new StringBuilder(); + } + } else if ((next == -1) || isComma(next)) { + if (paramNameBuffer.isEmpty()) { + throw new IOException("Empty parameter name detected."); + } else { + // End of a parameters section + parameters.add(Parameter.create(paramNameBuffer, null)); + result = createPreference(metadataBuffer, parameters); + } + } else if (next == ';') { + // End of parameter + parameters.add(Parameter.create(paramNameBuffer, null)); + paramNameBuffer = new StringBuilder(); + readingParamName = true; + readingParamValue = false; + } else if (isSpace(next) && paramNameBuffer.isEmpty()) { + // Ignore white spaces + } else if (isTokenChar(next)) { + paramNameBuffer.append((char) next); + } else { + throw new IOException("Unexpected character \"" + (char) next + "\" detected."); + } + } else if (readingParamValue) { + if ((next == -1) || isComma(next) || isSpace(next)) { + if (paramValueBuffer.isEmpty()) { + throw new IOException("Empty parameter value detected"); + } else { + // End of a parameters section + parameters.add(Parameter.create(paramNameBuffer, paramValueBuffer)); + result = createPreference(metadataBuffer, parameters); + } + } else if (next == ';') { + // End of parameter + parameters.add(Parameter.create(paramNameBuffer, paramValueBuffer)); + paramNameBuffer = new StringBuilder(); + readingParamName = true; + readingParamValue = false; + } else if ((next == '"') && paramValueBuffer.isEmpty()) { + // Parse the quoted string + boolean done = false; + boolean quotedPair = false; + + while ((!done) && (next != -1)) { + next = read(); + + if (quotedPair) { + // End of a quoted pair (escape sequence) + if (isText(next)) { + paramValueBuffer.append((char) next); + quotedPair = false; + } else { + throw new IOException( + "Invalid character detected in quoted string. Please check your value"); + } + } else if (isDoubleQuote(next)) { + // End of quoted string + done = true; + } else if (next == '\\') { + // Begin of a quoted pair (escape sequence) + quotedPair = true; + } else if (isText(next)) { + paramValueBuffer.append((char) next); + } else { + throw new IOException( + "Invalid character detected in quoted string. Please check your value"); + } + } + } else if (isTokenChar(next)) { + paramValueBuffer.append((char) next); + } else { + throw new IOException("Unexpected character \"" + (char) next + "\" detected."); + } + } + } + + if (isComma(next)) { + // Unread character which isn't part of the value + unread(); + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/PreferenceWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/PreferenceWriter.java index 1b0ca1cd65..3ec395b390 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/PreferenceWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/PreferenceWriter.java @@ -1,94 +1,90 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.util.List; import org.restlet.data.Parameter; import org.restlet.data.Preference; -import java.io.IOException; -import java.util.Iterator; -import java.util.List; - /** * Preference header writer. - * + * * @author Jerome Louvel */ public class PreferenceWriter extends HeaderWriter> { - /** - * Indicates if the quality value is valid. - * - * @param quality The quality value. - * @return True if the quality value is valid. - */ - public static boolean isValidQuality(float quality) { - return (quality >= 0F) && (quality <= 1F); - } - - /** - * Writes a list of preferences with a comma separator. - * - * @param prefs The list of preferences. - * @return The formatted list of preferences. - * @throws IOException - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static String write(List prefs) { - return new PreferenceWriter().append(prefs).toString(); - } - - @Override - public PreferenceWriter append(Preference pref) { - append(pref.getMetadata().getName()); - - if (pref.getQuality() < 1F) { - append(";q="); - appendQuality(pref.getQuality()); - } - - if (pref.getParameters() != null) { - Parameter param; - - for (Iterator iter = pref.getParameters().iterator(); iter.hasNext();) { - param = iter.next(); - - if (param.getName() != null) { - append(';').append(param.getName()); - - if ((param.getValue() != null) && (param.getValue().length() > 0)) { - append('=').append(param.getValue()); - } - } - } - } - - return this; - } - - /** - * Formats a quality value.
- * If the quality is invalid, an IllegalArgumentException is thrown. - * - * @param quality The quality value as a float. - * @return This writer. - */ - public PreferenceWriter appendQuality(float quality) { - if (!isValidQuality(quality)) { - throw new IllegalArgumentException("Invalid quality value detected. Value must be between 0 and 1."); - } - - java.text.NumberFormat formatter = java.text.NumberFormat.getNumberInstance(java.util.Locale.US); - formatter.setMaximumFractionDigits(2); - append(formatter.format(quality)); - - return this; - } - + /** + * Indicates if the quality value is valid. + * + * @param quality The quality value. + * @return True if the quality value is valid. + */ + public static boolean isValidQuality(float quality) { + return (quality >= 0F) && (quality <= 1F); + } + + /** + * Writes a list of preferences with a comma separator. + * + * @param prefs The list of preferences. + * @return The formatted list of preferences. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static String write(List prefs) { + return new PreferenceWriter().append(prefs).toString(); + } + + @Override + public PreferenceWriter append(Preference pref) { + append(pref.getMetadata().getName()); + + if (pref.getQuality() < 1F) { + append(";q="); + appendQuality(pref.getQuality()); + } + + if (pref.getParameters() != null) { + Parameter param; + + for (final Parameter parameter : pref.getParameters()) { + param = parameter; + + if (param.getName() != null) { + append(';').append(param.getName()); + + if ((param.getValue() != null) && (!param.getValue().isEmpty())) { + append('=').append(param.getValue()); + } + } + } + } + + return this; + } + + /** + * Formats a quality value.
+ * If the quality is invalid, an IllegalArgumentException is thrown. + * + * @param quality The quality value as a float. + * @return This writer. + */ + public PreferenceWriter appendQuality(float quality) { + if (!isValidQuality(quality)) { + throw new IllegalArgumentException( + "Invalid quality value detected. Value must be between 0 and 1."); + } + + java.text.NumberFormat formatter = + java.text.NumberFormat.getNumberInstance(java.util.Locale.US); + formatter.setMaximumFractionDigits(2); + append(formatter.format(quality)); + + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/ProductReader.java b/org.restlet/src/main/java/org/restlet/engine/header/ProductReader.java index b8a4a7470c..d8b5c3f4ee 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/ProductReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/ProductReader.java @@ -1,125 +1,120 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Product; - import java.util.ArrayList; import java.util.List; +import org.restlet.data.Product; /** * User agent header reader. - * + * * @author Thierry Boileau */ public class ProductReader { - /** - * Parses the given user agent String to a list of Product instances. - * - * @param userAgent - * @return the List of Product objects parsed from the String - * @throws IllegalArgumentException Thrown if the String can not be parsed as a - * list of Product instances. - */ - public static List read(String userAgent) throws IllegalArgumentException { - final List result = new ArrayList(); - - if (userAgent != null) { - String token = null; - String version = null; - String comment = null; - final char[] tab = userAgent.trim().toCharArray(); - StringBuilder tokenBuilder = new StringBuilder(); - StringBuilder versionBuilder = null; - StringBuilder commentBuilder = null; - int index = 0; - boolean insideToken = true; - boolean insideVersion = false; - boolean insideComment = false; - - for (index = 0; index < tab.length; index++) { - final char c = tab[index]; - if (insideToken) { - if (HeaderUtils.isTokenChar(c) || (c == ' ')) { - tokenBuilder.append(c); - } else { - token = tokenBuilder.toString().trim(); - insideToken = false; - if (c == '/') { - insideVersion = true; - versionBuilder = new StringBuilder(); - } else if (c == '(') { - insideComment = true; - commentBuilder = new StringBuilder(); - } - } - } else { - if (insideVersion) { - if (c != ' ') { - versionBuilder.append(c); - } else { - insideVersion = false; - version = versionBuilder.toString(); - } - } else { - if (c == '(') { - insideComment = true; - commentBuilder = new StringBuilder(); - } else { - if (insideComment) { - if (c == ')') { - insideComment = false; - comment = commentBuilder.toString(); - result.add(new Product(token, version, comment)); - insideToken = true; - tokenBuilder = new StringBuilder(); - } else { - commentBuilder.append(c); - } - } else { - result.add(new Product(token, version, null)); - insideToken = true; - tokenBuilder = new StringBuilder(); - tokenBuilder.append(c); - } - } - } - } - } + /** + * Parses the given user agent String to a list of Product instances. + * + * @param userAgent + * @return the List of Product objects parsed from the String + * @throws IllegalArgumentException Thrown if the String cannot be parsed as a list of Product + * instances. + */ + public static List read(String userAgent) throws IllegalArgumentException { + final List result = new ArrayList<>(); - if (insideComment) { - comment = commentBuilder.toString(); - result.add(new Product(token, version, comment)); - } else { - if (insideVersion) { - version = versionBuilder.toString(); - result.add(new Product(token, version, null)); - } else { - if (insideToken && (!tokenBuilder.isEmpty())) { - token = tokenBuilder.toString(); - result.add(new Product(token, null, null)); - } - } - } - } + if (userAgent != null) { + String token = null; + String version = null; + String comment = null; + final char[] tab = userAgent.trim().toCharArray(); + StringBuilder tokenBuilder = new StringBuilder(); + StringBuilder versionBuilder = null; + StringBuilder commentBuilder = null; + int index = 0; + boolean insideToken = true; + boolean insideVersion = false; + boolean insideComment = false; - return result; + for (index = 0; index < tab.length; index++) { + final char c = tab[index]; + if (insideToken) { + if (HeaderUtils.isTokenChar(c) || (c == ' ')) { + tokenBuilder.append(c); + } else { + token = tokenBuilder.toString().trim(); + insideToken = false; + if (c == '/') { + insideVersion = true; + versionBuilder = new StringBuilder(); + } else if (c == '(') { + insideComment = true; + commentBuilder = new StringBuilder(); + } + } + } else { + if (insideVersion) { + if (c != ' ') { + versionBuilder.append(c); + } else { + insideVersion = false; + version = versionBuilder.toString(); + } + } else { + if (c == '(') { + insideComment = true; + commentBuilder = new StringBuilder(); + } else { + if (insideComment) { + if (c == ')') { + insideComment = false; + comment = commentBuilder.toString(); + result.add(new Product(token, version, comment)); + insideToken = true; + tokenBuilder = new StringBuilder(); + } else { + commentBuilder.append(c); + } + } else { + result.add(new Product(token, version, null)); + insideToken = true; + tokenBuilder = new StringBuilder(); + tokenBuilder.append(c); + } + } + } + } + } - } + if (insideComment) { + comment = commentBuilder.toString(); + result.add(new Product(token, version, comment)); + } else { + if (insideVersion) { + version = versionBuilder.toString(); + result.add(new Product(token, version, null)); + } else { + if (insideToken && (!tokenBuilder.isEmpty())) { + token = tokenBuilder.toString(); + result.add(new Product(token, null, null)); + } + } + } + } - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e. it isn't instantiable and extensible. - */ - private ProductReader() { - } + return result; + } + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private ProductReader() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/ProductWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/ProductWriter.java index ba90f6cb4d..289f443a71 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/ProductWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/ProductWriter.java @@ -1,58 +1,55 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Product; - import java.util.Iterator; import java.util.List; +import org.restlet.data.Product; /** * User agent header writer. - * + * * @author Thierry Boileau */ public class ProductWriter { - /** - * Formats the given List of Products to a String. - * - * @param products The list of products to format. - * @return the List of Products as String. - */ - public static String write(List products) { - StringBuilder builder = new StringBuilder(); - - for (Iterator iterator = products.iterator(); iterator.hasNext();) { - Product product = iterator.next(); + /** + * Formats the given List of Products to a String. + * + * @param products The list of products to format. + * @return the List of Products as String. + */ + public static String write(List products) { + StringBuilder builder = new StringBuilder(); - if ((product.getName() == null) || (product.getName().isEmpty())) { - throw new IllegalArgumentException("Product name cannot be null."); - } + for (Iterator iterator = products.iterator(); iterator.hasNext(); ) { + Product product = iterator.next(); - builder.append(product.getName()); + if ((product.getName() == null) || (product.getName().isEmpty())) { + throw new IllegalArgumentException("Product name cannot be null."); + } - if (product.getVersion() != null) { - builder.append("/").append(product.getVersion()); - } + builder.append(product.getName()); - if (product.getComment() != null) { - builder.append(" (").append(product.getComment()).append(")"); - } + if (product.getVersion() != null) { + builder.append("/").append(product.getVersion()); + } - if (iterator.hasNext()) { - builder.append(" "); - } - } + if (product.getComment() != null) { + builder.append(" (").append(product.getComment()).append(")"); + } - return builder.toString(); - } + if (iterator.hasNext()) { + builder.append(" "); + } + } + return builder.toString(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/RangeReader.java b/org.restlet/src/main/java/org/restlet/engine/header/RangeReader.java index f82a34cbee..7fdbd8782c 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/RangeReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/RangeReader.java @@ -1,69 +1,68 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Range; -import org.restlet.representation.Representation; - import java.util.ArrayList; import java.util.List; +import org.restlet.data.Range; +import org.restlet.representation.Representation; /** * Range header reader. - * + * * @author Jerome Louvel */ public class RangeReader { - private static final String BYTES_RANGE_PREFIX = "bytes "; + private static final String BYTES_RANGE_PREFIX = "bytes "; - /** - * Parse the Content-Range header value and update the given representation. - * - * @param value Content-range header. - * @param representation Representation to update. - */ - public static void update(String value, Representation representation) { - if (value != null && value.startsWith(BYTES_RANGE_PREFIX)) { - value = value.substring(BYTES_RANGE_PREFIX.length()); + /** + * Parse the Content-Range header value and update the given representation. + * + * @param value Content-range header. + * @param representation Representation to update. + */ + public static void update(String value, Representation representation) { + if (value != null && value.startsWith(BYTES_RANGE_PREFIX)) { + value = value.substring(BYTES_RANGE_PREFIX.length()); - int index = value.indexOf("-"); - int index1 = value.indexOf("/"); + int index = value.indexOf('-'); + int index1 = value.indexOf('/'); - if (index != -1) { - long startIndex = (index == 0) ? Range.INDEX_LAST : Long.parseLong(value.substring(0, index)); - long endIndex = Long.parseLong(value.substring(index + 1, index1)); + if (index != -1) { + long startIndex = + (index == 0) ? Range.INDEX_LAST : Long.parseLong(value.substring(0, index)); + long endIndex = Long.parseLong(value.substring(index + 1, index1)); - representation.setRange(new Range(startIndex, endIndex - startIndex + 1)); - } + representation.setRange(new Range(startIndex, endIndex - startIndex + 1)); + } - String strLength = value.substring(index1 + 1); - if (!("*".equals(strLength))) { - representation.setSize(Long.parseLong(strLength)); - } - } - } + String strLength = value.substring(index1 + 1); + if (!("*".equals(strLength))) { + representation.setSize(Long.parseLong(strLength)); + } + } + } - /** - * Parse the Range header and returns the list of corresponding Range objects. - * - * @param rangeHeader The Range header value. - * @return The list of corresponding Range objects. - */ - public static List read(String rangeHeader) { - List result = new ArrayList(); - String prefix = "bytes="; - if (rangeHeader != null && rangeHeader.startsWith(prefix)) { - rangeHeader = rangeHeader.substring(prefix.length()); + /** + * Parse the Range header and returns the list of corresponding Range objects. + * + * @param rangeHeader The Range header value. + * @return The list of corresponding Range objects. + */ + public static List read(String rangeHeader) { + List result = new ArrayList<>(); + String prefix = "bytes="; + if (rangeHeader != null && rangeHeader.startsWith(prefix)) { + rangeHeader = rangeHeader.substring(prefix.length()); - String[] array = rangeHeader.split(","); + String[] array = rangeHeader.split(","); for (String s : array) { String value = s.trim(); long index = 0; @@ -83,15 +82,14 @@ public static List read(String rangeHeader) { } result.add(new Range(index, length)); } - } + } - return result; - } + return result; + } - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e., it isn't instantiable and extensible. - */ - private RangeReader() { - } + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private RangeReader() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/RangeWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/RangeWriter.java index fc1ca5e10e..c660e4967e 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/RangeWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/RangeWriter.java @@ -1,132 +1,134 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.util.List; import org.restlet.data.Range; import org.restlet.representation.Representation; -import java.util.List; - /** * Range header writer. - * + * * @author Jerome Louvel */ public class RangeWriter extends HeaderWriter { - /** - * Formats {@code ranges} as a Range header value - * - * @param ranges List of ranges to format - * @return {@code ranges} formatted or null if the list is null or empty. - */ - public static String write(List ranges) { - return new RangeWriter().append(ranges).toString(); - } - - /** - * Formats {@code range} as a Content-Range header value. - * - * @param range Range to format - * @param size Total size of the entity - * @return {@code range} formatted - */ - public static String write(Range range, long size) { - StringBuilder b = new StringBuilder(range.getUnitName() + " "); - - if (range.getIndex() >= Range.INDEX_FIRST) { - b.append(range.getIndex()); - b.append("-"); - if (range.getSize() != Range.SIZE_MAX) { - b.append(range.getIndex() + range.getSize() - 1); - } else { - if (size != Representation.UNKNOWN_SIZE) { - b.append(size - 1); - } else { - throw new IllegalArgumentException( - "The entity has an unknown size, can't determine the last byte position."); - } - } - } else if (range.getIndex() == Range.INDEX_LAST) { - if (range.getSize() != Range.SIZE_MAX) { - if (size != Representation.UNKNOWN_SIZE) { - if (range.getSize() <= size) { - b.append(size - range.getSize()); - b.append("-"); - b.append(size - 1); - } else { - throw new IllegalArgumentException("The size of the range (" + range.getSize() - + ") is higher than the size of the entity (" + size + ")."); - } - } else { - throw new IllegalArgumentException( - "The entity has an unknown size, can't determine the last byte position."); - } - } else { - // This is not a valid range. - throw new IllegalArgumentException("The range provides no index and no size, it is invalid."); - } - } - - if (size != Representation.UNKNOWN_SIZE) { - b.append("/").append(size); - } else { - b.append("/*"); - } - - return b.toString(); - } - - /** - * Formats {@code ranges} as a Range header value - * - * @param ranges List of ranges to format - * @return This writer. - */ - public RangeWriter append(List ranges) { - if (ranges == null || ranges.isEmpty()) { - return this; - } - - append(ranges.get(0).getUnitName()); - append("="); - - for (int i = 0; i < ranges.size(); i++) { - if (i > 0) { - append(", "); - } - - append(ranges.get(i)); - } - - return this; - } - - @Override - public HeaderWriter append(Range range) { - if (range.getIndex() >= Range.INDEX_FIRST) { - append(range.getIndex()); - append("-"); - - if (range.getSize() != Range.SIZE_MAX) { - append(range.getIndex() + range.getSize() - 1); - } - } else if (range.getIndex() == Range.INDEX_LAST) { - append("-"); - - if (range.getSize() != Range.SIZE_MAX) { - append(range.getSize()); - } - } - - return this; - } - + /** + * Formats {@code ranges} as a Range header value + * + * @param ranges List of ranges to format + * @return {@code ranges} formatted or null if the list is null or empty. + */ + public static String write(List ranges) { + return new RangeWriter().append(ranges).toString(); + } + + /** + * Formats {@code range} as a Content-Range header value. + * + * @param range Range to format + * @param size Total size of the entity + * @return {@code range} formatted + */ + public static String write(Range range, long size) { + StringBuilder b = new StringBuilder(range.getUnitName() + " "); + + if (range.getIndex() >= Range.INDEX_FIRST) { + b.append(range.getIndex()); + b.append("-"); + if (range.getSize() != Range.SIZE_MAX) { + b.append(range.getIndex() + range.getSize() - 1); + } else { + if (size != Representation.UNKNOWN_SIZE) { + b.append(size - 1); + } else { + throw new IllegalArgumentException( + "The entity has an unknown size, can't determine the last byte position."); + } + } + } else if (range.getIndex() == Range.INDEX_LAST) { + if (range.getSize() != Range.SIZE_MAX) { + if (size != Representation.UNKNOWN_SIZE) { + if (range.getSize() <= size) { + b.append(size - range.getSize()); + b.append("-"); + b.append(size - 1); + } else { + throw new IllegalArgumentException( + "The size of the range (" + + range.getSize() + + ") is higher than the size of the entity (" + + size + + ")."); + } + } else { + throw new IllegalArgumentException( + "The entity has an unknown size, can't determine the last byte position."); + } + } else { + // This is not a valid range. + throw new IllegalArgumentException( + "The range provides no index and no size, it is invalid."); + } + } + + if (size != Representation.UNKNOWN_SIZE) { + b.append("/").append(size); + } else { + b.append("/*"); + } + + return b.toString(); + } + + /** + * Formats {@code ranges} as a Range header value + * + * @param ranges List of ranges to format + * @return This writer. + */ + public RangeWriter append(List ranges) { + if (ranges == null || ranges.isEmpty()) { + return this; + } + + append(ranges.getFirst().getUnitName()); + append("="); + + for (int i = 0; i < ranges.size(); i++) { + if (i > 0) { + append(", "); + } + + append(ranges.get(i)); + } + + return this; + } + + @Override + public HeaderWriter append(Range range) { + if (range.getIndex() >= Range.INDEX_FIRST) { + append(range.getIndex()); + append("-"); + + if (range.getSize() != Range.SIZE_MAX) { + append(range.getIndex() + range.getSize() - 1); + } + } else if (range.getIndex() == Range.INDEX_LAST) { + append("-"); + + if (range.getSize() != Range.SIZE_MAX) { + append(range.getSize()); + } + } + + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/RecipientInfoReader.java b/org.restlet/src/main/java/org/restlet/engine/header/RecipientInfoReader.java index 0c8480dd06..a6b622e8b3 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/RecipientInfoReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/RecipientInfoReader.java @@ -1,73 +1,71 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.io.IOException; +import java.util.Collection; import org.restlet.data.Header; import org.restlet.data.Protocol; import org.restlet.data.RecipientInfo; -import java.io.IOException; -import java.util.Collection; - /** * Recipient info header reader. - * + * * @author Jerome Louvel */ public class RecipientInfoReader extends HeaderReader { - /** - * Adds values to the given collection. - * - * @param header The header to read. - * @param collection The collection to update. - */ - public static void addValues(Header header, Collection collection) { - new RecipientInfoReader(header.getValue()).addValues(collection); - } + /** + * Adds values to the given collection. + * + * @param header The header to read. + * @param collection The collection to update. + */ + public static void addValues(Header header, Collection collection) { + new RecipientInfoReader(header.getValue()).addValues(collection); + } - /** - * Constructor. - * - * @param header The header to read. - */ - public RecipientInfoReader(String header) { - super(header); - } + /** + * Constructor. + * + * @param header The header to read. + */ + public RecipientInfoReader(String header) { + super(header); + } - @Override - public RecipientInfo readValue() throws IOException { - RecipientInfo result = new RecipientInfo(); - String protocolToken = readToken(); + @Override + public RecipientInfo readValue() throws IOException { + RecipientInfo result = new RecipientInfo(); + String protocolToken = readToken(); - if (protocolToken == null || protocolToken.isEmpty()) { - throw new IOException( - "Unexpected empty protocol token for while reading recipient info header, please check the value."); - } + if (protocolToken == null || protocolToken.isEmpty()) { + throw new IOException( + "Unexpected empty protocol token for while reading recipient info header, please check the value."); + } - if (peek() == '/') { - read(); - result.setProtocol(new Protocol(protocolToken, protocolToken, null, -1, readToken())); - } else { - result.setProtocol(new Protocol("HTTP", "HTTP", null, -1, protocolToken)); - } + if (peek() == '/') { + read(); + result.setProtocol(new Protocol(protocolToken, protocolToken, null, -1, readToken())); + } else { + result.setProtocol(new Protocol("HTTP", "HTTP", null, -1, protocolToken)); + } - // Move to the next text - if (skipSpaces()) { - result.setName(readRawText()); + // Move to the next text + if (skipSpaces()) { + result.setName(readRawText()); - // Move to the next text - if (skipSpaces()) { - result.setComment(readComment()); - } - } + // Move to the next text + if (skipSpaces()) { + result.setComment(readComment()); + } + } - return result; - } + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/RecipientInfoWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/RecipientInfoWriter.java index 7a4b030de4..ddf05e7e3c 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/RecipientInfoWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/RecipientInfoWriter.java @@ -1,58 +1,56 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.RecipientInfo; - import java.util.List; +import org.restlet.data.RecipientInfo; /** * Recipient info header writer. - * + * * @author Jerome Louvel */ public class RecipientInfoWriter extends HeaderWriter { - /** - * Creates a via header from the given recipients info. - * - * @param recipientsInfo The recipients info. - * @return Returns the Via header. - */ - public static String write(List recipientsInfo) { - return new RecipientInfoWriter().append(recipientsInfo).toString(); - } - - @Override - public RecipientInfoWriter append(RecipientInfo recipientInfo) { - if (recipientInfo.getProtocol() != null) { - appendToken(recipientInfo.getProtocol().getName()); - append('/'); - appendToken(recipientInfo.getProtocol().getVersion()); - appendSpace(); - - if (recipientInfo.getName() != null) { - append(recipientInfo.getName()); - - if (recipientInfo.getComment() != null) { - appendSpace(); - appendComment(recipientInfo.getComment()); - } - } else { - throw new IllegalArgumentException("The name (host or pseudonym) of a recipient can't be null"); - } - } else { - throw new IllegalArgumentException("The protocol of a recipient can't be null"); - } - - return this; - } - + /** + * Creates a via header from the given list of recipient info. + * + * @param recipientsInfo The list of recipient info. + * @return Returns the Via header. + */ + public static String write(List recipientsInfo) { + return new RecipientInfoWriter().append(recipientsInfo).toString(); + } + + @Override + public RecipientInfoWriter append(RecipientInfo recipientInfo) { + if (recipientInfo.getProtocol() != null) { + appendToken(recipientInfo.getProtocol().getName()); + append('/'); + appendToken(recipientInfo.getProtocol().getVersion()); + appendSpace(); + + if (recipientInfo.getName() != null) { + append(recipientInfo.getName()); + + if (recipientInfo.getComment() != null) { + appendSpace(); + appendComment(recipientInfo.getComment()); + } + } else { + throw new IllegalArgumentException( + "The name (host or pseudonym) of a recipient can't be null"); + } + } else { + throw new IllegalArgumentException("The protocol of a recipient can't be null"); + } + + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/StringReader.java b/org.restlet/src/main/java/org/restlet/engine/header/StringReader.java index 8334b5a7f3..580d64283c 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/StringReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/StringReader.java @@ -1,48 +1,45 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Header; - import java.io.IOException; import java.util.Collection; +import org.restlet.data.Header; /** * String header reader. - * + * * @author Manuel Boillod */ public class StringReader extends HeaderReader { - /** - * Adds values to the given collection. - * - * @param header The header to read. - * @param collection The collection to update. - */ - public static void addValues(Header header, Collection collection) { - new StringReader(header.getValue()).addValues(collection); - } - - /** - * Constructor. - * - * @param header The header to read. - */ - public StringReader(String header) { - super(header); - } - - @Override - public String readValue() throws IOException { - return readToken(); - } - + /** + * Adds values to the given collection. + * + * @param header The header to read. + * @param collection The collection to update. + */ + public static void addValues(Header header, Collection collection) { + new StringReader(header.getValue()).addValues(collection); + } + + /** + * Constructor. + * + * @param header The header to read. + */ + public StringReader(String header) { + super(header); + } + + @Override + public String readValue() throws IOException { + return readToken(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/StringWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/StringWriter.java index cefecb7e30..d28192b14f 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/StringWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/StringWriter.java @@ -1,36 +1,34 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; import java.util.Set; /** * String header writer. - * + * * @author Manuel Boillod */ public class StringWriter extends HeaderWriter { - /** - * Writes a set of values with a comma separator. - * - * @param values The set of values. - * @return The formatted set of values. - */ - public static String write(Set values) { - return new StringWriter().append(values).toString(); - } - - @Override - public StringWriter append(String value) { - return (StringWriter) appendToken(value); - } + /** + * Writes a set of values with a comma separator. + * + * @param values The set of values. + * @return The formatted set of values. + */ + public static String write(Set values) { + return new StringWriter().append(values).toString(); + } + @Override + public StringWriter append(String value) { + return (StringWriter) appendToken(value); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/TagReader.java b/org.restlet/src/main/java/org/restlet/engine/header/TagReader.java index c517827e52..66a07bb8d8 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/TagReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/TagReader.java @@ -1,49 +1,46 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Header; -import org.restlet.data.Tag; - import java.io.IOException; import java.util.Collection; +import org.restlet.data.Header; +import org.restlet.data.Tag; /** * Tag header reader. - * + * * @author Thierry Boileau */ public class TagReader extends HeaderReader { - /** - * Adds values to the given collection. - * - * @param header The header to read. - * @param collection The collection to update. - */ - public static void addValues(Header header, Collection collection) { - new TagReader(header.getValue()).addValues(collection); - } - - /** - * Constructor. - * - * @param header The header to read. - */ - public TagReader(String header) { - super(header); - } - - @Override - public Tag readValue() throws IOException { - return Tag.parse(readRawValue()); - } - + /** + * Adds values to the given collection. + * + * @param header The header to read. + * @param collection The collection to update. + */ + public static void addValues(Header header, Collection collection) { + new TagReader(header.getValue()).addValues(collection); + } + + /** + * Constructor. + * + * @param header The header to read. + */ + public TagReader(String header) { + super(header); + } + + @Override + public Tag readValue() throws IOException { + return Tag.parse(readRawValue()); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/TagWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/TagWriter.java index 5ede818710..1ef854c4da 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/TagWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/TagWriter.java @@ -1,48 +1,45 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.restlet.data.Tag; - import java.util.List; +import org.restlet.data.Tag; /** * Tag header writer. - * + * * @author Jerome Louvel */ public class TagWriter extends HeaderWriter { - /** - * Writes a list of tags. - * - * @param tags The tags to write. - * @return This writer. - */ - public static String write(List tags) { - return new TagWriter().append(tags).toString(); - } - - /** - * Writes a tag. - * - * @param tag The tag to write. - * @return This writer. - */ - public static String write(Tag tag) { - return tag.format(); - } - - @Override - public HeaderWriter append(Tag tag) { - return append(write(tag)); - } - + /** + * Writes a list of tags. + * + * @param tags The tags to write. + * @return This writer. + */ + public static String write(List tags) { + return new TagWriter().append(tags).toString(); + } + + /** + * Writes a tag. + * + * @param tag The tag to write. + * @return This writer. + */ + public static String write(Tag tag) { + return tag.format(); + } + + @Override + public HeaderWriter append(Tag tag) { + return append(write(tag)); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/TokenReader.java b/org.restlet/src/main/java/org/restlet/engine/header/TokenReader.java index e472204a41..89aeac23ed 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/TokenReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/TokenReader.java @@ -1,35 +1,33 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; import java.io.IOException; /** * Token header reader. - * + * * @author Jerome Louvel */ public class TokenReader extends HeaderReader { - /** - * Constructor. - * - * @param header The header to read. - */ - public TokenReader(String header) { - super(header); - } - - @Override - public String readValue() throws IOException { - return readToken(); - } + /** + * Constructor. + * + * @param header The header to read. + */ + public TokenReader(String header) { + super(header); + } + @Override + public String readValue() throws IOException { + return readToken(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/WarningReader.java b/org.restlet/src/main/java/org/restlet/engine/header/WarningReader.java index 98df497f6f..300d0b8544 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/WarningReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/WarningReader.java @@ -1,76 +1,73 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.io.IOException; +import java.util.Collection; import org.restlet.data.Header; import org.restlet.data.Status; import org.restlet.data.Warning; import org.restlet.engine.util.DateUtils; -import java.io.IOException; -import java.util.Collection; - /** * Warning header reader. - * + * * @author Thierry Boileau */ public class WarningReader extends HeaderReader { - /** - * Adds values to the given collection. - * - * @param header The header to read. - * @param collection The collection to update. - */ - public static void addValues(Header header, Collection collection) { - new WarningReader(header.getValue()).addValues(collection); - } - - /** - * Constructor. - * - * @param header The header to read. - */ - public WarningReader(String header) { - super(header); - } + /** + * Adds values to the given collection. + * + * @param header The header to read. + * @param collection The collection to update. + */ + public static void addValues(Header header, Collection collection) { + new WarningReader(header.getValue()).addValues(collection); + } - @Override - public Warning readValue() throws IOException { - Warning result = new Warning(); + /** + * Constructor. + * + * @param header The header to read. + */ + public WarningReader(String header) { + super(header); + } - String code = readToken(); - skipSpaces(); - String agent = readRawText(); - skipSpaces(); - String text = readQuotedString(); - // The date is not mandatory - skipSpaces(); - String date = null; - if (peek() != -1) { - date = readQuotedString(); - } + @Override + public Warning readValue() throws IOException { + Warning result = new Warning(); - if ((code == null) || (agent == null) || (text == null)) { - throw new IOException("Warning header malformed."); - } + String code = readToken(); + skipSpaces(); + String agent = readRawText(); + skipSpaces(); + String text = readQuotedString(); + // The date is not mandatory + skipSpaces(); + String date = null; + if (peek() != -1) { + date = readQuotedString(); + } - result.setStatus(Status.valueOf(Integer.parseInt(code))); - result.setAgent(agent); - result.setText(text); - if (date != null) { - result.setDate(DateUtils.parse(date)); - } + if ((code == null) || (agent == null) || (text == null)) { + throw new IOException("Warning header malformed."); + } - return result; - } + result.setStatus(Status.valueOf(Integer.parseInt(code))); + result.setAgent(agent); + result.setText(text); + if (date != null) { + result.setDate(DateUtils.parse(date)); + } + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/WarningWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/WarningWriter.java index 3d7f3348b8..1d1fb4d130 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/WarningWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/WarningWriter.java @@ -1,64 +1,62 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import java.util.List; import org.restlet.data.Warning; import org.restlet.engine.util.DateUtils; -import java.util.List; - /** * Warning header writer. - * + * * @author Thierry Boileau */ public class WarningWriter extends HeaderWriter { - /** - * Writes a warning. - * - * @param warnings The list of warnings to format. - * @return The formatted warning. - */ - public static String write(List warnings) { - return new WarningWriter().append(warnings).toString(); - } - - @Override - public WarningWriter append(Warning warning) { - String agent = warning.getAgent(); - String text = warning.getText(); - - if (warning.getStatus() == null) { - throw new IllegalArgumentException("Can't write warning. Invalid status code detected"); - } - - if ((agent == null) || (agent.isEmpty())) { - throw new IllegalArgumentException("Can't write warning. Invalid agent detected"); - } - - if ((text == null) || (text.isEmpty())) { - throw new IllegalArgumentException("Can't write warning. Invalid text detected"); - } - - append(Integer.toString(warning.getStatus().getCode())); - append(" "); - append(agent); - append(" "); - appendQuotedString(text); - - if (warning.getDate() != null) { - appendQuotedString(DateUtils.format(warning.getDate())); - } - - return this; - } - + /** + * Writes a warning. + * + * @param warnings The list of warnings to format. + * @return The formatted warning. + */ + public static String write(List warnings) { + return new WarningWriter().append(warnings).toString(); + } + + @Override + public WarningWriter append(Warning warning) { + + if (warning.getStatus() == null) { + throw new IllegalArgumentException("Can't write warning. Invalid status code detected"); + } + + String agent = warning.getAgent(); + String text = warning.getText(); + + if ((agent == null) || (agent.isEmpty())) { + throw new IllegalArgumentException("Can't write warning. Invalid agent detected"); + } + + if ((text == null) || (text.isEmpty())) { + throw new IllegalArgumentException("Can't write warning. Invalid text detected"); + } + + append(Integer.toString(warning.getStatus().getCode())); + append(" "); + append(agent); + append(" "); + appendQuotedString(text); + + if (warning.getDate() != null) { + appendQuotedString(DateUtils.format(warning.getDate())); + } + + return this; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/package-info.java b/org.restlet/src/main/java/org/restlet/engine/header/package-info.java new file mode 100644 index 0000000000..a540c4e548 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/header/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports HTTP header parsing and formatting. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.header; diff --git a/org.restlet/src/main/java/org/restlet/engine/header/package.html b/org.restlet/src/main/java/org/restlet/engine/header/package.html deleted file mode 100644 index 73ec80f11b..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/header/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Supports HTTP header parsing and formatting. -

-@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/internal/Activator.java b/org.restlet/src/main/java/org/restlet/engine/internal/Activator.java index 5803404e74..638a1c293f 100644 --- a/org.restlet/src/main/java/org/restlet/engine/internal/Activator.java +++ b/org.restlet/src/main/java/org/restlet/engine/internal/Activator.java @@ -1,132 +1,144 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.internal; -import org.osgi.framework.*; +import java.net.URL; +import java.util.List; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; import org.restlet.Client; import org.restlet.Server; import org.restlet.engine.Engine; -import java.net.URL; -import java.util.List; - /** - * OSGi activator. It registers the NRE into the Restlet API and also introspect - * the bundles to find connector or authentication helpers. - * + * OSGi activator. It registers the NRE into the Restlet API and also introspects the bundles to + * find connector or authentication helpers. + * * @author Jerome Louvel */ public class Activator implements BundleActivator { - /** - * Registers the helpers for a given bundle. - * - * @param bundle The bundle to inspect. - * @param helpers The helpers list to update. - * @param constructorClass The class to use as constructor parameter. - * @param descriptorPath The descriptor file path. - */ - private void registerHelper(Bundle bundle, List helpers, Class constructorClass, String descriptorPath) { - // Discover server helpers - URL configUrl = bundle.getEntry(descriptorPath); - - if (configUrl == null) { - configUrl = bundle.getEntry("/src/" + descriptorPath); - } - - if (configUrl != null) { - registerHelper(bundle, helpers, constructorClass, configUrl); - } - } - - /** - * Registers the helpers for a given bundle. - * - * @param bundle The bundle to inspect. - * @param helpers The helpers list to update. - * @param constructorClass The class to use as constructor parameter. - * @param descriptorUrl The descriptor URL to inspect. - */ - private void registerHelper(final Bundle bundle, List helpers, Class constructorClass, URL descriptorUrl) { - Engine.getInstance().registerHelpers(new ClassLoader() { - @Override - public Class loadClass(String name) throws ClassNotFoundException { - return bundle.loadClass(name); - } - }, descriptorUrl, helpers, constructorClass); - } - - /** - * Registers the helpers for a given bundle. - * - * @param bundle The bundle to inspect. - */ - private void registerHelpers(Bundle bundle) { - // Register server helpers - registerHelper(bundle, Engine.getInstance().getRegisteredServers(), Server.class, - Engine.DESCRIPTOR_SERVER_PATH); - - // Register client helpers - registerHelper(bundle, Engine.getInstance().getRegisteredClients(), Client.class, - Engine.DESCRIPTOR_CLIENT_PATH); - - // Register authentication helpers - registerHelper(bundle, Engine.getInstance().getRegisteredAuthenticators(), null, - Engine.DESCRIPTOR_AUTHENTICATOR_PATH); - - // Register converter helpers - registerHelper(bundle, Engine.getInstance().getRegisteredConverters(), null, Engine.DESCRIPTOR_CONVERTER_PATH); - } - - /** - * Starts the OSGi bundle by registering the engine with the bundle of the - * Restlet API. - * - * @param context The bundle context. - */ - public void start(BundleContext context) throws Exception { - org.restlet.engine.Engine.register(false); - - // Discover helpers in installed bundles and start - // the bundle if necessary - for (final Bundle bundle : context.getBundles()) { - registerHelpers(bundle); - } - - // Listen to installed bundles - context.addBundleListener(new BundleListener() { - public void bundleChanged(BundleEvent event) { - switch (event.getType()) { - case BundleEvent.INSTALLED: - registerHelpers(event.getBundle()); - break; - - case BundleEvent.UNINSTALLED: - break; - } - } - }); - - Engine.getInstance().registerDefaultConnectors(); - Engine.getInstance().registerDefaultAuthentications(); - Engine.getInstance().registerDefaultConverters(); - } - - /** - * Stops the OSGi bundle by unregistering the engine with the bundle of the - * Restlet API. - * - * @param context The bundle context. - */ - public void stop(BundleContext context) throws Exception { - org.restlet.engine.Engine.clear(); - } - + /** + * Registers the helpers for a given bundle. + * + * @param bundle The bundle to inspect. + * @param helpers The helpers list to update. + * @param constructorClass The class to use as a constructor parameter. + * @param descriptorPath The descriptor file path. + */ + private void registerHelper( + Bundle bundle, List helpers, Class constructorClass, String descriptorPath) { + // Discover server helpers + URL configUrl = bundle.getEntry(descriptorPath); + + if (configUrl == null) { + configUrl = bundle.getEntry("/src/" + descriptorPath); + } + + if (configUrl != null) { + registerHelper(bundle, helpers, constructorClass, configUrl); + } + } + + /** + * Registers the helpers for a given bundle. + * + * @param bundle The bundle to inspect. + * @param helpers The helpers list to update. + * @param constructorClass The class to use as a constructor parameter. + * @param descriptorUrl The descriptor URL to inspect. + */ + private void registerHelper( + final Bundle bundle, List helpers, Class constructorClass, URL descriptorUrl) { + Engine.getInstance() + .registerHelpers( + new ClassLoader() { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return bundle.loadClass(name); + } + }, + descriptorUrl, + helpers, + constructorClass); + } + + /** + * Registers the helpers for a given bundle. + * + * @param bundle The bundle to inspect. + */ + private void registerHelpers(Bundle bundle) { + // Register server helpers + registerHelper( + bundle, + Engine.getInstance().getRegisteredServers(), + Server.class, + Engine.DESCRIPTOR_SERVER_PATH); + + // Register client helpers + registerHelper( + bundle, + Engine.getInstance().getRegisteredClients(), + Client.class, + Engine.DESCRIPTOR_CLIENT_PATH); + + // Register authentication helpers + registerHelper( + bundle, + Engine.getInstance().getRegisteredAuthenticators(), + null, + Engine.DESCRIPTOR_AUTHENTICATOR_PATH); + + // Register converter helpers + registerHelper( + bundle, + Engine.getInstance().getRegisteredConverters(), + null, + Engine.DESCRIPTOR_CONVERTER_PATH); + } + + /** + * Starts the OSGi bundle by registering the engine with the bundle of the Restlet API. + * + * @param context The bundle context. + */ + public void start(BundleContext context) throws Exception { + org.restlet.engine.Engine.register(false); + + // Discover helpers in installed bundles and start + // the bundle if necessary + for (final Bundle bundle : context.getBundles()) { + registerHelpers(bundle); + } + + // Listen to installed bundles + context.addBundleListener( + event -> { + if (event.getType() == BundleEvent.INSTALLED) { + registerHelpers(event.getBundle()); + } + }); + + Engine.getInstance().registerDefaultConnectors(); + Engine.getInstance().registerDefaultAuthentications(); + Engine.getInstance().registerDefaultConverters(); + } + + /** + * Stops the OSGi bundle by unregistering the engine with the bundle of the Restlet API. + * + * @param context The bundle context. + */ + public void stop(BundleContext context) throws Exception { + org.restlet.engine.Engine.clear(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/io/IoUtils.java b/org.restlet/src/main/java/org/restlet/engine/io/IoUtils.java index bceb55f382..ba38db873f 100644 --- a/org.restlet/src/main/java/org/restlet/engine/io/IoUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/io/IoUtils.java @@ -1,587 +1,595 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; +import static org.restlet.data.Range.isBytesRange; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Objects; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.data.CharacterSet; import org.restlet.data.Range; import org.restlet.engine.Engine; import org.restlet.representation.Representation; -import java.io.*; -import java.util.logging.Level; - -import static org.restlet.data.Range.isBytesRange; - /** * IO manipulation utilities. - * + * * @author Thierry Boileau */ public class IoUtils { - /** - * The size to use when instantiating buffered items such as instances of the - * {@link BufferedReader} class. It looks for the System property - * "org.restlet.engine.io.bufferSize" and if not defined, uses the "8192" - * default value. - */ - public static final int BUFFER_SIZE = getProperty("org.restlet.engine.io.bufferSize", 8192); - - /** Support for byte to hexa conversions. */ - private static final char[] HEXDIGITS = "0123456789ABCDEF".toCharArray(); - - /** - * The number of milliseconds after which IO operation will time out. It looks - * for the System property "org.restlet.engine.io.timeoutMs" and if not defined, - * uses the "60000" default value. - */ - public final static int TIMEOUT_MS = getProperty("org.restlet.engine.io.timeoutMs", 60000); - - /** - * Copies an input stream to an output stream. When the reading is done, the - * input stream is closed. - * - * @param inputStream The input stream. - * @param outputStream The output stream. - * @throws IOException - */ - public static void copy(InputStream inputStream, java.io.OutputStream outputStream) throws IOException { - if (inputStream != null) { - if (outputStream != null) { - inputStream.transferTo(outputStream); - outputStream.flush(); - inputStream.close(); - } else { - Context.getCurrentLogger().log(Level.FINE, - "Unable to copy input to output stream. Output stream is null."); - } - } else { - Context.getCurrentLogger().log(Level.FINE, "Unable to copy input to output stream. Input stream is null."); - } - } - - /** - * Copies an input stream to a random access file. When the reading is done, the - * input stream is closed. - * - * @param inputStream The input stream. - * @param randomAccessFile The random access file. - * @throws IOException - */ - public static void copy(InputStream inputStream, java.io.RandomAccessFile randomAccessFile) throws IOException { - int bytesRead; - byte[] buffer = new byte[2048]; - - while ((bytesRead = inputStream.read(buffer)) > 0) { - randomAccessFile.write(buffer, 0, bytesRead); - } - - inputStream.close(); - } - - /** - * Copies characters from a reader to a writer. When the reading is done, the - * reader is closed. - * - * @param reader The reader. - * @param writer The writer. - * @throws IOException - */ - public static void copy(Reader reader, java.io.Writer writer) throws IOException { - reader.transferTo(writer); - writer.flush(); - reader.close(); - } - - /** - * Deletes an individual file or an empty directory. - * - * @param file The individual file or directory to delete. - * @return True if the deletion was successful. - */ - public static boolean delete(java.io.File file) { - return IoUtils.delete(file, false); - } - - /** - * Deletes an individual file or a directory. A recursive deletion can be forced - * as well. Under Windows operating systems, the garbage collector will be - * invoked once before attempting to delete in order to prevent locking issues. - * - * @param file The individual file or directory to delete. - * @param recursive Indicates if directory with content should be deleted - * recursively as well. - * @return True if the deletion was successful or if the file or directory - * didn't exist. - */ - public static boolean delete(java.io.File file, boolean recursive) { - String osName = System.getProperty("os.name").toLowerCase(); - return IoUtils.delete(file, recursive, osName.startsWith("windows")); - } - - /** - * Deletes an individual file or a directory. A recursive deletion can be forced - * as well. The garbage collector can be run once before attempting to delete, - * to workaround lock issues under Windows operating systems. - * - * @param file The individual file or directory to delete. - * @param recursive Indicates if directory with content should be deleted - * recursively as well. - * @param garbageCollect Indicates if the garbage collector should be run. - * @return True if the deletion was successful or if the file or directory - * didn't exist. - */ - public static boolean delete(java.io.File file, boolean recursive, boolean garbageCollect) { - boolean result = true; - boolean runGC = garbageCollect; - - if (file.exists()) { - if (file.isDirectory()) { - java.io.File[] entries = file.listFiles(); - - // Check if the directory is empty - if (entries.length > 0) { - if (recursive) { - for (int i = 0; result && (i < entries.length); i++) { - if (runGC) { - System.gc(); - runGC = false; - } - - result = delete(entries[i], true, false); - } - } else { - result = false; - } - } - } - - if (runGC) { - System.gc(); - runGC = false; - } - - result = result && file.delete(); - } - - return result; - } - - /** - * Exhaust the content of the representation by reading it and silently - * discarding anything read. - * - * @param input The input stream to exhaust. - * @return The number of bytes consumed or -1 if unknown. - */ - public static long exhaust(InputStream input) throws IOException { - long result = -1L; - - if (input != null) { - byte[] buf = new byte[2048]; - int read = input.read(buf); - result = (read == -1) ? -1 : 0; - - while (read != -1) { - result += read; - read = input.read(buf); - } - } - - return result; - } - - /** - * Returns the size effectively available. This returns the same value as - * {@link Representation#getSize()} if no range is defined, otherwise it returns - * the size of the range using {@link Range#getSize()}. - * - * @param representation The representation to evaluate. - * @return The available size. - */ - public static long getAvailableSize(Representation representation) { - Range range = representation.getRange(); - if (range == null || !isBytesRange(range)) { - return representation.getSize(); - } else if (range.getSize() != Range.SIZE_MAX) { - if (representation.hasKnownSize()) { - return Math.min(range.getIndex() + range.getSize(), representation.getSize()) - range.getIndex(); - } else { - return Representation.UNKNOWN_SIZE; - } - } else if (representation.hasKnownSize()) { - if (range.getIndex() != Range.INDEX_LAST) { - return representation.getSize() - range.getIndex(); - } - - return representation.getSize(); - } - - return Representation.UNKNOWN_SIZE; - } - - private static int getProperty(String name, int defaultValue) { - int result = defaultValue; - - try { - result = Integer.parseInt(System.getProperty(name)); - } catch (NumberFormatException nfe) { - result = defaultValue; - } - - return result; - } - - /** - * Returns a reader from an input stream and a character set. - * - * @param stream The input stream. - * @param characterSet The character set. May be null. - * @return The equivalent reader. - * @throws UnsupportedEncodingException if a character set is given, but not - * supported - */ - public static Reader getReader(InputStream stream, CharacterSet characterSet) throws UnsupportedEncodingException { - if (characterSet != null) { - return new InputStreamReader(stream, characterSet.getName()); - } - - return new InputStreamReader(stream); - } - - /** - * Returns a reader from a writer representation.Internally, it uses a writer - * thread and a pipe stream. - * - * @param representation The representation to read from. - * @return The character reader. - * @throws IOException - */ - public static Reader getReader(final org.restlet.representation.WriterRepresentation representation) - throws IOException { - Reader result = null; - final java.io.PipedWriter pipedWriter = new java.io.PipedWriter(); - - java.io.PipedReader pipedReader = new java.io.PipedReader(pipedWriter); - - // Gets a thread that will handle the task of continuously - // writing the representation into the input side of the pipe - Runnable task = new org.restlet.engine.util.ContextualRunnable() { - public void run() { - try { - representation.write(pipedWriter); - pipedWriter.flush(); - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.WARNING, "Error while writing to the piped reader.", ioe); - } finally { - try { - pipedWriter.close(); - } catch (IOException ioe2) { - Context.getCurrentLogger().log(Level.WARNING, "Error while closing the pipe.", ioe2); - } - } - } - }; - - org.restlet.Context context = org.restlet.Context.getCurrent(); - - if (context != null && context.getExecutorService() != null) { - context.getExecutorService().execute(task); - } else { - Engine.createThreadWithLocalVariables(task, "Restlet-IoUtils").start(); - } - - result = pipedReader; - - return result; - - } - - /** - * Returns an output stream based on a given writer. - * - * @param writer The writer. - * @param characterSet The character set used to write on the output stream. - * @return the output stream of the writer - */ - public static java.io.OutputStream getStream(java.io.Writer writer, CharacterSet characterSet) { - return new WriterOutputStream(writer, characterSet); - } - - /** - * Returns an input stream based on a given character reader. - * - * @param reader The character reader. - * @param characterSet The stream character set. - * @return An input stream based on a given character reader. - */ - public static InputStream getStream(Reader reader, CharacterSet characterSet) { - InputStream result = null; - - try { - result = new ReaderInputStream(reader, characterSet); - } catch (IOException e) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to create the reader input stream", e); - } - - return result; - } - - /** - * Returns an input stream based on the given representation's content and its - * write(OutputStream) method. Internally, it uses a writer thread and a pipe - * stream. - * - * @param representation the representation to get the - * {@link java.io.OutputStream} from. - * @return A stream with the representation's content. - */ - public static InputStream getStream(final Representation representation) { - InputStream result = null; - - if (representation == null) { - return null; - } - - final PipeStream pipe = new PipeStream(); - final java.io.OutputStream os = pipe.getOutputStream(); - - // Creates a thread that will handle the task of continuously - // writing the representation into the input side of the pipe - Runnable task = new org.restlet.engine.util.ContextualRunnable() { - public void run() { - try { - representation.write(os); - os.flush(); - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.WARNING, "Error while writing to the piped input stream.", - ioe); - } finally { - try { - os.close(); - } catch (IOException ioe2) { - Context.getCurrentLogger().log(Level.WARNING, "Error while closing the pipe.", ioe2); - } - } - } - }; - - org.restlet.Context context = org.restlet.Context.getCurrent(); - - if (context != null && context.getExecutorService() != null) { - context.getExecutorService().execute(task); - } else { - Engine.createThreadWithLocalVariables(task, "Restlet-IoUtils").start(); - } - - result = pipe.getInputStream(); - - return result; - } - - /** - * Converts the representation to a string value. Be careful when using this - * method as the conversion of large content to a string fully stored in memory - * can result in OutOfMemoryErrors being thrown. - * - * @param representation The representation to convert. - * @return The representation as a string value. - */ - public static String getText(Representation representation) throws IOException { - String result = null; - - if (representation.isAvailable()) { - if (representation.getSize() == 0) { - result = ""; - } else { - java.io.StringWriter sw = new java.io.StringWriter(); - representation.write(sw); - sw.flush(); - result = sw.toString(); - } - } - - return result; - } - - /** - * Returns a writer to the given output stream, using the given character set - * for encoding to bytes. - * - * @param outputStream The target output stream. - * @param characterSet The character set for encoding. - * @return The wrapping writer. - */ - public static Writer getWriter(OutputStream outputStream, CharacterSet characterSet) { - Writer result = null; - - if (characterSet != null) { - result = new OutputStreamWriter(outputStream, characterSet.toCharset()); - } else { - // Use the default HTTP character set - result = new OutputStreamWriter(outputStream, CharacterSet.ISO_8859_1.toCharset()); - } - - return result; - } - - /** - * Converts a char array into a byte array using the default character set. - * - * @param chars The source characters. - * @return The result bytes. - */ - public static byte[] toByteArray(char[] chars) { - return IoUtils.toByteArray(chars, java.nio.charset.Charset.defaultCharset().name()); - } - - /** - * Converts a char array into a byte array using the provided character set. - * - * @param chars The source characters. - * @param charsetName The character set to use. - * @return The result bytes. - */ - public static byte[] toByteArray(char[] chars, String charsetName) { - java.nio.CharBuffer cb = java.nio.CharBuffer.wrap(chars); - java.nio.ByteBuffer bb = java.nio.charset.Charset.forName(charsetName).encode(cb); - byte[] r = new byte[bb.remaining()]; - bb.get(r); - return r; - } - - /** - * Converts a byte array into a character array using the default character set. - * - * @param bytes The source bytes. - * @return The result characters. - */ - public static char[] toCharArray(byte[] bytes) { - return IoUtils.toCharArray(bytes, java.nio.charset.Charset.defaultCharset().name()); - } - - /** - * Converts a byte array into a character array using the default character set. - * - * @param bytes The source bytes. - * @param charsetName The character set to use. - * @return The result characters. - */ - public static char[] toCharArray(byte[] bytes, String charsetName) { - java.nio.ByteBuffer bb = java.nio.ByteBuffer.wrap(bytes); - java.nio.CharBuffer cb = java.nio.charset.Charset.forName(charsetName).decode(bb); - char[] r = new char[cb.remaining()]; - cb.get(r); - return r; - } - - /** - * Converts a byte array into an hexadecimal string. - * - * @param byteArray The byte array to convert. - * @return The hexadecimal string. - */ - public static String toHexString(byte[] byteArray) { - final char[] hexChars = new char[2 * byteArray.length]; - int i = 0; - - for (final byte b : byteArray) { - hexChars[i++] = HEXDIGITS[(b >> 4) & 0xF]; - hexChars[i++] = HEXDIGITS[b & 0xF]; - } - - return new String(hexChars); - } - - /** - * Converts an input stream to a string.
- * As this method uses the InputStreamReader class, the default character set is - * used for decoding the input stream. - * - * @see InputStreamReader - * @see IoUtils#toString(InputStream, CharacterSet) - * @param inputStream The input stream. - * @return The converted string. - */ - public static String toString(InputStream inputStream) { - return IoUtils.toString(inputStream, null); - } - - /** - * Converts an input stream to a string using the specified character set for - * decoding the input stream. Once read, the input stream is closed. - * - * @see InputStreamReader - * @param inputStream The input stream. - * @param characterSet The character set - * @return The converted string. - */ - public static String toString(InputStream inputStream, CharacterSet characterSet) { - String result = null; - - if (inputStream != null) { - try { - if (characterSet != null) { - result = IoUtils.toString(new InputStreamReader(inputStream, characterSet.getName())); - } else { - result = IoUtils.toString(new InputStreamReader(inputStream)); - } - - inputStream.close(); - } catch (Exception e) { - // Returns an empty string - } - } - - return result; - } - - /** - * Converts a reader to a string. - * - * @see InputStreamReader - * - * @param reader The characters reader. - * @return The converted string. - */ - public static String toString(Reader reader) { - String result = null; - - if (reader != null) { - try { - StringBuilder sb = new StringBuilder(); - BufferedReader br = (reader instanceof BufferedReader) ? (BufferedReader) reader - : new BufferedReader(reader, BUFFER_SIZE); - char[] buffer = new char[2048]; - int charsRead = br.read(buffer); - - while (charsRead != -1) { - sb.append(buffer, 0, charsRead); - charsRead = br.read(buffer); - } - - br.close(); - result = sb.toString(); - } catch (Exception e) { - // Returns an empty string - } - } - - return result; - } - - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e. it isn't instantiable and extensible. - */ - private IoUtils() { - } + /** + * The size to use when instantiating buffered items such as instances of the {@link + * BufferedReader} class. It looks for the System property "org.restlet.engine.io.bufferSize" + * and if not defined, uses the "8192" default value. + */ + public static final int BUFFER_SIZE = getProperty("org.restlet.engine.io.bufferSize", 8192); + + /** Support for byte to hexa conversions. */ + private static final char[] HEXDIGITS = "0123456789ABCDEF".toCharArray(); + + /** + * The number of milliseconds after which IO operation will time out. It looks for the System + * property "org.restlet.engine.io.timeoutMs" and if not defined, uses the "60000" default + * value. + */ + public static final int TIMEOUT_MS = getProperty("org.restlet.engine.io.timeoutMs", 60000); + + /** + * Copies an input stream to an output stream. When the reading is done, the input stream is + * closed. + * + * @param inputStream The input stream. + * @param outputStream The output stream. + * @throws IOException + */ + public static void copy(InputStream inputStream, java.io.OutputStream outputStream) + throws IOException { + if (inputStream != null) { + if (outputStream != null) { + inputStream.transferTo(outputStream); + outputStream.flush(); + inputStream.close(); + } else { + Context.getCurrentLogger() + .log( + Level.FINE, + "Unable to copy input to output stream. Output stream is null."); + } + } else { + Context.getCurrentLogger() + .log( + Level.FINE, + "Unable to copy input to output stream. Input stream is null."); + } + } + + /** + * Copies an input stream to a random access file. When the reading is done, the input stream is + * closed. + * + * @param inputStream The input stream. + * @param randomAccessFile The random access file. + * @throws IOException + */ + public static void copy(InputStream inputStream, java.io.RandomAccessFile randomAccessFile) + throws IOException { + int bytesRead; + byte[] buffer = new byte[2048]; + + while ((bytesRead = inputStream.read(buffer)) > 0) { + randomAccessFile.write(buffer, 0, bytesRead); + } + + inputStream.close(); + } + + /** + * Copies characters from a reader to a writer. When the reading is done, the reader is closed. + * + * @param reader The reader. + * @param writer The writer. + * @throws IOException + */ + public static void copy(Reader reader, java.io.Writer writer) throws IOException { + reader.transferTo(writer); + writer.flush(); + reader.close(); + } + + /** + * Deletes an individual file or an empty directory. + * + * @param file The individual file or directory to delete. + * @return True if the deletion was successful. + */ + public static boolean delete(java.io.File file) { + return IoUtils.delete(file, false); + } + + /** + * Deletes an individual file or a directory. A recursive deletion can be forced as well. Under + * Windows operating systems, the garbage collector will be invoked once before attempting to + * delete to prevent locking issues. + * + * @param file The individual file or directory to delete. + * @param recursive Indicates if a directory with content should be deleted recursively as well. + * @return True if the deletion was successful or if the file or directory didn't exist. + */ + public static boolean delete(java.io.File file, boolean recursive) { + String osName = System.getProperty("os.name").toLowerCase(); + return IoUtils.delete(file, recursive, osName.startsWith("windows")); + } + + /** + * Deletes an individual file or a directory. A recursive deletion can be forced as well. The + * garbage collector can be run once before attempting to delete, to workaround lock issues + * under Windows operating systems. + * + * @param file The individual file or directory to delete. + * @param recursive Indicates if a directory with content should be deleted recursively as well. + * @param garbageCollect Indicates if the garbage collector should be run. + * @return True if the deletion was successful or if the file or directory didn't exist. + */ + public static boolean delete(java.io.File file, boolean recursive, boolean garbageCollect) { + if (!file.exists()) { + return true; + } + + boolean result = true; + boolean runGC = garbageCollect; + + if (file.isDirectory()) { + java.io.File[] entries = file.listFiles(); + + // Check if the directory is empty + if (entries != null && entries.length > 0) { + if (recursive) { + for (int i = 0; result && (i < entries.length); i++) { + if (runGC) { + System.gc(); + runGC = false; + } + + result = delete(entries[i], true, false); + } + } else { + result = false; + } + } + } + + if (runGC) { + System.gc(); + } + + result = result && file.delete(); + + return result; + } + + /** + * Exhaust the content of the representation by reading it and silently discarding anything + * read. + * + * @param input The input stream to exhaust. + * @return The number of bytes consumed or -1 if unknown. + */ + public static long exhaust(InputStream input) throws IOException { + long result = -1L; + + if (input != null) { + byte[] buf = new byte[2048]; + int read = input.read(buf); + result = (read == -1) ? -1 : 0; + + while (read != -1) { + result += read; + read = input.read(buf); + } + } + + return result; + } + + /** + * Returns the size effectively available. This returns the same value as {@link + * Representation#getSize()} if no range is defined, otherwise it returns the size of the range + * using {@link Range#getSize()}. + * + * @param representation The representation to evaluate. + * @return The available size. + */ + public static long getAvailableSize(Representation representation) { + Range range = representation.getRange(); + if (range == null || !isBytesRange(range)) { + return representation.getSize(); + } else if (range.getSize() != Range.SIZE_MAX) { + if (representation.hasKnownSize()) { + return Math.min(range.getIndex() + range.getSize(), representation.getSize()) + - range.getIndex(); + } else { + return Representation.UNKNOWN_SIZE; + } + } else if (representation.hasKnownSize()) { + if (range.getIndex() != Range.INDEX_LAST) { + return representation.getSize() - range.getIndex(); + } + + return representation.getSize(); + } + + return Representation.UNKNOWN_SIZE; + } + + private static int getProperty(String name, int defaultValue) { + int result; + + try { + result = Integer.parseInt(System.getProperty(name)); + } catch (NumberFormatException nfe) { + result = defaultValue; + } + + return result; + } + + /** + * Returns a reader from an input stream and a character set. + * + * @param stream The input stream. + * @param characterSet The character set. Maybe null. + * @return The equivalent reader. + * @throws UnsupportedEncodingException if a character set is given but not supported + */ + public static Reader getReader(InputStream stream, CharacterSet characterSet) + throws UnsupportedEncodingException { + if (characterSet != null) { + return new InputStreamReader(stream, characterSet.getName()); + } + + return new InputStreamReader(stream); + } + + /** + * Returns a reader from a writer representation. Internally, it uses a writer thread and a pipe + * stream. + * + * @param representation The representation to read from. + * @return The character reader. + * @throws IOException + */ + public static Reader getReader( + final org.restlet.representation.WriterRepresentation representation) + throws IOException { + Reader result = null; + final java.io.PipedWriter pipedWriter = new java.io.PipedWriter(); + + java.io.PipedReader pipedReader = new java.io.PipedReader(pipedWriter); + + // Gets a thread that will handle the task of continuously + // writing the representation into the input side of the pipe + Runnable task = + new org.restlet.engine.util.ContextualRunnable() { + public void run() { + try { + representation.write(pipedWriter); + pipedWriter.flush(); + } catch (IOException ioe) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Error while writing to the piped reader.", + ioe); + } finally { + try { + pipedWriter.close(); + } catch (IOException ioe2) { + Context.getCurrentLogger() + .log(Level.WARNING, "Error while closing the pipe.", ioe2); + } + } + } + }; + + org.restlet.Context context = org.restlet.Context.getCurrent(); + + if (context != null && context.getExecutorService() != null) { + context.getExecutorService().execute(task); + } else { + Engine.createThreadWithLocalVariables(task, "Restlet-IoUtils").start(); + } + + result = pipedReader; + + return result; + } + + /** + * Returns an output stream based on a given writer. + * + * @param writer The writer. + * @param characterSet The character set used to write on the output stream. + * @return the output stream of the writer + */ + public static java.io.OutputStream getStream(java.io.Writer writer, CharacterSet characterSet) { + return new WriterOutputStream(writer, characterSet); + } + + /** + * Returns an input stream based on a given character reader. + * + * @param reader The character reader. + * @param characterSet The stream character set. + * @return An input stream based on a given character reader. + */ + public static InputStream getStream(Reader reader, CharacterSet characterSet) { + return new ReaderInputStream(reader, characterSet); + } + + /** + * Returns an input stream based on the given representation's content and its + * write(OutputStream) method. Internally, it uses a writer thread and a pipe stream. + * + * @param representation the representation to get the {@link java.io.OutputStream} from. + * @return A stream with the representation's content. + */ + public static InputStream getStream(final Representation representation) { + InputStream result = null; + + if (representation == null) { + return null; + } + + final PipeStream pipe = new PipeStream(); + final java.io.OutputStream os = pipe.getOutputStream(); + + // Creates a thread that will handle the task of continuously + // writing the representation into the input side of the pipe + Runnable task = + new org.restlet.engine.util.ContextualRunnable() { + public void run() { + try { + representation.write(os); + os.flush(); + } catch (IOException ioe) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Error while writing to the piped input stream.", + ioe); + } finally { + try { + os.close(); + } catch (IOException ioe2) { + Context.getCurrentLogger() + .log(Level.WARNING, "Error while closing the pipe.", ioe2); + } + } + } + }; + + org.restlet.Context context = org.restlet.Context.getCurrent(); + + if (context != null && context.getExecutorService() != null) { + context.getExecutorService().execute(task); + } else { + Engine.createThreadWithLocalVariables(task, "Restlet-IoUtils").start(); + } + + result = pipe.getInputStream(); + + return result; + } + + /** + * Converts the representation to a string value. Be careful when using this method as the + * conversion of large content to a string fully stored in memory can result in + * OutOfMemoryErrors being thrown. + * + * @param representation The representation to convert. + * @return The representation as a string value. + */ + public static String getText(Representation representation) throws IOException { + String result = null; + + if (representation.isAvailable()) { + if (representation.getSize() == 0) { + result = ""; + } else { + java.io.StringWriter sw = new java.io.StringWriter(); + representation.write(sw); + sw.flush(); + result = sw.toString(); + } + } + + return result; + } + + /** + * Returns a writer to the given output stream, using the given character set for encoding to + * bytes. + * + * @param outputStream The target output stream. + * @param characterSet The character set for encoding. + * @return The wrapping writer. + */ + public static Writer getWriter(OutputStream outputStream, CharacterSet characterSet) { + // Use the default HTTP character set if necessary + final Charset charset = + Objects.requireNonNullElse(characterSet, CharacterSet.ISO_8859_1).toCharset(); + return new OutputStreamWriter(outputStream, charset); + } + + /** + * Converts a char array into a byte array using the default character set. + * + * @param chars The source characters. + * @return The result bytes. + */ + public static byte[] toByteArray(char[] chars) { + return IoUtils.toByteArray(chars, java.nio.charset.Charset.defaultCharset().name()); + } + + /** + * Converts a char array into a byte array using the provided character set. + * + * @param chars The source characters. + * @param charsetName The character set to use. + * @return The result bytes. + */ + public static byte[] toByteArray(char[] chars, String charsetName) { + java.nio.CharBuffer cb = java.nio.CharBuffer.wrap(chars); + java.nio.ByteBuffer bb = java.nio.charset.Charset.forName(charsetName).encode(cb); + byte[] r = new byte[bb.remaining()]; + bb.get(r); + return r; + } + + /** + * Converts a byte array into a character array using the default character set. + * + * @param bytes The source bytes. + * @return The result characters. + */ + public static char[] toCharArray(byte[] bytes) { + return IoUtils.toCharArray(bytes, java.nio.charset.Charset.defaultCharset().name()); + } + + /** + * Converts a byte array into a character array using the default character set. + * + * @param bytes The source bytes. + * @param charsetName The character set to use. + * @return The result characters. + */ + public static char[] toCharArray(byte[] bytes, String charsetName) { + java.nio.ByteBuffer bb = java.nio.ByteBuffer.wrap(bytes); + java.nio.CharBuffer cb = java.nio.charset.Charset.forName(charsetName).decode(bb); + char[] r = new char[cb.remaining()]; + cb.get(r); + return r; + } + + /** + * Converts a byte array into a hexadecimal string. + * + * @param byteArray The byte array to convert. + * @return The hexadecimal string. + */ + public static String toHexString(byte[] byteArray) { + final char[] hexChars = new char[2 * byteArray.length]; + int i = 0; + + for (final byte b : byteArray) { + hexChars[i++] = HEXDIGITS[(b >> 4) & 0xF]; + hexChars[i++] = HEXDIGITS[b & 0xF]; + } + + return new String(hexChars); + } + + /** + * Converts an input stream to a string.
+ * As this method uses the InputStreamReader class, the default character set is used for + * decoding the input stream. + * + * @see InputStreamReader + * @see IoUtils#toString(InputStream, CharacterSet) + * @param inputStream The input stream. + * @return The converted string. + */ + public static String toString(InputStream inputStream) { + return IoUtils.toString(inputStream, null); + } + + /** + * Converts an input stream to a string using the specified character set for decoding the input + * stream. Once read, the input stream is closed. + * + * @see InputStreamReader + * @param inputStream The input stream. + * @param characterSet The character set + * @return The converted string. + */ + public static String toString(InputStream inputStream, CharacterSet characterSet) { + String result = null; + + if (inputStream != null) { + try { + if (characterSet != null) { + result = + IoUtils.toString( + new InputStreamReader(inputStream, characterSet.getName())); + } else { + result = IoUtils.toString(new InputStreamReader(inputStream)); + } + + inputStream.close(); + } catch (Exception e) { + // Returns an empty string + } + } + + return result; + } + + /** + * Converts a reader to a string. + * + * @see InputStreamReader + * @param reader The characters' reader. + * @return The converted string. + */ + public static String toString(Reader reader) { + String result = null; + + if (reader != null) { + try { + StringBuilder sb = new StringBuilder(); + BufferedReader br = + (reader instanceof BufferedReader bufferedReader) + ? bufferedReader + : new BufferedReader(reader, BUFFER_SIZE); + char[] buffer = new char[2048]; + int charsRead = br.read(buffer); + + while (charsRead != -1) { + sb.append(buffer, 0, charsRead); + charsRead = br.read(buffer); + } + + br.close(); + result = sb.toString(); + } catch (Exception e) { + // Returns an empty string + } + } + + return result; + } + + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private IoUtils() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/io/PipeStream.java b/org.restlet/src/main/java/org/restlet/engine/io/PipeStream.java index d81151b765..2b9f57c944 100644 --- a/org.restlet/src/main/java/org/restlet/engine/io/PipeStream.java +++ b/org.restlet/src/main/java/org/restlet/engine/io/PipeStream.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; import java.io.IOException; @@ -17,84 +16,86 @@ import java.util.concurrent.TimeUnit; /** - * Pipe stream that pipes output streams into input streams. Implementation - * based on a shared synchronized queue. - * + * Pipe stream that pipes output streams into input streams. Implementation based on a shared + * synchronized queue. + * * @author Jerome Louvel */ public class PipeStream { - /** The queue timeout. */ - private static final long QUEUE_TIMEOUT = 5; - - /** The supporting synchronized queue. */ - private final BlockingQueue queue; + /** The queue timeout. */ + private static final long QUEUE_TIMEOUT = 5; - /** Constructor. */ - public PipeStream() { - this.queue = new ArrayBlockingQueue(1024); - } + /** The supporting synchronized queue. */ + private final BlockingQueue queue; - /** - * Returns a new input stream that can read from the pipe. - * - * @return A new input stream that can read from the pipe. - */ - public InputStream getInputStream() { - return new InputStream() { - private boolean endReached = false; + /** Constructor. */ + public PipeStream() { + this.queue = new ArrayBlockingQueue<>(1024); + } - @Override - public int read() throws IOException { - try { - if (this.endReached) { - return -1; - } + /** + * Returns a new input stream that can read from the pipe. + * + * @return A new input stream that can read from the pipe. + */ + public InputStream getInputStream() { + return new InputStream() { + private boolean endReached = false; - final Integer value = queue.poll(QUEUE_TIMEOUT, TimeUnit.SECONDS); + @Override + public int read() throws IOException { + try { + if (this.endReached) { + return -1; + } - if (value == null) { - throw new IOException("Timeout while reading from the queue-based input stream"); - } else { - this.endReached = (value == -1); - return value; - } - } catch (InterruptedException ie) { - throw new IOException("Interruption occurred while writing in the queue"); - } - } - }; - } + final Integer value = queue.poll(QUEUE_TIMEOUT, TimeUnit.SECONDS); - /** - * Returns a new output stream that can write into the pipe. - * - * @return A new output stream that can write into the pipe. - */ - public OutputStream getOutputStream() { - return new OutputStream() { - @Override - public void write(int b) throws IOException { - try { - if (!queue.offer(b & 0xff, QUEUE_TIMEOUT, TimeUnit.SECONDS)) { - throw new IOException("Timeout while writing to the queue-based output stream"); - } - } catch (InterruptedException ie) { - throw new IOException("Interruption occurred while writing in the queue"); - } - } + if (value == null) { + throw new IOException( + "Timeout while reading from the queue-based input stream"); + } else { + this.endReached = (value == -1); + return value; + } + } catch (InterruptedException ie) { + throw new IOException("Interruption occurred while writing in the queue"); + } + } + }; + } - @Override - public void close() throws IOException { - try { - if (!queue.offer(-1, QUEUE_TIMEOUT, TimeUnit.SECONDS)) { - throw new IOException("Timeout while writing to the queue-based output stream"); - } - } catch (InterruptedException ie) { - throw new IOException("Interruption occurred while writing in the queue"); - } - } - }; - } + /** + * Returns a new output stream that can write into the pipe. + * + * @return A new output stream that can write into the pipe. + */ + public OutputStream getOutputStream() { + return new OutputStream() { + @Override + public void write(int b) throws IOException { + try { + if (!queue.offer(b & 0xff, QUEUE_TIMEOUT, TimeUnit.SECONDS)) { + throw new IOException( + "Timeout while writing to the queue-based output stream"); + } + } catch (InterruptedException ie) { + throw new IOException("Interruption occurred while writing in the queue"); + } + } + @Override + public void close() throws IOException { + try { + if (!queue.offer(-1, QUEUE_TIMEOUT, TimeUnit.SECONDS)) { + throw new IOException( + "Timeout while writing to the queue-based output stream"); + } + } catch (InterruptedException ie) { + throw new IOException("Interruption occurred while writing in the queue"); + } + } + }; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/io/RangeInputStream.java b/org.restlet/src/main/java/org/restlet/engine/io/RangeInputStream.java index 591ac21a01..8cb60b7944 100644 --- a/org.restlet/src/main/java/org/restlet/engine/io/RangeInputStream.java +++ b/org.restlet/src/main/java/org/restlet/engine/io/RangeInputStream.java @@ -1,167 +1,171 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; -import org.restlet.data.Range; -import org.restlet.representation.Representation; - import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import org.restlet.data.Range; +import org.restlet.representation.Representation; /** * Filters an input stream to expose only a given range. - * + * * @author Jerome Louvel */ public class RangeInputStream extends FilterInputStream { - /** The current position. */ - private volatile long position; - - /** The range to satisfy. */ - private volatile Range range; - - /** The total size of the source stream. */ - private volatile long totalSize; - - /** The start index inside the source stream. */ - private final long startIndex; - - /** The end index inside the source stream. */ - private final long endIndex; - - /** The range size available. */ - private volatile int availableSize; - - /** - * Constructs a stream exposing only a range of a given source stream. - * - * @param in The source input stream. - * @param totalSize The total size of the source stream. - * @param range The range to satisfy. - */ - public RangeInputStream(InputStream in, long totalSize, Range range) { - super(in); - this.range = range; - this.position = 0; - this.totalSize = totalSize; - this.availableSize = (int) range.getSize(); - - if (totalSize == Representation.UNKNOWN_SIZE) { - if (range.getIndex() == Range.INDEX_LAST) { - if (range.getSize() == Range.SIZE_MAX) { - // Read the whole stream - this.startIndex = -1; - this.endIndex = -1; - } else { - throw new IllegalArgumentException("Can't determine the start and end index."); - } - } else { - if (range.getSize() == Range.SIZE_MAX) { - this.startIndex = range.getIndex(); - this.endIndex = -1; - } else { - this.startIndex = range.getIndex(); - this.endIndex = range.getIndex() + range.getSize() - 1; - } - } - } else { - if (range.getIndex() == Range.INDEX_LAST) { - if (range.getSize() == Range.SIZE_MAX) { - this.startIndex = -1; - this.endIndex = -1; - } else { - this.startIndex = totalSize - range.getSize(); - this.endIndex = -1; - } - } else { - if (range.getSize() == Range.SIZE_MAX) { - this.startIndex = range.getIndex(); - this.endIndex = -1; - } else { - this.startIndex = range.getIndex(); - this.endIndex = range.getIndex() + range.getSize() - 1; - } - } - } - } - - @Override - public int available() throws IOException { - return this.availableSize; - } - - @Override - public synchronized void mark(int readlimit) { - if (range.getIndex() == Range.INDEX_LAST) { - super.mark(readlimit + (int) (totalSize - range.getSize())); - } else { - super.mark(readlimit + (int) range.getIndex()); - } - } - - @Override - public int read() throws IOException { - int result = super.read(); - - while ((result != -1) && !this.range.isIncluded(position++, totalSize)) { - result = super.read(); - } - - if ((result != -1) && (this.availableSize > 0)) { - this.availableSize--; - } - - return result; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - // Reach the start index. - while (!(position >= startIndex)) { - long skipped = skip(startIndex - position); - - if (skipped <= 0) { - throw new IOException("Cannot skip ahead in FilterInputStream"); - } - - position += skipped; - } - - int result = -1; - - if (endIndex != -1) { - // Read up until the end index - if (position > endIndex) { - // The end index is reached. - result = -1; - } else { - // Take care to read the right number of bytes according to the - // end index and the buffer size. - result = super.read(b, off, ((position + len) > endIndex) ? (int) (endIndex - position + 1) : len); - } - } else { - // Read normally up until the end of the stream. - result = super.read(b, off, len); - } - - if (result > 0) { - // Move the cursor. - position += result; - } - - if ((result != -1) && (this.availableSize > 0)) { - this.availableSize -= result; - } - - return result; - } + /** The current position. */ + private volatile long position; + + /** The range to satisfy. */ + private volatile Range range; + + /** The total size of the source stream. */ + private volatile long totalSize; + + /** The start index inside the source stream. */ + private final long startIndex; + + /** The end index inside the source stream. */ + private final long endIndex; + + /** The range size available. */ + private volatile int availableSize; + + /** + * Constructs a stream exposing only a range of a given source stream. + * + * @param in The source input stream. + * @param totalSize The total size of the source stream. + * @param range The range to satisfy. + */ + public RangeInputStream(InputStream in, long totalSize, Range range) { + super(in); + this.range = range; + this.position = 0; + this.totalSize = totalSize; + this.availableSize = (int) range.getSize(); + + if (totalSize == Representation.UNKNOWN_SIZE) { + if (range.getIndex() == Range.INDEX_LAST) { + if (range.getSize() == Range.SIZE_MAX) { + // Read the whole stream + this.startIndex = -1; + this.endIndex = -1; + } else { + throw new IllegalArgumentException("Can't determine the start and end index."); + } + } else { + if (range.getSize() == Range.SIZE_MAX) { + this.startIndex = range.getIndex(); + this.endIndex = -1; + } else { + this.startIndex = range.getIndex(); + this.endIndex = range.getIndex() + range.getSize() - 1; + } + } + } else { + if (range.getIndex() == Range.INDEX_LAST) { + if (range.getSize() == Range.SIZE_MAX) { + this.startIndex = -1; + this.endIndex = -1; + } else { + this.startIndex = totalSize - range.getSize(); + this.endIndex = -1; + } + } else { + if (range.getSize() == Range.SIZE_MAX) { + this.startIndex = range.getIndex(); + this.endIndex = -1; + } else { + this.startIndex = range.getIndex(); + this.endIndex = range.getIndex() + range.getSize() - 1; + } + } + } + } + + @Override + public int available() throws IOException { + return this.availableSize; + } + + @Override + public synchronized void mark(int readLimit) { + if (range.getIndex() == Range.INDEX_LAST) { + super.mark(readLimit + (int) (totalSize - range.getSize())); + } else { + super.mark(readLimit + (int) range.getIndex()); + } + } + + @Override + public int read() throws IOException { + int result = super.read(); + + while ((result != -1) && !this.range.isIncluded(position++, totalSize)) { + result = super.read(); + } + + if ((result != -1) && (this.availableSize > 0)) { + this.availableSize--; + } + + return result; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + // Reach the start index. + while (position < startIndex) { + long skipped = skip(startIndex - position); + + if (skipped <= 0) { + throw new IOException("Cannot skip ahead in FilterInputStream"); + } + + position += skipped; + } + + int result; + + if (endIndex != -1) { + // Read up until the end index + if (position > endIndex) { + // The end index is reached. + result = -1; + } else { + // Take care to read the right number of bytes according to the + // end index and the buffer size. + result = + super.read( + b, + off, + ((position + len) > endIndex) + ? (int) (endIndex - position + 1) + : len); + } + } else { + // Read normally up until the end of the stream. + result = super.read(b, off, len); + } + + if (result > 0) { + // Move the cursor. + position += result; + } + + if ((result != -1) && (this.availableSize > 0)) { + this.availableSize -= result; + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/io/ReaderInputStream.java b/org.restlet/src/main/java/org/restlet/engine/io/ReaderInputStream.java index 1a24b53e79..4dca0ff066 100644 --- a/org.restlet/src/main/java/org/restlet/engine/io/ReaderInputStream.java +++ b/org.restlet/src/main/java/org/restlet/engine/io/ReaderInputStream.java @@ -1,16 +1,13 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; -import org.restlet.data.CharacterSet; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -18,125 +15,128 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetEncoder; +import org.restlet.data.CharacterSet; /** - * Input stream based on a reader. The implementation relies on the NIO - * {@link CharsetEncoder} class. - * + * Input stream based on a reader. The implementation relies on the NIO {@link CharsetEncoder} + * class. + * * @author Jerome Louvel */ public class ReaderInputStream extends InputStream { - /** The NIO byte buffer. */ - private final ByteBuffer byteBuffer; - - /** The NIO character buffer. */ - private final CharBuffer charBuffer; - - /** The character set encoder. */ - private final CharsetEncoder charsetEncoder; - - /** Indicates if the end of the wrapped reader has been reached. */ - private volatile boolean endReached; - - /** The wrapped reader. */ - private final BufferedReader reader; - - /** - * Constructor. Uses the {@link CharacterSet#ISO_8859_1} character set by - * default. - * - * @param reader The reader to wrap as an input stream. - * @throws IOException - */ - public ReaderInputStream(Reader reader) throws IOException { - this(reader, CharacterSet.ISO_8859_1); - } - - /** - * Constructor. - * - * @param reader The reader to wrap as an input stream. - * @param characterSet The character set to use for encoding. - * @throws IOException - */ - public ReaderInputStream(Reader reader, CharacterSet characterSet) throws IOException { - this.byteBuffer = ByteBuffer.allocate(1024); - this.byteBuffer.flip(); - this.charBuffer = CharBuffer.allocate(1024); - this.charBuffer.flip(); - this.charsetEncoder = (characterSet == null) ? CharacterSet.ISO_8859_1.toCharset().newEncoder() - : characterSet.toCharset().newEncoder(); - this.endReached = false; - this.reader = (reader instanceof BufferedReader) ? (BufferedReader) reader - : new BufferedReader(reader, IoUtils.BUFFER_SIZE); - } - - @Override - public int available() throws IOException { - return this.byteBuffer.hasRemaining() ? this.byteBuffer.remaining() : 0; - } - - /** - * Closes the wrapped reader. - */ - @Override - public void close() throws IOException { - this.reader.close(); - } - - @Override - public int read() throws IOException { - byte[] temp = new byte[1]; - return (read(temp) == -1) ? -1 : temp[0] & 0xFF; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - int result = 0; - boolean iterate = true; - - while (iterate) { - // Do we need to refill the byte buffer? - if (!this.byteBuffer.hasRemaining() && !this.endReached) { - // Do we need to refill the char buffer? - if (!this.charBuffer.hasRemaining()) { - this.charBuffer.clear(); - int read = this.reader.read(this.charBuffer); - this.charBuffer.flip(); - - if (read == -1) { - this.endReached = true; - } - } - - if ((len > 0) && this.charBuffer.hasRemaining()) { - // Refill the byte buffer - this.byteBuffer.clear(); - this.charsetEncoder.encode(this.charBuffer, this.byteBuffer, this.endReached); - this.byteBuffer.flip(); - } - } - - // Copies as much bytes as possible in the target array - int readLength = Math.min(len, this.byteBuffer.remaining()); - - if (readLength > 0) { - this.byteBuffer.get(b, off, readLength); - off += readLength; - len -= readLength; - result += readLength; - } - - // Can we iterate again? - iterate = (len > 0) - && (!this.endReached || this.byteBuffer.hasRemaining() || this.charBuffer.hasRemaining()); - } - - if (this.endReached && (result == 0)) { - result = -1; - } - - return result; - } + /** The NIO byte buffer. */ + private final ByteBuffer byteBuffer; + + /** The NIO character buffer. */ + private final CharBuffer charBuffer; + + /** The character set encoder. */ + private final CharsetEncoder charsetEncoder; + + /** Indicates if the end of the wrapped reader has been reached. */ + private volatile boolean endReached; + + /** The wrapped reader. */ + private final BufferedReader reader; + + /** + * Constructor. Uses the {@link CharacterSet#ISO_8859_1} character set by default. + * + * @param reader The reader to wrap as an input stream. + */ + public ReaderInputStream(Reader reader) { + this(reader, CharacterSet.ISO_8859_1); + } + + /** + * Constructor. + * + * @param reader The reader to wrap as an input stream. + * @param characterSet The character set to use for encoding. + */ + public ReaderInputStream(Reader reader, CharacterSet characterSet) { + this.byteBuffer = ByteBuffer.allocate(1024); + this.byteBuffer.flip(); + this.charBuffer = CharBuffer.allocate(1024); + this.charBuffer.flip(); + this.charsetEncoder = + (characterSet == null) + ? CharacterSet.ISO_8859_1.toCharset().newEncoder() + : characterSet.toCharset().newEncoder(); + this.endReached = false; + this.reader = + (reader instanceof BufferedReader bufferedReader) + ? bufferedReader + : new BufferedReader(reader, IoUtils.BUFFER_SIZE); + } + + @Override + public int available() throws IOException { + return this.byteBuffer.hasRemaining() ? this.byteBuffer.remaining() : 0; + } + + /** Closes the wrapped reader. */ + @Override + public void close() throws IOException { + this.reader.close(); + } + + @Override + public int read() throws IOException { + byte[] temp = new byte[1]; + return (read(temp) == -1) ? -1 : temp[0] & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int result = 0; + boolean iterate = true; + + while (iterate) { + // Do we need to refill the byte buffer? + if (!this.byteBuffer.hasRemaining() && !this.endReached) { + // Do we need to refill the char buffer? + if (!this.charBuffer.hasRemaining()) { + this.charBuffer.clear(); + int read = this.reader.read(this.charBuffer); + this.charBuffer.flip(); + + if (read == -1) { + this.endReached = true; + } + } + + if ((len > 0) && this.charBuffer.hasRemaining()) { + // Refill the byte buffer + this.byteBuffer.clear(); + this.charsetEncoder.encode(this.charBuffer, this.byteBuffer, this.endReached); + this.byteBuffer.flip(); + } + } + + // Copies as many bytes as possible in the target array + int readLength = Math.min(len, this.byteBuffer.remaining()); + + if (readLength > 0) { + this.byteBuffer.get(b, off, readLength); + off += readLength; + len -= readLength; + result += readLength; + } + + // Can we iterate again? + iterate = + (len > 0) + && (!this.endReached + || this.byteBuffer.hasRemaining() + || this.charBuffer.hasRemaining()); + } + + if (this.endReached && (result == 0)) { + result = -1; + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/io/UnclosableInputStream.java b/org.restlet/src/main/java/org/restlet/engine/io/UnclosableInputStream.java index ad7169338c..aff8437776 100644 --- a/org.restlet/src/main/java/org/restlet/engine/io/UnclosableInputStream.java +++ b/org.restlet/src/main/java/org/restlet/engine/io/UnclosableInputStream.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; import java.io.FilterInputStream; @@ -14,24 +13,23 @@ import java.io.InputStream; /** - * InputStream decorator to trap {@code close()} calls so that the underlying - * stream is not closed. - * + * InputStream decorator to trap {@code close()} calls so that the underlying stream is not closed. + * * @author Kevin Conaway - * */ public class UnclosableInputStream extends FilterInputStream { - /** - * Constructor. - * - * @param source The source input stream. - */ - public UnclosableInputStream(InputStream source) { - super(source); - } + /** + * Constructor. + * + * @param source The source input stream. + */ + public UnclosableInputStream(InputStream source) { + super(source); + } - @Override - public void close() throws IOException { - } + @Override + public void close() throws IOException { + // Do nothing + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/io/UnclosableOutputStream.java b/org.restlet/src/main/java/org/restlet/engine/io/UnclosableOutputStream.java index 096eab5729..5cd40e7f9e 100644 --- a/org.restlet/src/main/java/org/restlet/engine/io/UnclosableOutputStream.java +++ b/org.restlet/src/main/java/org/restlet/engine/io/UnclosableOutputStream.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; import java.io.FilterOutputStream; @@ -14,23 +13,23 @@ import java.io.OutputStream; /** - * OutputStream decorator to trap close() calls so that the decorated stream - * does not get closed. - * + * OutputStream decorator to trap close() calls so that the decorated stream does not get closed. + * * @author Kevin Conaway */ public class UnclosableOutputStream extends FilterOutputStream { - /** - * Constructor. - * - * @param source The decorated source stream. - */ - public UnclosableOutputStream(OutputStream source) { - super(source); - } + /** + * Constructor. + * + * @param source The decorated source stream. + */ + public UnclosableOutputStream(OutputStream source) { + super(source); + } - @Override - public void close() throws IOException { - } + @Override + public void close() throws IOException { + // Do nothing + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/io/WriterOutputStream.java b/org.restlet/src/main/java/org/restlet/engine/io/WriterOutputStream.java index b184bf6d49..95849efe88 100644 --- a/org.restlet/src/main/java/org/restlet/engine/io/WriterOutputStream.java +++ b/org.restlet/src/main/java/org/restlet/engine/io/WriterOutputStream.java @@ -1,16 +1,13 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; -import org.restlet.data.CharacterSet; - import java.io.IOException; import java.io.OutputStream; import java.io.Writer; @@ -18,52 +15,54 @@ import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import org.restlet.data.CharacterSet; /** * Output stream wrapping a character writer. - * + * * @author Kevin Conaway */ public class WriterOutputStream extends OutputStream { - /** The character set to use when parsing byte arrays. */ - private final Charset charSet; + /** The character set to use when parsing byte arrays. */ + private final Charset charSet; - /** The wrapped writer. */ - private final Writer writer; + /** The wrapped writer. */ + private final Writer writer; - /** - * Constructor. - * - * @param writer The wrapped writer. - * @param characterSet The character set. Use {@link CharacterSet#ISO_8859_1} by - * default if a null value is given. - */ - public WriterOutputStream(Writer writer, CharacterSet characterSet) { - this.writer = writer; - this.charSet = (characterSet == null) ? StandardCharsets.ISO_8859_1 : characterSet.toCharset(); - } + /** + * Constructor. + * + * @param writer The wrapped writer. + * @param characterSet The character set. Use {@link CharacterSet#ISO_8859_1} by default if a + * null value is given. + */ + public WriterOutputStream(Writer writer, CharacterSet characterSet) { + this.writer = writer; + this.charSet = + (characterSet == null) ? StandardCharsets.ISO_8859_1 : characterSet.toCharset(); + } - @Override - public void close() throws IOException { - super.close(); - this.writer.close(); - } + @Override + public void close() throws IOException { + super.close(); + this.writer.close(); + } - @Override - public void flush() throws IOException { - super.flush(); - this.writer.flush(); - } + @Override + public void flush() throws IOException { + super.flush(); + this.writer.flush(); + } - @Override - public void write(byte[] b, int off, int len) throws IOException { - CharBuffer charBuffer = this.charSet.decode(ByteBuffer.wrap(b, off, len)); - this.writer.write(charBuffer.toString()); - } + @Override + public void write(byte[] b, int off, int len) throws IOException { + CharBuffer charBuffer = this.charSet.decode(ByteBuffer.wrap(b, off, len)); + this.writer.write(charBuffer.toString()); + } - @Override - public void write(int b) throws IOException { - this.writer.write(b); - } + @Override + public void write(int b) throws IOException { + this.writer.write(b); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/io/package-info.java b/org.restlet/src/main/java/org/restlet/engine/io/package-info.java new file mode 100644 index 0000000000..51175c1c68 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/io/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports input and output work. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.io; diff --git a/org.restlet/src/main/java/org/restlet/engine/io/package.html b/org.restlet/src/main/java/org/restlet/engine/io/package.html deleted file mode 100644 index e5b3a0f7a9..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/io/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - -Supports input and output work. -

@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/local/ClapClientHelper.java b/org.restlet/src/main/java/org/restlet/engine/local/ClapClientHelper.java index 63f0754827..9faf52af62 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/ClapClientHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/ClapClientHelper.java @@ -1,169 +1,173 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; -import org.restlet.Client; -import org.restlet.Request; -import org.restlet.Response; -import org.restlet.data.*; -import org.restlet.representation.InputRepresentation; -import org.restlet.representation.Representation; -import org.restlet.service.MetadataService; - import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Date; import java.util.logging.Level; +import org.restlet.Client; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.LocalReference; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.data.Reference; +import org.restlet.data.Status; +import org.restlet.representation.InputRepresentation; +import org.restlet.representation.Representation; +import org.restlet.service.MetadataService; /** - * Connector to the resources accessed via class loaders. Note that if you use - * the class authority for your CLAP URIs, you can provide a custom classloader - * instead of the one of the connector. For this, your requests need to have a - * "org.restlet.clap.classLoader" attribute set with the instance of your - * classloader and use the {@link LocalReference#CLAP_CLASS} authority. - * + * Connector to the resources accessed via class loaders. Note that if you use the class authority + * for your CLAP URIs, you can provide a custom classloader instead of the one of the connector. For + * this, your requests need to have a "org.restlet.clap.classLoader" attribute set with the instance + * of your classloader and use the {@link LocalReference#CLAP_CLASS} authority. + * * @author Jerome Louvel */ public class ClapClientHelper extends LocalClientHelper { - /** - * Constructor. - * - * @param client The client to help. - */ - public ClapClientHelper(Client client) { - super(client); - getProtocols().add(Protocol.CLAP); - } - - /** - * Handles a call with a given class loader. - * - * @param request The request to handle. - * @param response The response to update. - */ - protected void handleClassLoader(Request request, Response response, ClassLoader classLoader) { - MetadataService metadataService = getMetadataService(); - - if (!request.getMethod().equals(Method.GET) && !request.getMethod().equals(Method.HEAD)) { - response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - response.getAllowedMethods().add(Method.GET); - response.getAllowedMethods().add(Method.HEAD); - - return; - } - - String path = request.getResourceRef().getPath(); - URL url = null; - Date modificationDate = null; - - // Prepare a classloader URI, removing the leading slash - if ((path != null) && path.startsWith("/")) { - path = path.substring(1); - } - - // Get the URL to the classloader 'resource' - if (classLoader != null) { - // As the path may be percent-encoded, it has to be - // percent-decoded. - url = classLoader.getResource(Reference.decode(path)); - } else { - getLogger().warning("Unable to get the resource. The selected classloader is null."); - } - - // The ClassLoader returns a directory listing in some cases. - // As this listing is partial, it is of little value in the context - // of the CLAP client, so we have to ignore them. - if (url != null) { - if (url.getProtocol().equals("file")) { - File file = new File(url.getFile()); - modificationDate = new Date(file.lastModified()); - - if (file.isDirectory()) { - url = null; - } - } - } - - if (url == null) { - response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); - return; - } - - try { - InputStream inputStream = url.openStream(); - - // check for empty input stream on jar directories - if (url.getProtocol().equals("jar")) { - if (inputStream.available() == 0) { - response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); - return; - } - } - - Representation output = new InputRepresentation(inputStream, metadataService.getDefaultMediaType()); - output.setLocationRef(request.getResourceRef()); - output.setModificationDate(modificationDate); - - // Update the expiration date - long timeToLive = getTimeToLive(); - - if (timeToLive == 0) { - output.setExpirationDate(null); - } else if (timeToLive > 0) { - output.setExpirationDate(new Date(System.currentTimeMillis() + (1000L * timeToLive))); - } - - // Update the metadata based on file extensions - String name = path.substring(path.lastIndexOf('/') + 1); - Entity.updateMetadata(name, output, true, getMetadataService()); - - // Update the response - response.setEntity(output); - response.setStatus(Status.SUCCESS_OK); - } catch (IOException ioe) { - getLogger().log(Level.WARNING, "Unable to open the representation's input stream", ioe); - response.setStatus(Status.SERVER_ERROR_INTERNAL); - } - } - - @Override - protected void handleLocal(Request request, Response response, String decodedPath) { - String scheme = request.getResourceRef().getScheme(); - - if (scheme.equalsIgnoreCase(Protocol.CLAP.getSchemeName())) { - LocalReference cr = new LocalReference(request.getResourceRef()); - ClassLoader classLoader = null; - - if ((cr.getClapAuthorityType() == LocalReference.CLAP_CLASS) - || (cr.getClapAuthorityType() == LocalReference.CLAP_DEFAULT)) { - // Sometimes, a specific class loader needs to be used, - // make sure that it can be provided as a request's attribute - Object classLoaderAttribute = request.getAttributes().get("org.restlet.clap.classLoader"); - - if (classLoaderAttribute != null) { - classLoader = (ClassLoader) classLoaderAttribute; - } else { - classLoader = getClass().getClassLoader(); - } - } else if (cr.getClapAuthorityType() == LocalReference.CLAP_SYSTEM) { - classLoader = ClassLoader.getSystemClassLoader(); - } else if (cr.getClapAuthorityType() == LocalReference.CLAP_THREAD) { - classLoader = Thread.currentThread().getContextClassLoader(); - } - - handleClassLoader(request, response, classLoader); - } else { - throw new IllegalArgumentException( - "Protocol \"" + scheme + "\" not supported by the connector. Only CLAP is supported."); - } - } + /** + * Constructor. + * + * @param client The client to help. + */ + public ClapClientHelper(Client client) { + super(client); + getProtocols().add(Protocol.CLAP); + } + + /** + * Handles a call with a given class loader. + * + * @param request The request to handle. + * @param response The response to update. + */ + protected void handleClassLoader(Request request, Response response, ClassLoader classLoader) { + MetadataService metadataService = getMetadataService(); + + if (!request.getMethod().equals(Method.GET) && !request.getMethod().equals(Method.HEAD)) { + response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + response.getAllowedMethods().add(Method.GET); + response.getAllowedMethods().add(Method.HEAD); + + return; + } + + String path = request.getResourceRef().getPath(); + URL url = null; + Date modificationDate = null; + + // Prepare a classloader URI, removing the leading slash + if ((path != null) && path.startsWith("/")) { + path = path.substring(1); + } + + // Get the URL to the classloader 'resource' + if (classLoader != null) { + // As the path may be percent-encoded, it has to be + // percent-decoded. + url = classLoader.getResource(Reference.decode(path)); + } else { + getLogger().warning("Unable to get the resource. The selected classloader is null."); + } + + // The ClassLoader returns a directory listing in some cases. + // As this listing is partial, it is of little value in the context + // of the CLAP client, so we have to ignore them. + if (url != null && url.getProtocol().equals("file")) { + File file = new File(url.getFile()); + modificationDate = new Date(file.lastModified()); + + if (file.isDirectory()) { + url = null; + } + } + + if (url == null) { + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return; + } + + try { + InputStream inputStream = url.openStream(); + + // check for empty input stream on jar directories + if (url.getProtocol().equals("jar") && inputStream.available() == 0) { + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + return; + } + + Representation output = + new InputRepresentation(inputStream, metadataService.getDefaultMediaType()); + output.setLocationRef(request.getResourceRef()); + output.setModificationDate(modificationDate); + + // Update the expiration date + long timeToLive = getTimeToLive(); + + if (timeToLive == 0) { + output.setExpirationDate(null); + } else if (timeToLive > 0) { + output.setExpirationDate( + new Date(System.currentTimeMillis() + (1000L * timeToLive))); + } + + // Update the metadata based on file extensions + if (path != null) { + String name = path.substring(path.lastIndexOf('/') + 1); + Entity.updateMetadata(name, output, true, getMetadataService()); + } + + // Update the response + response.setEntity(output); + response.setStatus(Status.SUCCESS_OK); + } catch (IOException ioe) { + getLogger().log(Level.WARNING, "Unable to open the representation's input stream", ioe); + response.setStatus(Status.SERVER_ERROR_INTERNAL); + } + } + + @Override + protected void handleLocal(Request request, Response response, String decodedPath) { + String scheme = request.getResourceRef().getScheme(); + + if (scheme.equalsIgnoreCase(Protocol.CLAP.getSchemeName())) { + LocalReference cr = new LocalReference(request.getResourceRef()); + ClassLoader classLoader = null; + + if ((cr.getClapAuthorityType() == LocalReference.CLAP_CLASS) + || (cr.getClapAuthorityType() == LocalReference.CLAP_DEFAULT)) { + // Sometimes, a specific class loader needs to be used, + // make sure that it can be provided as a request's attribute + Object classLoaderAttribute = + request.getAttributes().get("org.restlet.clap.classLoader"); + + if (classLoaderAttribute != null) { + classLoader = (ClassLoader) classLoaderAttribute; + } else { + classLoader = getClass().getClassLoader(); + } + } else if (cr.getClapAuthorityType() == LocalReference.CLAP_SYSTEM) { + classLoader = ClassLoader.getSystemClassLoader(); + } else if (cr.getClapAuthorityType() == LocalReference.CLAP_THREAD) { + classLoader = Thread.currentThread().getContextClassLoader(); + } + + handleClassLoader(request, response, classLoader); + } else { + throw new IllegalArgumentException( + "Protocol \"" + + scheme + + "\" not supported by the connector. Only CLAP is supported."); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/DirectoryServerResource.java b/org.restlet/src/main/java/org/restlet/engine/local/DirectoryServerResource.java index 35c2ce4a27..fcc08200f6 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/DirectoryServerResource.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/DirectoryServerResource.java @@ -1,18 +1,30 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.logging.Level; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; -import org.restlet.data.*; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.data.ReferenceList; +import org.restlet.data.Status; import org.restlet.engine.util.StringUtils; import org.restlet.representation.Representation; import org.restlet.representation.Variant; @@ -20,453 +32,460 @@ import org.restlet.resource.ResourceException; import org.restlet.resource.ServerResource; -import java.io.IOException; -import java.util.*; -import java.util.logging.Level; - /** - * Resource supported by a set of context representations (from file system, - * class loaders and webapp context). A content negotiation mechanism (similar - * to Apache HTTP server) is available. It is based on path extensions to detect - * variants (languages, media types or character sets). - * - * @see Apache - * mod_negotiation module + * Resource supported by a set of context representations (from file system, class loaders, and + * webapp context). A content negotiation mechanism (similar to Apache HTTP Server) is available. It + * is based on path extensions to detect variants (languages, media types, or character sets). + * * @author Jerome Louvel * @author Thierry Boileau + * @see Apache + * mod_negotiation module */ public class DirectoryServerResource extends ServerResource { - /** The list of variants for the GET method. */ - private volatile List variantsGet; - - /** - * The local base name of the resource. For example, "foo.en" and - * "foo.en-GB.html" return "foo". - */ - private volatile String baseName; - - /** The base variant. */ - private volatile Variant baseVariant; - - /** The parent directory client dispatcher. */ - private volatile Restlet directoryClientDispatcher; - - /** The parent directory handler. */ - private volatile Directory directory; - - /** If the resource is a directory, this contains its content. */ - private volatile ReferenceList directoryContent; - - /** - * If the resource is a directory, the non-trailing slash character leads to - * redirection. - */ - private volatile boolean directoryRedirection; - - /** Indicates if the target resource is a directory. */ - private volatile boolean directoryTarget; - - /** The context's directory URI (file, clap URI). */ - private volatile String directoryUri; - - /** If the resource is a file, this contains its content. */ - private volatile Representation fileContent; - - /** Indicates if the target resource is a file. */ - private volatile boolean fileTarget; - - /** Indicates if the target resource is a directory with an index. */ - private volatile boolean indexTarget; - - /** The original target URI, in case of extensions tunneling. */ - private volatile Reference originalRef; - - /** The prototype variant. */ - private volatile Variant protoVariant; - - /** The resource path relative to the directory URI. */ - private volatile String relativePart; - - /** The context's target URI (file, clap URI). */ - private volatile String targetUri; - - /** The unique representation of the target URI, if it exists. */ - private volatile Reference uniqueReference; - - @Override - public Representation delete() throws ResourceException { - if (!this.directory.isModifiable()) { - setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED, "The directory is not modifiable."); - return null; - } - - Request contextRequest = new Request(Method.DELETE, this.targetUri); - Response contextResponse = new Response(contextRequest); - - if (this.directoryTarget && !this.indexTarget) { - // let the client handle the directory's deletion - contextRequest.setResourceRef(this.targetUri); - dispatchRequest(contextRequest, contextResponse); - - setStatus(contextResponse.getStatus()); - return null; - } - - ReferenceList references = getVariantsReferences(); - - if (references.isEmpty()) { - // no representation found - setStatus(Status.CLIENT_ERROR_NOT_FOUND); - } else if (this.uniqueReference != null) { - // only one representation - contextRequest.setResourceRef(this.uniqueReference); - dispatchRequest(contextRequest, contextResponse); - setStatus(contextResponse.getStatus()); - } else { - // several variants found, but not the right one - setStatus(Status.CLIENT_ERROR_NOT_ACCEPTABLE, - "Unable to process properly the request. Several variants exist but none of them suits precisely. "); - } - - return null; - } - - /** - * This initialization method aims at answering the following questions:
- *

    - *
  • does this request target a directory?
  • - *
  • does this request target a directory, with an index file?
  • - *
  • should this request be redirected (target is a directory with no trailing - * "/")?
  • - *
  • does this request target a file?
  • - *
- *
- * The following constraints must be taken into account:
- *
    - *
  • the underlying helper may not support content negotiation and be able to - * return the list of possible variants of the target file (e.g. the CLAP - * helper).
  • - *
  • the underlying helper may not support directory listing
  • - *
  • the extensions tunneling cannot apply on a directory
  • - *
  • underlying helpers that do not support content negotiation cannot support - * extensions tunneling
  • - *
- */ - @Override - public void doInit() throws ResourceException { - this.directory = (Directory) getRequestAttributes().get("org.restlet.directory"); - this.directoryClientDispatcher = getDirectory().getContext() != null - ? getDirectory().getContext().getClientDispatcher() - : null; - if (getClientDispatcher() == null) { - getLogger().warning("No client dispatcher is available. Can't get the target URI: " + this.targetUri); - - throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "No client dispatcher is available."); - } - - // Update the member variables - setNegotiated(this.directory.isNegotiatingContent()); - this.relativePart = getReference().getRemainingPart(false, false); - this.originalRef = getOriginalRef(); - if (this.originalRef != null) { - // Restore the original URI in case the call has been tunneled. - if ((getApplication() != null) && getApplication().getTunnelService().isExtensionsTunnel()) { - Reference originalBaseRef = new Reference(this.originalRef); - originalBaseRef.setPath(getReference().getBaseRef().getPath()); - this.originalRef.setBaseRef(originalBaseRef); - this.relativePart = this.originalRef.getRemainingPart(false, false); - } - } - - if (this.relativePart.startsWith("/")) { - // We enforce the leading slash on the root URI - this.relativePart = this.relativePart.substring(1); - } - - // The target URI does not take into account the query and fragment - // parts of the resource. - this.targetUri = new Reference(directory.getRootRef().toString() + this.relativePart).toString(false, false); - preventUpperDirectoryAccess(); - - // Try to detect the presence of a directory - Response contextResponse = getRepresentation(this.targetUri); - - if (contextResponse.getEntity() != null) { - // As a convention, underlying client connectors return the - // directory listing with the media-type "MediaType.TEXT_URI_LIST" when handling - // directories - if (MediaType.TEXT_URI_LIST.equals(contextResponse.getEntity().getMediaType())) { - this.directoryTarget = true; - this.fileTarget = false; - this.directoryContent = tryToConvertAsReferenceList(contextResponse.getEntity()); - - if (!getReference().getPath().endsWith("/")) { - // All requests will be automatically redirected - this.directoryRedirection = true; - } - - if (!this.targetUri.endsWith("/")) { - this.targetUri += "/"; - this.relativePart += "/"; - } - - // Append the index name - if (!StringUtils.isNullOrEmpty(getDirectory().getIndexName())) { - this.directoryUri = this.targetUri; - this.baseName = getDirectory().getIndexName(); - this.targetUri = this.directoryUri + this.baseName; - this.indexTarget = true; - } else { - this.directoryUri = this.targetUri; - this.baseName = null; - } - } else { - // Allows underlying helpers that do not support "content negotiation" to return - // the targeted file. - // Sometimes we immediately reach the target entity, so we return it directly. - this.directoryTarget = false; - this.fileTarget = true; - this.fileContent = contextResponse.getEntity(); - } - } else { - this.directoryTarget = false; - this.fileTarget = false; - - // Let's try with the optional index, in case the underlying - // client connector does not handle directory listing. - if (this.targetUri.endsWith("/")) { - // In this case, the trailing "/" shows that the URI must point to a directory - if (!StringUtils.isNullOrEmpty(getDirectory().getIndexName())) { - this.directoryUri = this.targetUri; - this.directoryTarget = true; - - contextResponse = getRepresentation(this.directoryUri + getDirectory().getIndexName()); - if (contextResponse.getEntity() != null) { - this.baseName = getDirectory().getIndexName(); - this.targetUri = this.directoryUri + this.baseName; - this.directoryContent = new ReferenceList(); - this.directoryContent.add(new Reference(this.targetUri)); - this.indexTarget = true; - } - } - } else { - // Try to determine if this target URI with no trailing "/" is a directory, in - // order to force the - // redirection. - if (!StringUtils.isNullOrEmpty(getDirectory().getIndexName())) { - // Append the index name - contextResponse = getRepresentation(this.targetUri + "/" + getDirectory().getIndexName()); - if (contextResponse.getEntity() != null) { - this.directoryUri = this.targetUri + "/"; - this.baseName = getDirectory().getIndexName(); - this.targetUri = this.directoryUri + this.baseName; - this.directoryTarget = true; - this.directoryRedirection = true; - this.directoryContent = new ReferenceList(); - this.directoryContent.add(new Reference(this.targetUri)); - this.indexTarget = true; - } - } - } - } - - // In case the request does not target a directory and the file - // has not been found, try with the tunneled URI. - if (isNegotiated() && !this.directoryTarget && !this.fileTarget && this.originalRef != null) { - this.relativePart = getReference().getRemainingPart(); - - // The target URI does not take into account the query and fragment parts of the - // resource. - this.targetUri = new Reference(directory.getRootRef().toString() + this.relativePart).normalize() - .toString(false, false); - if (!this.targetUri.startsWith(directory.getRootRef().toString())) { - // Prevent the client from accessing resources in upper directories - this.targetUri = directory.getRootRef().toString(); - } - } - - if (!fileTarget || fileContent == null || !getRequest().getMethod().isSafe()) { - // Try to get the directory content, in case the request does not target a - // directory - if (!this.directoryTarget) { - int lastSlashIndex = this.targetUri.lastIndexOf('/'); - if (lastSlashIndex == -1) { - this.directoryUri = ""; - this.baseName = this.targetUri; - } else { - this.directoryUri = this.targetUri.substring(0, lastSlashIndex + 1); - this.baseName = this.targetUri.substring(lastSlashIndex + 1); - } - - contextResponse = getRepresentation(this.directoryUri); - if ((contextResponse.getEntity() != null) - && MediaType.TEXT_URI_LIST.equals(contextResponse.getEntity().getMediaType())) { - this.directoryContent = tryToConvertAsReferenceList(contextResponse.getEntity()); - } - } - - if (this.baseName != null) { - // Analyze extensions - this.baseVariant = new Variant(); - Entity.updateMetadata(this.baseName, this.baseVariant, true, getMetadataService()); - this.protoVariant = new Variant(); - Entity.updateMetadata(this.baseName, this.protoVariant, false, getMetadataService()); - - // Remove stored extensions from the base name - this.baseName = Entity.getBaseName(this.baseName, getMetadataService()); - } - - // Check if the resource exists or not. - List variants = getVariants(Method.GET); - if ((variants == null) || (variants.isEmpty())) { - setExisting(false); - } - } - - // Check if the resource is located in a sub directory. - if (isExisting() && !this.directory.isDeeplyAccessible()) { - // Count the number of "/" character. - int index = this.relativePart.indexOf("/"); - if (index != -1) { - index = this.relativePart.indexOf("/", index); - setExisting((index == -1)); - } - } - - // Log results - getLogger().fine("Converted target URI: " + this.targetUri); - getLogger().fine("Converted base name : " + this.baseName); - - } - - private ReferenceList tryToConvertAsReferenceList(Representation entity) throws ResourceException { - try { - return new ReferenceList(entity); - } catch (IOException e) { - throw new ResourceException(e); - } - } - - @Override - protected Representation get() throws ResourceException { - // Content negotiation has been disabled - // The variant that may need to meet the request conditions - - List variants = getVariants(Method.GET); - if ((variants == null) || (variants.isEmpty())) { - // Resource not found - throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND); - } - - if (variants.size() == 1) { - return (Representation) variants.get(0); - } - - ReferenceList variantRefs = new ReferenceList(); - - for (Variant variant : variants) { - if (variant.getLocationRef() != null) { - variantRefs.add(variant.getLocationRef()); - } else { - getLogger().warning( - "A resource with multiple variants should provide a location for each variant when content negotiation is turned off"); - } - } - - if (!variantRefs.isEmpty()) { - // Return the list of variants - setStatus(Status.REDIRECTION_MULTIPLE_CHOICES); - return variantRefs.getTextRepresentation(); - } - - throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND); - } - - /** - * Returns the local base name of the file. For example, "foo.en" and - * "foo.en-GB.html" return "foo". - * - * @return The local name of the file. - */ - public String getBaseName() { - return this.baseName; - } - - /** - * Returns a client dispatcher. - * - * @return A client dispatcher. - */ - protected Restlet getClientDispatcher() { - return directoryClientDispatcher; - } - - /** - * Returns the parent directory handler. - * - * @return The parent directory handler. - */ - public Directory getDirectory() { - return this.directory; - } - - /** - * If the resource is a directory, this returns its content. - * - * @return The directory content. - */ - protected ReferenceList getDirectoryContent() { - return directoryContent; - } - - /** - * Returns the context's directory URI (file, clap URI). - * - * @return The context's directory URI (file, clap URI). - */ - public String getDirectoryUri() { - return this.directoryUri; - } - - /** - * Returns a representation of the resource at the target URI. Leverages the - * client dispatcher of the parent directory's context. - * - * @param resourceUri The URI of the target resource. - * @return A response with the representation if success. - */ - private Response getRepresentation(String resourceUri) { - return dispatchRequest(new Request(Method.GET, resourceUri)); - } - - /** - * Returns a representation of the resource at the target URI. Leverages the - * client dispatcher of the parent directory's context. - * - * @param resourceUri The URI of the target resource. - * @param acceptedMediaType The accepted media type or null. - * @return A response with the representation if success. - */ - protected Response getRepresentation(String resourceUri, MediaType acceptedMediaType) { - Request request = new Request(Method.GET, resourceUri); - - if (acceptedMediaType != null) { - request.getClientInfo().accept(acceptedMediaType); - } - - return dispatchRequest(request); - } - - /** - * Allows sorting the list of representations set by the resource. - * - * @return A Comparator instance imposing a sort order of representations or - * null if no special order is wanted. - */ - private Comparator getRepresentationsComparator() { - // Sort the list of representations by their identifier. + /** The list of variants for the GET method. */ + private volatile List variantsGet; + + /** + * The local base name of the resource. For example, "foo.en" and "foo.en-GB.html" return "foo". + */ + private volatile String baseName; + + /** The base variant. */ + private volatile Variant baseVariant; + + /** The parent directory client dispatcher. */ + private volatile Restlet directoryClientDispatcher; + + /** The parent directory handler. */ + private volatile Directory directory; + + /** If the resource is a directory, this contains its content. */ + private volatile ReferenceList directoryContent; + + /** If the resource is a directory, the non-trailing slash character leads to redirection. */ + private volatile boolean directoryRedirection; + + /** Indicates if the target resource is a directory. */ + private volatile boolean directoryTarget; + + /** The context's directory URI (file, clap URI). */ + private volatile String directoryUri; + + /** If the resource is a file, this contains its content. */ + private volatile Representation fileContent; + + /** Indicates if the target resource is a file. */ + private volatile boolean fileTarget; + + /** Indicates if the target resource is a directory with an index. */ + private volatile boolean indexTarget; + + /** The original target URI, in case of extensions tunneling. */ + private volatile Reference originalRef; + + /** The prototype variant. */ + private volatile Variant protoVariant; + + /** The resource path relative to the directory URI. */ + private volatile String relativePart; + + /** The context's target URI (file, clap URI). */ + private volatile String targetUri; + + /** The unique representation of the target URI, if it exists. */ + private volatile Reference uniqueReference; + + @Override + public Representation delete() throws ResourceException { + if (!this.directory.isModifiable()) { + setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED, "The directory is not modifiable."); + } else { + Request contextRequest = new Request(Method.DELETE, this.targetUri); + Response contextResponse = new Response(contextRequest); + + if (this.directoryTarget && !this.indexTarget) { + // let the client handle the directory's deletion + contextRequest.setResourceRef(this.targetUri); + dispatchRequest(contextRequest, contextResponse); + + setStatus(contextResponse.getStatus()); + } else { + ReferenceList references = getVariantsReferences(); + + if (references.isEmpty()) { + // no representation found + setStatus(Status.CLIENT_ERROR_NOT_FOUND); + } else if (this.uniqueReference != null) { + // only one representation + contextRequest.setResourceRef(this.uniqueReference); + dispatchRequest(contextRequest, contextResponse); + setStatus(contextResponse.getStatus()); + } else { + // several variants found, but not the right one + setStatus( + Status.CLIENT_ERROR_NOT_ACCEPTABLE, + "Unable to process properly the request. Several variants exist but none of them suits precisely. "); + } + } + } + return null; + } + + /** + * This initialization method aims at answering the following questions:
+ * + *
    + *
  • does this request target a directory? + *
  • does this request target a directory, with an index file? + *
  • should this request be redirected (target is a directory with no trailing "/")? + *
  • does this request target a file? + *
+ * + *
+ * The following constraints must be taken into account:
+ * + *
    + *
  • the underlying helper may not support content negotiation and be able to return the + * list of possible variants of the target file (e.g., the CLAP helper). + *
  • the underlying helper may not support directory listing + *
  • the extensions tunneling cannot apply on a directory + *
  • underlying helpers that do not support content negotiation cannot support extensions + * tunneling + *
+ */ + @Override + public void doInit() throws ResourceException { + this.directory = (Directory) getRequestAttributes().get("org.restlet.directory"); + this.directoryClientDispatcher = + getDirectory().getContext() != null + ? getDirectory().getContext().getClientDispatcher() + : null; + if (getClientDispatcher() == null) { + getLogger() + .log( + Level.WARNING, + "No client dispatcher is available. Can''t get the target URI: {0}", + this.targetUri); + + throw new ResourceException( + Status.SERVER_ERROR_INTERNAL, "No client dispatcher is available."); + } + + // Update the member variables + setNegotiated(this.directory.isNegotiatingContent()); + this.relativePart = getReference().getRemainingPart(false, false); + this.originalRef = getOriginalRef(); + if (this.originalRef != null + && (getApplication() != null) + && getApplication().getTunnelService().isExtensionsTunnel()) { + // Restore the original URI in case the call has been tunneled. + Reference originalBaseRef = new Reference(this.originalRef); + originalBaseRef.setPath(getReference().getBaseRef().getPath()); + this.originalRef.setBaseRef(originalBaseRef); + this.relativePart = this.originalRef.getRemainingPart(false, false); + } + + if (this.relativePart.startsWith("/")) { + // We enforce the leading slash on the root URI + this.relativePart = this.relativePart.substring(1); + } + + // The target URI does not take into account the query and fragment + // parts of the resource. + this.targetUri = + new Reference(directory.getRootRef().toString() + this.relativePart) + .toString(false, false); + preventUpperDirectoryAccess(); + + // Try to detect the presence of a directory + Response contextResponse = getRepresentation(this.targetUri); + + if (contextResponse.getEntity() != null) { + // As a convention, underlying client connectors return the + // directory listing with the media-type "MediaType.TEXT_URI_LIST" when handling + // directories + if (MediaType.TEXT_URI_LIST.equals(contextResponse.getEntity().getMediaType())) { + this.directoryTarget = true; + this.fileTarget = false; + this.directoryContent = tryToConvertAsReferenceList(contextResponse.getEntity()); + + if (!getReference().getPath().endsWith("/")) { + // All requests will be automatically redirected + this.directoryRedirection = true; + } + + if (!this.targetUri.endsWith("/")) { + this.targetUri += "/"; + this.relativePart += "/"; + } + + // Append the index name + if (!StringUtils.isNullOrEmpty(getDirectory().getIndexName())) { + this.directoryUri = this.targetUri; + this.baseName = getDirectory().getIndexName(); + this.targetUri = this.directoryUri + this.baseName; + this.indexTarget = true; + } else { + this.directoryUri = this.targetUri; + this.baseName = null; + } + } else { + // Allows underlying helpers that do not support "content negotiation" to return + // the targeted file. + // Sometimes we immediately reach the target entity, so we return it directly. + this.directoryTarget = false; + this.fileTarget = true; + this.fileContent = contextResponse.getEntity(); + } + } else { + this.directoryTarget = false; + this.fileTarget = false; + + // Let's try with the optional index, in case the underlying + // client connector does not handle directory listing. + if (this.targetUri.endsWith("/")) { + // In this case, the trailing "/" shows that the URI must point to a directory + if (!StringUtils.isNullOrEmpty(getDirectory().getIndexName())) { + this.directoryUri = this.targetUri; + this.directoryTarget = true; + + contextResponse = + getRepresentation(this.directoryUri + getDirectory().getIndexName()); + if (contextResponse.getEntity() != null) { + this.baseName = getDirectory().getIndexName(); + this.targetUri = this.directoryUri + this.baseName; + this.directoryContent = new ReferenceList(); + this.directoryContent.add(new Reference(this.targetUri)); + this.indexTarget = true; + } + } + } else { + // Try to determine if this target URI with no trailing "/" is a directory to force + // the redirection. + if (!StringUtils.isNullOrEmpty(getDirectory().getIndexName())) { + // Append the index name + contextResponse = + getRepresentation(this.targetUri + "/" + getDirectory().getIndexName()); + if (contextResponse.getEntity() != null) { + this.directoryUri = this.targetUri + "/"; + this.baseName = getDirectory().getIndexName(); + this.targetUri = this.directoryUri + this.baseName; + this.directoryTarget = true; + this.directoryRedirection = true; + this.directoryContent = new ReferenceList(); + this.directoryContent.add(new Reference(this.targetUri)); + this.indexTarget = true; + } + } + } + } + + // In case the request does not target a directory and the file + // has not been found, try with the tunneled URI. + if (isNegotiated() + && !this.directoryTarget + && !this.fileTarget + && this.originalRef != null) { + this.relativePart = getReference().getRemainingPart(); + + // The target URI does not take into account the query and fragment parts of the + // resource. + this.targetUri = + new Reference(directory.getRootRef().toString() + this.relativePart) + .normalize() + .toString(false, false); + if (!this.targetUri.startsWith(directory.getRootRef().toString())) { + // Prevent the client from accessing resources in upper directories + this.targetUri = directory.getRootRef().toString(); + } + } + + if (!fileTarget || fileContent == null || !getRequest().getMethod().isSafe()) { + // Try to get the directory content, in case the request does not target a + // directory + if (!this.directoryTarget) { + int lastSlashIndex = this.targetUri.lastIndexOf('/'); + if (lastSlashIndex == -1) { + this.directoryUri = ""; + this.baseName = this.targetUri; + } else { + this.directoryUri = this.targetUri.substring(0, lastSlashIndex + 1); + this.baseName = this.targetUri.substring(lastSlashIndex + 1); + } + + contextResponse = getRepresentation(this.directoryUri); + if ((contextResponse.getEntity() != null) + && MediaType.TEXT_URI_LIST.equals( + contextResponse.getEntity().getMediaType())) { + this.directoryContent = + tryToConvertAsReferenceList(contextResponse.getEntity()); + } + } + + if (this.baseName != null) { + // Analyze extensions + this.baseVariant = new Variant(); + Entity.updateMetadata(this.baseName, this.baseVariant, true, getMetadataService()); + this.protoVariant = new Variant(); + Entity.updateMetadata( + this.baseName, this.protoVariant, false, getMetadataService()); + + // Remove stored extensions from the base name + this.baseName = Entity.getBaseName(this.baseName, getMetadataService()); + } + + // Check if the resource exists or not. + List variants = getVariants(Method.GET); + if ((variants == null) || (variants.isEmpty())) { + setExisting(false); + } + } + + // Check if the resource is located in a subdirectory. + if (isExisting() && !this.directory.isDeeplyAccessible()) { + // Count the number of "/" characters. + int index = this.relativePart.indexOf('/'); + if (index != -1) { + index = this.relativePart.indexOf('/', index); + setExisting((index == -1)); + } + } + + // Log results + getLogger().log(Level.FINE, "Converted target URI: {0}", this.targetUri); + getLogger().log(Level.FINE, "Converted base name: {0}", this.baseName); + } + + private ReferenceList tryToConvertAsReferenceList(Representation entity) + throws ResourceException { + try { + return new ReferenceList(entity); + } catch (IOException e) { + throw new ResourceException(e); + } + } + + @Override + protected Representation get() throws ResourceException { + // Content negotiation has been disabled + // The variant that may need to meet the request conditions + + List variants = getVariants(Method.GET); + if ((variants == null) || (variants.isEmpty())) { + // Resource not found + throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND); + } + + if (variants.size() == 1) { + return (Representation) variants.getFirst(); + } + + ReferenceList variantRefs = new ReferenceList(); + + for (Variant variant : variants) { + if (variant.getLocationRef() != null) { + variantRefs.add(variant.getLocationRef()); + } else { + getLogger() + .warning( + "A resource with multiple variants should provide a location for each variant when content negotiation is turned off"); + } + } + + if (!variantRefs.isEmpty()) { + // Return the list of variants + setStatus(Status.REDIRECTION_MULTIPLE_CHOICES); + return variantRefs.getTextRepresentation(); + } + + throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND); + } + + /** + * Returns the local base name of the file. For example, "foo.en" and "foo.en-GB.html" return + * "foo". + * + * @return The local name of the file. + */ + public String getBaseName() { + return this.baseName; + } + + /** + * Returns a client dispatcher. + * + * @return A client dispatcher. + */ + protected Restlet getClientDispatcher() { + return directoryClientDispatcher; + } + + /** + * Returns the parent directory handler. + * + * @return The parent directory handler. + */ + public Directory getDirectory() { + return this.directory; + } + + /** + * If the resource is a directory, this returns its content. + * + * @return The directory content. + */ + protected ReferenceList getDirectoryContent() { + return directoryContent; + } + + /** + * Returns the context's directory URI (file, clap URI). + * + * @return The context's directory URI (file, clap URI). + */ + public String getDirectoryUri() { + return this.directoryUri; + } + + /** + * Returns a representation of the resource at the target URI. Leverages the client dispatcher + * of the parent directory's context. + * + * @param resourceUri The URI of the target resource. + * @return A response with the representation if success. + */ + private Response getRepresentation(String resourceUri) { + return dispatchRequest(new Request(Method.GET, resourceUri)); + } + + /** + * Returns a representation of the resource at the target URI. Leverages the client dispatcher + * of the parent directory's context. + * + * @param resourceUri The URI of the target resource. + * @param acceptedMediaType The accepted media type or null. + * @return A response with the representation if success. + */ + protected Response getRepresentation(String resourceUri, MediaType acceptedMediaType) { + Request request = new Request(Method.GET, resourceUri); + + if (acceptedMediaType != null) { + request.getClientInfo().accept(acceptedMediaType); + } + + return dispatchRequest(request); + } + + /** + * Allows sorting the list of representations set by the resource. + * + * @return A Comparator instance imposing a sort order of representations or null if no special + * order is wanted. + */ + private Comparator getRepresentationsComparator() { + // Sort the list of representations by their identifier. return (rep0, rep1) -> { boolean bRep0Null = (rep0.getLocationRef() == null); boolean bRep1Null = (rep1.getLocationRef() == null); @@ -481,311 +500,318 @@ private Comparator getRepresentationsComparator() { return 1; } - return rep0.getLocationRef().getLastSegment().compareTo(rep1.getLocationRef().getLastSegment()); + return rep0.getLocationRef() + .getLastSegment() + .compareTo(rep1.getLocationRef().getLastSegment()); }; - } - - /** - * Returns the context's target URI (file, clap URI). - * - * @return The context's target URI (file, clap URI). - */ - public String getTargetUri() { - return this.targetUri; - } - - @Override - public List getVariants() { - return getVariants(getMethod()); - } - - /** - * Returns the list of variants for the given method. - * - * @param method The related method. - * @return The list of variants for the given method. - */ - @Override - protected List getVariants(Method method) { - if (!Method.GET.equals(method) && !Method.HEAD.equals(method)) { - return null; - } - - if (variantsGet != null) { - return variantsGet; - } - - getLogger().fine("Getting variants for: " + getTargetUri()); - - if (this.fileTarget && (this.fileContent != null)) { - // found a target file, set its content location - if (getOriginalRef() != null) { - this.fileContent.setLocationRef(getRequest().getOriginalRef()); - } else { - this.fileContent.setLocationRef(getReference()); - } - - variantsGet = Arrays.asList(this.fileContent); - - return variantsGet; - } - - if ((this.directoryContent != null) && (getReference() != null) && (getReference().getBaseRef() != null)) { - // filter the directory listing - - // Allow sorting the list of representations - SortedSet resultSet = new TreeSet(getRepresentationsComparator()); - - // Compute the base reference (from a call's client point of view) - String baseReference = getVariantsBaseReference(); - - int rootLength = getDirectoryUri().length(); - - if (this.baseName != null) { - String filePath; - for (Reference ref : getVariantsReferences()) { - // Add the new variant to the result list - Response contextResponse = getRepresentation(ref.toString()); - if (contextResponse.getStatus().isSuccess() && (contextResponse.getEntity() != null)) { - filePath = ref.toString(false, false).substring(rootLength); - Representation rep = contextResponse.getEntity(); - - if (filePath.startsWith("/")) { - rep.setLocationRef(baseReference + filePath); - } else { - rep.setLocationRef(baseReference + "/" + filePath); - } - - resultSet.add(rep); - } - } - } - - if (!resultSet.isEmpty()) { - this.variantsGet = new ArrayList(resultSet); - - return this.variantsGet; - } - - if (this.directoryTarget && getDirectory().isListingAllowed()) { - // computes variants from the directory listing - ReferenceList userList = new ReferenceList(this.directoryContent.size()); - // Set the list identifier - userList.setIdentifier(baseReference); - - SortedSet sortedSet = new TreeSet(getDirectory().getComparator()); - sortedSet.addAll(this.directoryContent); - - for (Reference ref : sortedSet) { - String filePart = ref.toString(false, false).substring(rootLength); - StringBuilder filePath = new StringBuilder(); - if ((!baseReference.endsWith("/")) && (!filePart.startsWith("/"))) { - filePath.append('/'); - } - filePath.append(filePart); - userList.add(baseReference + filePath); - } - List list = getDirectory().getIndexVariants(userList); - - if (list != null && !list.isEmpty()) { - this.variantsGet = new ArrayList(); - for (Variant variant : list) { - this.variantsGet.add(getDirectory().getIndexRepresentation(variant, userList)); - } - } - } - } - - return this.variantsGet; - } - - private String getVariantsBaseReference() { - String baseRef = getReference().getBaseRef().toString(false, false); - - if (!baseRef.endsWith("/")) { - baseRef += "/"; - } - - int lastIndex = this.relativePart.lastIndexOf("/"); - - if (lastIndex != -1) { - baseRef += this.relativePart.substring(0, lastIndex); - } - return baseRef; - } - - /** - * Returns the references of the representations of the target resource - * according to the directory handler property - * - * @return The list of variants references - */ - private ReferenceList getVariantsReferences() { - this.uniqueReference = null; - - // Ask for the list of all variants of this resource - Response contextResponse = getRepresentation(this.targetUri, MediaType.TEXT_URI_LIST); - - if (contextResponse.getEntity() == null) { - return new ReferenceList(0); - } - - if (!MediaType.TEXT_URI_LIST.equals(contextResponse.getEntity().getMediaType())) { - // The unique reference has been found. - this.uniqueReference = contextResponse.getEntity().getLocationRef(); - return new ReferenceList(Arrays.asList(contextResponse.getEntity().getLocationRef())); - } - - ReferenceList listVariants; - try { - // Test if the given response is the list of all variants for this resource - listVariants = new ReferenceList(contextResponse.getEntity()); - } catch (IOException ioe) { - getLogger().log(Level.WARNING, "Unable to get resource variants", ioe); - return new ReferenceList(0); - } - - ReferenceList variantsReferences = new ReferenceList(0); - for (Reference variantReference : listVariants) { - String entryUri = variantReference.toString(); - int lastSlashIndex = entryUri.lastIndexOf('/'); - String fullEntryName = (lastSlashIndex == -1) ? entryUri : entryUri.substring(lastSlashIndex + 1); - - // Remove the extensions from the base name - int firstDotIndex = fullEntryName.indexOf('.'); - String baseEntryName = (firstDotIndex != -1) ? fullEntryName.substring(0, firstDotIndex) : fullEntryName; - - if (!baseEntryName.equals(this.baseName)) { - // Not a valid variant - continue; - } - - // Test if the variant is included in the base prototype variant - Variant variant = new Variant(); - Entity.updateMetadata(fullEntryName, variant, true, getMetadataService()); - - if (!this.protoVariant.includes(variant)) { - // Not a valid variant - continue; - } - - variantsReferences.add(variantReference); - - if (variant.equals(this.baseVariant)) { - // The unique reference has been found. - this.uniqueReference = variantReference; - } - } - - return variantsReferences; - } - - @Override - public Representation handle() { - if (!this.directoryRedirection) { - return super.handle(); - } - - // detected a directory, but the current reference lacks the trailing "/", let's - // redirect. - Reference directoryReference = (this.originalRef != null) ? this.originalRef : getReference().getTargetRef(); - if (directoryReference.hasQuery()) { - redirectSeeOther(directoryReference.toString(false, false) + "/?" + directoryReference.getQuery()); - } else { - redirectSeeOther(directoryReference.toString(false, false) + "/"); - } - - return null; - } - - /** - * Indicates if the target resource is a directory. - * - * @return True if the target resource is a directory. - */ - public boolean isDirectoryTarget() { - return this.directoryTarget; - } - - /** - * Indicates if the target resource is a file. - * - * @return True if the target resource is a file. - */ - public boolean isFileTarget() { - return this.fileTarget; - } - - /** - * Transmit the given request to the clientDispatcher.
- * It completes the request's attributes map with the current Directory - * ("org.restlet.directory" key). - * - * @param request The request to send. - * @return The response - */ - private Response dispatchRequest(final Request request) { - final Response response = new Response(request); - dispatchRequest(request, response); - - if (response.getStatus().equals(Status.CLIENT_ERROR_FORBIDDEN)) { - throw new ResourceException(response.getStatus()); - } - - return response; - } - - /** - * Transmit the given request to the clientDispatcher.
- * It completes the request's attributes map with the current Directory - * ("org.restlet.directory" key). - * - * @param request The request to send. - * @param response The related response. - */ - private void dispatchRequest(final Request request, final Response response) { - request.getAttributes().put("org.restlet.directory", this.directory); - getClientDispatcher().handle(request, response); - } - - /** - * Prevent the client from accessing resources in upper directories - */ - public void preventUpperDirectoryAccess() { - String targetUriPath = Reference.decode(targetUri); - - if (!targetUriPath.startsWith(Reference.decode(directory.getRootRef().toString()))) { - throw new ResourceException(Status.CLIENT_ERROR_FORBIDDEN); - } - } - - @Override - public Representation put(Representation entity) throws ResourceException { - if (!this.directory.isModifiable()) { - setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED, "The directory is not modifiable."); - return null; - } - - // Transfer of PUT calls is only allowed if the readOnly flag is not set. - Request contextRequest = new Request(Method.PUT, this.targetUri); - - // Add support of partial PUT calls. - contextRequest.getRanges().addAll(getRanges()); - contextRequest.setEntity(entity); - Response contextResponse = new Response(contextRequest); - contextRequest.setResourceRef(this.targetUri); - dispatchRequest(contextRequest, contextResponse); - setStatus(contextResponse.getStatus()); - - return null; - } - - /** - * Sets the context's target URI (file, clap URI). - * - * @param targetUri The context's target URI. - */ - public void setTargetUri(String targetUri) { - this.targetUri = targetUri; - } + } + + /** + * Returns the context's target URI (file, clap URI). + * + * @return The context's target URI (file, clap URI). + */ + public String getTargetUri() { + return this.targetUri; + } + + @Override + public List getVariants() { + return getVariants(getMethod()); + } + + /** + * Returns the list of variants for the given method. + * + * @param method The related method. + * @return The list of variants for the given method. + */ + @Override + protected List getVariants(Method method) { + if (!Method.GET.equals(method) && !Method.HEAD.equals(method)) { + return null; + } + + if (variantsGet != null) { + return variantsGet; + } + + getLogger().fine("Getting variants for: " + getTargetUri()); + + if (this.fileTarget && (this.fileContent != null)) { + // found a target file, set its content location + if (getOriginalRef() != null) { + this.fileContent.setLocationRef(getRequest().getOriginalRef()); + } else { + this.fileContent.setLocationRef(getReference()); + } + + variantsGet = Arrays.asList(this.fileContent); + + return variantsGet; + } + + if ((this.directoryContent != null) + && (getReference() != null) + && (getReference().getBaseRef() != null)) { + // filter the directory listing + + // Allow sorting the list of representations + SortedSet resultSet = new TreeSet<>(getRepresentationsComparator()); + + // Compute the base reference (from a call's client point of view) + String baseReference = getVariantsBaseReference(); + + int rootLength = getDirectoryUri().length(); + + if (this.baseName != null) { + String filePath; + for (Reference ref : getVariantsReferences()) { + // Add the new variant to the result list + Response contextResponse = getRepresentation(ref.toString()); + if (contextResponse.getStatus().isSuccess() + && (contextResponse.getEntity() != null)) { + filePath = ref.toString(false, false).substring(rootLength); + Representation rep = contextResponse.getEntity(); + + if (filePath.startsWith("/")) { + rep.setLocationRef(baseReference + filePath); + } else { + rep.setLocationRef(baseReference + "/" + filePath); + } + + resultSet.add(rep); + } + } + } + + if (!resultSet.isEmpty()) { + this.variantsGet = new ArrayList<>(resultSet); + + return this.variantsGet; + } + + if (this.directoryTarget && getDirectory().isListingAllowed()) { + // computes variants from the directory listing + ReferenceList userList = new ReferenceList(this.directoryContent.size()); + // Set the list identifier + userList.setIdentifier(baseReference); + + SortedSet sortedSet = new TreeSet<>(getDirectory().getComparator()); + sortedSet.addAll(this.directoryContent); + + for (Reference ref : sortedSet) { + String filePart = ref.toString(false, false).substring(rootLength); + StringBuilder filePath = new StringBuilder(); + if ((!baseReference.endsWith("/")) && (!filePart.startsWith("/"))) { + filePath.append('/'); + } + filePath.append(filePart); + userList.add(baseReference + filePath); + } + List list = getDirectory().getIndexVariants(userList); + + if (list != null && !list.isEmpty()) { + this.variantsGet = new ArrayList<>(); + for (Variant variant : list) { + this.variantsGet.add( + getDirectory().getIndexRepresentation(variant, userList)); + } + } + } + } + + return this.variantsGet; + } + + private String getVariantsBaseReference() { + String baseRef = getReference().getBaseRef().toString(false, false); + + if (!baseRef.endsWith("/")) { + baseRef += "/"; + } + + int lastIndex = this.relativePart.lastIndexOf('/'); + + if (lastIndex != -1) { + baseRef += this.relativePart.substring(0, lastIndex); + } + return baseRef; + } + + /** + * Returns the references of the representations of the target resource according to the + * directory handler property + * + * @return The list of variants references + */ + private ReferenceList getVariantsReferences() { + this.uniqueReference = null; + + // Ask for the list of all variants of this resource + Response contextResponse = getRepresentation(this.targetUri, MediaType.TEXT_URI_LIST); + + if (contextResponse.getEntity() == null) { + return new ReferenceList(0); + } + + if (!MediaType.TEXT_URI_LIST.equals(contextResponse.getEntity().getMediaType())) { + // The unique reference has been found. + this.uniqueReference = contextResponse.getEntity().getLocationRef(); + return new ReferenceList( + Collections.singletonList(contextResponse.getEntity().getLocationRef())); + } + + ReferenceList listVariants; + try { + // Test if the given response is the list of all variants for this resource + listVariants = new ReferenceList(contextResponse.getEntity()); + } catch (IOException ioe) { + getLogger().log(Level.WARNING, "Unable to get resource variants", ioe); + return new ReferenceList(0); + } + + ReferenceList variantsReferences = new ReferenceList(0); + for (Reference variantReference : listVariants) { + String entryUri = variantReference.toString(); + int lastSlashIndex = entryUri.lastIndexOf('/'); + String fullEntryName = + (lastSlashIndex == -1) ? entryUri : entryUri.substring(lastSlashIndex + 1); + + // Remove the extensions from the base name + int firstDotIndex = fullEntryName.indexOf('.'); + String baseEntryName = + (firstDotIndex != -1) + ? fullEntryName.substring(0, firstDotIndex) + : fullEntryName; + + if (baseEntryName.equals(this.baseName)) { // Valid variant + // Test if the variant is included in the base prototype variant + Variant variant = new Variant(); + Entity.updateMetadata(fullEntryName, variant, true, getMetadataService()); + + if (this.protoVariant.includes(variant)) { // Valid variant + variantsReferences.add(variantReference); + + if (variant.equals(this.baseVariant)) { + // The unique reference has been found. + this.uniqueReference = variantReference; + } + } + } + } + + return variantsReferences; + } + + @Override + public Representation handle() { + if (!this.directoryRedirection) { + return super.handle(); + } + + // detected a directory, but the current reference lacks the trailing "/", let's + // redirect. + Reference directoryReference = + (this.originalRef != null) ? this.originalRef : getReference().getTargetRef(); + if (directoryReference.hasQuery()) { + redirectSeeOther( + directoryReference.toString(false, false) + + "/?" + + directoryReference.getQuery()); + } else { + redirectSeeOther(directoryReference.toString(false, false) + "/"); + } + + return null; + } + + /** + * Indicates if the target resource is a directory. + * + * @return True if the target resource is a directory. + */ + public boolean isDirectoryTarget() { + return this.directoryTarget; + } + + /** + * Indicates if the target resource is a file. + * + * @return True if the target resource is a file. + */ + public boolean isFileTarget() { + return this.fileTarget; + } + + /** + * Transmit the given request to the clientDispatcher.
+ * It completes the request's attributes map with the current Directory ("org.restlet.directory" + * key). + * + * @param request The request to send. + * @return The response + */ + private Response dispatchRequest(final Request request) { + final Response response = new Response(request); + dispatchRequest(request, response); + + if (response.getStatus().equals(Status.CLIENT_ERROR_FORBIDDEN)) { + throw new ResourceException(response.getStatus()); + } + + return response; + } + + /** + * Transmit the given request to the clientDispatcher.
+ * It completes the request's attributes map with the current Directory ("org.restlet.directory" + * key). + * + * @param request The request to send. + * @param response The related response. + */ + private void dispatchRequest(final Request request, final Response response) { + request.getAttributes().put("org.restlet.directory", this.directory); + getClientDispatcher().handle(request, response); + } + + /** Prevent the client from accessing resources in upper directories */ + public void preventUpperDirectoryAccess() { + String targetUriPath = Reference.decode(targetUri); + + if (!targetUriPath.startsWith(Reference.decode(directory.getRootRef().toString()))) { + throw new ResourceException(Status.CLIENT_ERROR_FORBIDDEN); + } + } + + @Override + public Representation put(Representation entity) throws ResourceException { + if (this.directory + .isModifiable()) { // Transfer of PUT calls is only allowed if the readOnly flag is + // not set. + Request contextRequest = new Request(Method.PUT, this.targetUri); + + // Add support for partial PUT calls. + contextRequest.getRanges().addAll(getRanges()); + contextRequest.setEntity(entity); + Response contextResponse = new Response(contextRequest); + contextRequest.setResourceRef(this.targetUri); + dispatchRequest(contextRequest, contextResponse); + setStatus(contextResponse.getStatus()); + } else { + setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED, "The directory is not modifiable."); + } + + return null; + } + + /** + * Sets the context's target URI (file, clap URI). + * + * @param targetUri The context's target URI. + */ + public void setTargetUri(String targetUri) { + this.targetUri = targetUri; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/Entity.java b/org.restlet/src/main/java/org/restlet/engine/local/Entity.java index 42dfe7d2bc..279f509b44 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/Entity.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/Entity.java @@ -1,315 +1,322 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; -import org.restlet.data.*; -import org.restlet.representation.Representation; -import org.restlet.representation.Variant; -import org.restlet.service.MetadataService; - import java.io.File; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.TreeSet; +import org.restlet.data.CharacterSet; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; +import org.restlet.representation.Representation; +import org.restlet.representation.Variant; +import org.restlet.service.MetadataService; /** * Represents a local entity, for example a regular file or a directory. - * + * * @author Thierry Boileau * @author Jerome Louvel */ public abstract class Entity { - /** - * Return the base name that is to say the longest part of a given name without - * known extensions (beginning from the left). - * - * @param name The given name. - * @param metadataService Service that holds the known extensions. - * @return The base name of this entity. - */ - public static String getBaseName(String name, MetadataService metadataService) { - final String[] result = name.split("\\."); - final StringBuilder baseName = new StringBuilder().append(result[0]); - boolean extensionFound = false; - for (int i = 1; (i < result.length) && !extensionFound; i++) { - extensionFound = metadataService.getMetadata(result[i]) != null; - if (!extensionFound) { - baseName.append(".").append(result[i]); - } - } - return baseName.toString(); - } - - /** - * Returns the list of known extensions taken from a given entity name. - * - * @param name the given name. - * @param metadataService Service that holds the known extensions. - * @return The list of known extensions taken from the entity name. - */ - public static Collection getExtensions(String name, MetadataService metadataService) { - final Set result = new TreeSet(); - final String[] tokens = name.split("\\."); - boolean extensionFound = false; - - int i; - for (i = 1; (i < tokens.length) && !extensionFound; i++) { - extensionFound = metadataService.getMetadata(tokens[i]) != null; - } - if (extensionFound) { - for (--i; (i < tokens.length); i++) { - result.add(tokens[i]); - } - } - - return result; - } - - /** - * Returns the list of known extensions taken from a given variant. - * - * @param variant the given variant. - * @param metadataService Service that holds the known extensions. - * @return The list of known extensions taken from the variant. - */ - public static Collection getExtensions(Variant variant, MetadataService metadataService) { - final Set result = new TreeSet(); - - String extension = metadataService.getExtension(variant.getCharacterSet()); - if (extension != null) { - result.add(extension); - } - extension = metadataService.getExtension(variant.getMediaType()); - if (extension != null) { - result.add(extension); - } - for (Language language : variant.getLanguages()) { - extension = metadataService.getExtension(language); - if (extension != null) { - result.add(extension); - } - } - for (Encoding encoding : variant.getEncodings()) { - extension = metadataService.getExtension(encoding); - if (extension != null) { - result.add(extension); - } - } - - return result; - } - - /** - * Updates some variant metadata based on a given entry name with extensions. - * - * @param entryName The entry name with extensions. - * @param variant The variant to update. - * @param applyDefault Indicate if default metadata must be applied. - * @param metadataService The parent metadata service. - */ - public static void updateMetadata(String entryName, Variant variant, boolean applyDefault, - MetadataService metadataService) { - if (variant != null) { - String[] tokens = entryName.split("\\."); - Metadata current; - - // We found a potential variant - for (int j = 1; j < tokens.length; j++) { - current = metadataService.getMetadata(tokens[j]); - - if (current != null) { - // Metadata extension detected - if (current instanceof MediaType) { - variant.setMediaType((MediaType) current); - } else if (current instanceof CharacterSet) { - variant.setCharacterSet((CharacterSet) current); - } else if (current instanceof Encoding) { - // Do we need to add this metadata? - boolean found = false; - for (int i = 0; !found && i < variant.getEncodings().size(); i++) { - found = current.includes(variant.getEncodings().get(i)); - } - if (!found) { - variant.getEncodings().add((Encoding) current); - } - } else if (current instanceof Language) { - // Do we need to add this metadata? - boolean found = false; - for (int i = 0; !found && i < variant.getLanguages().size(); i++) { - found = current.includes(variant.getLanguages().get(i)); - } - if (!found) { - variant.getLanguages().add((Language) current); - } - } - } - - final int dashIndex = tokens[j].indexOf('-'); - if (dashIndex != -1) { - // We found a language extension with a region area - // specified. - // Try to find a language matching the primary part of the - // extension. - final String primaryPart = tokens[j].substring(0, dashIndex); - current = metadataService.getMetadata(primaryPart); - if (current instanceof Language) { - variant.getLanguages().add((Language) current); - } - } - } - - if (applyDefault) { - // If no language is defined, take the default one - if (variant.getLanguages().isEmpty()) { - final Language defaultLanguage = metadataService.getDefaultLanguage(); - - if ((defaultLanguage != null) && !defaultLanguage.equals(Language.ALL)) { - variant.getLanguages().add(defaultLanguage); - } - } - - // If no media type is defined, take the default one - if (variant.getMediaType() == null) { - final MediaType defaultMediaType = metadataService.getDefaultMediaType(); - - if ((defaultMediaType != null) && !defaultMediaType.equals(MediaType.ALL)) { - variant.setMediaType(defaultMediaType); - } - } - - // If no encoding is defined, take the default one - if (variant.getEncodings().isEmpty()) { - final Encoding defaultEncoding = metadataService.getDefaultEncoding(); - - if ((defaultEncoding != null) && !defaultEncoding.equals(Encoding.ALL) - && !defaultEncoding.equals(Encoding.IDENTITY)) { - variant.getEncodings().add(defaultEncoding); - } - } - - // If no character set is defined, take the default one - if (variant.getCharacterSet() == null) { - final CharacterSet defaultCharacterSet = metadataService.getDefaultCharacterSet(); - - if ((defaultCharacterSet != null) && !defaultCharacterSet.equals(CharacterSet.ALL)) { - variant.setCharacterSet(defaultCharacterSet); - } - } - } - } - } - - /** The metadata service to use. */ - private volatile MetadataService metadataService; - - /** - * Constructor. - * - * @param metadataService The metadata service to use. - */ - public Entity(MetadataService metadataService) { - this.metadataService = metadataService; - } - - /** - * Indicates if the entity does exist. - * - * @return True if the entity does exists. - */ - public abstract boolean exists(); - - /** - * Return the base name of this entity that is to say the longest part of the - * name without known extensions (beginning from the left). - * - * @return The base name of this entity. - */ - public String getBaseName() { - return getBaseName(getName(), getMetadataService()); - } - - /** - * Returns the list of contained entities if the current entity is a directory, - * null otherwise. - * - * @return The list of contained entities. - */ - public abstract List getChildren(); - - /** - * Returns the list of known extensions. - * - * @return The list of known extensions taken from the entity name. - */ - public Collection getExtensions() { - return getExtensions(getName(), getMetadataService()); - } - - /** - * Returns the metadata service to use. - * - * @return The metadata service to use. - */ - public MetadataService getMetadataService() { - return metadataService; - } - - /** - * Returns the name. - * - * @return The name. - */ - public abstract String getName(); - - /** - * Returns the parent directory (if any). - * - * @return The parent directory, null otherwise. - */ - public abstract Entity getParent(); - - /** - * Returns a representation of this local entity. - * - * @return A representation of this entity. - */ - public abstract Representation getRepresentation(MediaType defaultMediaType, int timeToLive); - - /** - * Returns a variant corresponding to the extensions of this entity. - * - * @return A variant corresponding to the extensions of this entity. - */ - public Variant getVariant() { - Variant result = new Variant(); - updateMetadata(getName(), result, true, getMetadataService()); - return result; - } - - /** - * Indicates if the entity is a directory. - * - * @return True if the entity is a directory. - */ - public abstract boolean isDirectory(); - - /** - * Indicates if the entity is a normal entity, especially if it is not a - * directory. - * - * @return True if the entity is a normal entity. - * @see File#isFile() - * @see File#isDirectory() - */ - public abstract boolean isNormal(); - + /** + * Return the base name that is to say the longest part of a given name without known extensions + * (beginning from the left). + * + * @param name The given name. + * @param metadataService Service that holds the known extensions. + * @return The base name of this entity. + */ + public static String getBaseName(String name, MetadataService metadataService) { + final String[] result = name.split("\\."); + final StringBuilder baseName = new StringBuilder().append(result[0]); + boolean extensionFound = false; + for (int i = 1; (i < result.length) && !extensionFound; i++) { + extensionFound = metadataService.getMetadata(result[i]) != null; + if (!extensionFound) { + baseName.append(".").append(result[i]); + } + } + return baseName.toString(); + } + + /** + * Returns the list of known extensions taken from a given entity name. + * + * @param name the given name. + * @param metadataService Service that holds the known extensions. + * @return The list of known extensions taken from the entity name. + */ + public static Collection getExtensions(String name, MetadataService metadataService) { + final Set result = new TreeSet<>(); + final String[] tokens = name.split("\\."); + boolean extensionFound = false; + + int i; + for (i = 1; (i < tokens.length) && !extensionFound; i++) { + extensionFound = metadataService.getMetadata(tokens[i]) != null; + } + if (extensionFound) { + for (--i; (i < tokens.length); i++) { + result.add(tokens[i]); + } + } + + return result; + } + + /** + * Returns the list of known extensions taken from a given variant. + * + * @param variant the given variant. + * @param metadataService Service that holds the known extensions. + * @return The list of known extensions taken from the variant. + */ + public static Collection getExtensions( + Variant variant, MetadataService metadataService) { + final Set result = new TreeSet<>(); + + String extension = metadataService.getExtension(variant.getCharacterSet()); + if (extension != null) { + result.add(extension); + } + extension = metadataService.getExtension(variant.getMediaType()); + if (extension != null) { + result.add(extension); + } + for (Language language : variant.getLanguages()) { + extension = metadataService.getExtension(language); + if (extension != null) { + result.add(extension); + } + } + for (Encoding encoding : variant.getEncodings()) { + extension = metadataService.getExtension(encoding); + if (extension != null) { + result.add(extension); + } + } + + return result; + } + + /** + * Updates some variant metadata based on a given entry name with extensions. + * + * @param entryName The entry name with extensions. + * @param variant The variant to update. + * @param applyDefault Indicate if default metadata must be applied. + * @param metadataService The parent metadata service. + */ + public static void updateMetadata( + String entryName, + Variant variant, + boolean applyDefault, + MetadataService metadataService) { + if (variant != null) { + String[] tokens = entryName.split("\\."); + Metadata current; + + // We found a potential variant + for (int j = 1; j < tokens.length; j++) { + current = metadataService.getMetadata(tokens[j]); + + if (current != null) { + // Metadata extension detected + switch (current) { + case MediaType mediaType -> variant.setMediaType(mediaType); + case CharacterSet characterSet -> variant.setCharacterSet(characterSet); + case Encoding encoding -> { + // Do we need to add this metadata? + boolean found = false; + for (int i = 0; !found && i < variant.getEncodings().size(); i++) { + found = current.includes(variant.getEncodings().get(i)); + } + if (!found) { + variant.getEncodings().add(encoding); + } + } + case Language language -> { + // Do we need to add this metadata? + boolean found = false; + for (int i = 0; !found && i < variant.getLanguages().size(); i++) { + found = current.includes(variant.getLanguages().get(i)); + } + if (!found) { + variant.getLanguages().add(language); + } + } + default -> + throw new IllegalStateException("Unexpected metadata: " + current); + } + } + + final int dashIndex = tokens[j].indexOf('-'); + if (dashIndex != -1) { + // We found a language extension with a region area specified. + // Try to find a language matching the primary part of the extension. + final String primaryPart = tokens[j].substring(0, dashIndex); + current = metadataService.getMetadata(primaryPart); + if (current instanceof Language language) { + variant.getLanguages().add(language); + } + } + } + + if (applyDefault) { + // If no language is defined, take the default one + if (variant.getLanguages().isEmpty()) { + final Language defaultLanguage = metadataService.getDefaultLanguage(); + + if ((defaultLanguage != null) && !defaultLanguage.equals(Language.ALL)) { + variant.getLanguages().add(defaultLanguage); + } + } + + // If no media type is defined, take the default one + if (variant.getMediaType() == null) { + final MediaType defaultMediaType = metadataService.getDefaultMediaType(); + + if ((defaultMediaType != null) && !defaultMediaType.equals(MediaType.ALL)) { + variant.setMediaType(defaultMediaType); + } + } + + // If no encoding is defined, take the default one + if (variant.getEncodings().isEmpty()) { + final Encoding defaultEncoding = metadataService.getDefaultEncoding(); + + if ((defaultEncoding != null) + && !defaultEncoding.equals(Encoding.ALL) + && !defaultEncoding.equals(Encoding.IDENTITY)) { + variant.getEncodings().add(defaultEncoding); + } + } + + // If no character set is defined, take the default one + if (variant.getCharacterSet() == null) { + final CharacterSet defaultCharacterSet = + metadataService.getDefaultCharacterSet(); + + if ((defaultCharacterSet != null) + && !defaultCharacterSet.equals(CharacterSet.ALL)) { + variant.setCharacterSet(defaultCharacterSet); + } + } + } + } + } + + /** The metadata service to use. */ + private volatile MetadataService metadataService; + + /** + * Constructor. + * + * @param metadataService The metadata service to use. + */ + protected Entity(MetadataService metadataService) { + this.metadataService = metadataService; + } + + /** + * Indicates if the entity does exist. + * + * @return True if the entity does exist. + */ + public abstract boolean exists(); + + /** + * Return the base name of this entity, meaning the longest part of the name without known + * extensions (beginning from the left). + * + * @return The base name of this entity. + */ + public String getBaseName() { + return getBaseName(getName(), getMetadataService()); + } + + /** + * Returns the list of contained entities if the current entity is a directory, null otherwise. + * + * @return The list of contained entities. + */ + public abstract List getChildren(); + + /** + * Returns the list of known extensions. + * + * @return The list of known extensions taken from the entity name. + */ + public Collection getExtensions() { + return getExtensions(getName(), getMetadataService()); + } + + /** + * Returns the metadata service to use. + * + * @return The metadata service to use. + */ + public MetadataService getMetadataService() { + return metadataService; + } + + /** + * Returns the name. + * + * @return The name. + */ + public abstract String getName(); + + /** + * Returns the parent directory (if any). + * + * @return The parent directory, null otherwise. + */ + public abstract Entity getParent(); + + /** + * Returns a representation of this local entity. + * + * @return A representation of this entity. + */ + public abstract Representation getRepresentation(MediaType defaultMediaType, int timeToLive); + + /** + * Returns a variant corresponding to the extensions of this entity. + * + * @return A variant corresponding to the extensions of this entity. + */ + public Variant getVariant() { + Variant result = new Variant(); + updateMetadata(getName(), result, true, getMetadataService()); + return result; + } + + /** + * Indicates if the entity is a directory. + * + * @return True if the entity is a directory. + */ + public abstract boolean isDirectory(); + + /** + * Indicates if the entity is a normal entity, especially if it is not a directory. + * + * @return True if the entity is a normal entity. + * @see File#isFile() + * @see File#isDirectory() + */ + public abstract boolean isNormal(); } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/EntityClientHelper.java b/org.restlet/src/main/java/org/restlet/engine/local/EntityClientHelper.java index b23b57e479..c67ce10845 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/EntityClientHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/EntityClientHelper.java @@ -1,252 +1,269 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; +import java.util.Collection; +import java.util.Iterator; import org.restlet.Client; import org.restlet.Request; import org.restlet.Response; -import org.restlet.data.*; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Preference; +import org.restlet.data.Reference; +import org.restlet.data.ReferenceList; +import org.restlet.data.Status; import org.restlet.representation.Representation; import org.restlet.representation.Variant; -import java.util.Collection; -import java.util.Iterator; - /** - * Connector to the local entities. That connector supports the content - * negotiation feature (i.e. for GET and HEAD methods) and implements the - * response to GET/HEAD methods. - * + * Connector to the local entities. That connector supports the content negotiation feature (i.e., + * for GET and HEAD methods) and implements the response to GET/HEAD methods. + * * @author Thierry Boileau */ public abstract class EntityClientHelper extends LocalClientHelper { - /** - * Constructor. - * - * @param client The client to help. - */ - public EntityClientHelper(Client client) { - super(client); - } - - /** - * Generate a Reference for a variant name (which is URL decoded) and handle the - * translation between the incoming requested path (which is URL encoded). - * - * @param scheme The scheme of the requested resource. - * @param encodedParentDirPath The encoded path of the parent directory of the - * requested resource. - * @param encodedEntityName The encoded name of the requested resource. - * @param decodedVariantName The decoded name of a returned resource. - * @return A new Reference. - */ - public Reference createReference(String scheme, String encodedParentDirPath, String encodedEntityName, - String decodedVariantName) { - return new Reference(scheme + "://" + encodedParentDirPath + "/" - + getReencodedVariantEntityName(encodedEntityName, decodedVariantName)); - } - - /** - * Returns a local entity for the given path. - * - * @param path The path of the entity. - * @return A local entity for the given path. - */ - public abstract Entity getEntity(String path); - - /** - * Percent-encodes the given percent-decoded variant name of a resource whose - * percent-encoded name is given. Tries to match the longest common part of both - * encoded entity name and decoded variant name. - * - * @param encodedEntityName the percent-encoded name of the initial - * resource - * @param decodedVariantEntityName the percent-decoded entity name of a variant - * of the initial resource. - * @return The variant percent-encoded entity name. - */ - protected String getReencodedVariantEntityName(String encodedEntityName, String decodedVariantEntityName) { - int i = 0; - int j = 0; - boolean stop = false; - char[] encodeds = encodedEntityName.toCharArray(); - char[] decodeds = decodedVariantEntityName.toCharArray(); - - for (i = 0; (i < decodeds.length) && (j < encodeds.length) && !stop; i++) { - char decodedChar = decodeds[i]; - char encodedChar = encodeds[j]; - - if (encodedChar == '%') { - String dec = Reference.decode(encodedEntityName.substring(j, j + 3)); - if (decodedChar == dec.charAt(0)) { - j += 3; - } else { - stop = true; - } - } else if (decodedChar == encodedChar) { - j++; - } else { - String dec = Reference.decode(encodedEntityName.substring(j, j + 1)); - if (decodedChar == dec.charAt(0)) { - j++; - } else { - stop = true; - } - } - } - - if (stop) { - return encodedEntityName.substring(0, j) + decodedVariantEntityName.substring(i - 1); - } - - if (j == encodedEntityName.length()) { - return encodedEntityName.substring(0, j) + decodedVariantEntityName.substring(i); - } - - return encodedEntityName.substring(0, j); - } - - /** - * Handles a GET call. - * - * @param request The request to answer. - * @param response The response to update. - * @param entity The requested entity (normal or directory). - */ - protected void handleEntityGet(Request request, Response response, Entity entity) { - Representation output = null; - - // Get variants for a resource - boolean found = false; - Iterator> iterator = request.getClientInfo().getAcceptedMediaTypes().iterator(); - while (iterator.hasNext() && !found) { - Preference pref = iterator.next(); - found = pref.getMetadata().equals(MediaType.TEXT_URI_LIST); - } - - if (found) { - // Try to list all variants of this resource - // 1- set up base name as the longest part of the name without known - // extensions (beginning from the left) - String baseName = entity.getBaseName(); - - // 2- looking for resources with the same base name - Entity parent = entity.getParent(); - - if (parent != null) { - Collection entities = parent.getChildren(); - - if (entities != null) { - ReferenceList rl = new ReferenceList(entities.size()); - String scheme = request.getResourceRef().getScheme(); - String path = request.getResourceRef().getPath(); - String encodedParentDirectoryURI = path.substring(0, path.lastIndexOf("/")); - String encodedEntityName = path.substring(path.lastIndexOf("/") + 1); - - for (Entity entry : entities) { - if (baseName.equals(entry.getBaseName())) { - rl.add(createReference(scheme, encodedParentDirectoryURI, encodedEntityName, - entry.getName())); - } - } - - output = rl.getTextRepresentation(); - } - } - } else { - if (entity.exists()) { - if (entity.isDirectory()) { - // Return the directory listing - Collection children = entity.getChildren(); - ReferenceList rl = new ReferenceList(children.size()); - String directoryUri = request.getResourceRef().getTargetRef().toString(); - - // Ensures that the directory URI ends with a slash - if (!directoryUri.endsWith("/")) { - directoryUri += "/"; - } - - for (Entity entry : children) { - if (entry.isDirectory()) { - rl.add(directoryUri + Reference.encode(entry.getName()) + "/"); - } else { - rl.add(directoryUri + Reference.encode(entry.getName())); - } - } - - output = rl.getTextRepresentation(); - } else { - // Return the file content - output = entity.getRepresentation(getMetadataService().getDefaultMediaType(), getTimeToLive()); - output.setLocationRef(request.getResourceRef()); - Entity.updateMetadata(entity.getName(), output, true, getMetadataService()); - } - } else { - // We look for the possible variant which has the same - // metadata based on extensions (in a distinct order) and - // default metadata. - Entity uniqueVariant = null; - - // 1- set up base name as the longest part of the name without - // known extensions (beginning from the left) - String baseName = entity.getBaseName(); - Variant entityVariant = entity.getVariant(); - - // 2- looking for resources with the same base name - Entity parent = entity.getParent(); - if (parent != null) { - Collection files = parent.getChildren(); - - if (files != null) { - for (Entity entry : files) { - if (baseName.equals(entry.getBaseName())) { - Variant entryVariant = entry.getVariant(); - - if (entityVariant.isCompatible(entryVariant)) { - // The right representation has been found. - uniqueVariant = entry; - break; - } - } - } - } - } - - if (uniqueVariant != null) { - // Return the file content - output = uniqueVariant.getRepresentation(getMetadataService().getDefaultMediaType(), - getTimeToLive()); - output.setLocationRef(request.getResourceRef()); - Entity.updateMetadata(entity.getName(), output, true, getMetadataService()); - } - } - } - - if (output == null) { - response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); - } else { - output.setLocationRef(request.getResourceRef()); - response.setEntity(output); - response.setStatus(Status.SUCCESS_OK); - } - } - - @Override - protected void handleLocal(Request request, Response response, String decodedPath) { - if (Method.GET.equals(request.getMethod()) || Method.HEAD.equals(request.getMethod())) { - handleEntityGet(request, response, getEntity(decodedPath)); - } else { - response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - response.getAllowedMethods().add(Method.GET); - response.getAllowedMethods().add(Method.HEAD); - } - } + /** + * Constructor. + * + * @param client The client to help. + */ + protected EntityClientHelper(Client client) { + super(client); + } + + /** + * Generate a Reference for a variant name (which is URL decoded) and handle the translation + * between the incoming requested path (which is URL encoded). + * + * @param scheme The scheme of the requested resource. + * @param encodedParentDirPath The encoded path of the parent directory of the requested + * resource. + * @param encodedEntityName The encoded name of the requested resource. + * @param decodedVariantName The decoded name of a returned resource. + * @return A new Reference. + */ + public Reference createReference( + String scheme, + String encodedParentDirPath, + String encodedEntityName, + String decodedVariantName) { + return new Reference( + scheme + + "://" + + encodedParentDirPath + + "/" + + getReencodedVariantEntityName(encodedEntityName, decodedVariantName)); + } + + /** + * Returns a local entity for the given path. + * + * @param path The path of the entity. + * @return A local entity for the given path. + */ + public abstract Entity getEntity(String path); + + /** + * Percent-encodes the given percent-decoded variant name of a resource whose percent-encoded + * name is given. Tries to match the longest common part of both encoded entity name and decoded + * variant name. + * + * @param encodedEntityName the percent-encoded name of the initial resource + * @param decodedVariantEntityName the percent-decoded entity name of a variant of the initial + * resource. + * @return The variant percent-encoded entity name. + */ + protected String getReencodedVariantEntityName( + String encodedEntityName, String decodedVariantEntityName) { + int i = 0; + int j = 0; + boolean stop = false; + char[] encodedChars = encodedEntityName.toCharArray(); + char[] decodedChars = decodedVariantEntityName.toCharArray(); + + for (i = 0; (i < decodedChars.length) && (j < encodedChars.length) && !stop; i++) { + char decodedChar = decodedChars[i]; + char encodedChar = encodedChars[j]; + + if (encodedChar == '%') { + String dec = Reference.decode(encodedEntityName.substring(j, j + 3)); + if (decodedChar == dec.charAt(0)) { + j += 3; + } else { + stop = true; + } + } else if (decodedChar == encodedChar) { + j++; + } else { + String dec = Reference.decode(encodedEntityName.substring(j, j + 1)); + if (decodedChar == dec.charAt(0)) { + j++; + } else { + stop = true; + } + } + } + + if (stop) { + return encodedEntityName.substring(0, j) + decodedVariantEntityName.substring(i - 1); + } + + if (j == encodedEntityName.length()) { + return encodedEntityName.substring(0, j) + decodedVariantEntityName.substring(i); + } + + return encodedEntityName.substring(0, j); + } + + /** + * Handles a GET call. + * + * @param request The request to answer. + * @param response The response to update. + * @param entity The requested entity (normal or directory). + */ + protected void handleEntityGet(Request request, Response response, Entity entity) { + Representation output = null; + + // Get variants for a resource + boolean found = false; + Iterator> iterator = + request.getClientInfo().getAcceptedMediaTypes().iterator(); + while (iterator.hasNext() && !found) { + Preference pref = iterator.next(); + found = pref.getMetadata().equals(MediaType.TEXT_URI_LIST); + } + + if (found) { + // Try to list all variants of this resource + // 1- set up the base name as the longest part of the name without known + // extensions (beginning from the left) + String baseName = entity.getBaseName(); + + // 2- looking for resources with the same base name + Entity parent = entity.getParent(); + + if (parent != null) { + Collection entities = parent.getChildren(); + + if (entities != null) { + ReferenceList rl = new ReferenceList(entities.size()); + String scheme = request.getResourceRef().getScheme(); + String path = request.getResourceRef().getPath(); + String encodedParentDirectoryURI = path.substring(0, path.lastIndexOf('/')); + String encodedEntityName = path.substring(path.lastIndexOf('/') + 1); + + for (Entity entry : entities) { + if (baseName.equals(entry.getBaseName())) { + rl.add( + createReference( + scheme, + encodedParentDirectoryURI, + encodedEntityName, + entry.getName())); + } + } + + output = rl.getTextRepresentation(); + } + } + } else { + if (entity.exists()) { + if (entity.isDirectory()) { + // Return the directory listing + Collection children = entity.getChildren(); + ReferenceList rl = new ReferenceList(children.size()); + String directoryUri = request.getResourceRef().getTargetRef().toString(); + + // Ensures that the directory URI ends with a slash + if (!directoryUri.endsWith("/")) { + directoryUri += "/"; + } + + for (Entity entry : children) { + if (entry.isDirectory()) { + rl.add(directoryUri + Reference.encode(entry.getName()) + "/"); + } else { + rl.add(directoryUri + Reference.encode(entry.getName())); + } + } + + output = rl.getTextRepresentation(); + } else { + // Return the file content + output = + entity.getRepresentation( + getMetadataService().getDefaultMediaType(), getTimeToLive()); + output.setLocationRef(request.getResourceRef()); + Entity.updateMetadata(entity.getName(), output, true, getMetadataService()); + } + } else { + // We look for the possible variant which has the same + // metadata based on extensions (in a distinct order) and + // default metadata. + Entity uniqueVariant = null; + + // 1- set up the base name as the longest part of the name without + // known extensions (beginning from the left) + String baseName = entity.getBaseName(); + Variant entityVariant = entity.getVariant(); + + // 2- looking for resources with the same base name + Entity parent = entity.getParent(); + if (parent != null) { + Collection files = parent.getChildren(); + + if (files != null) { + for (Entity entry : files) { + if (baseName.equals(entry.getBaseName())) { + Variant entryVariant = entry.getVariant(); + + if (entityVariant.isCompatible(entryVariant)) { + // The right representation has been found. + uniqueVariant = entry; + break; + } + } + } + } + } + + if (uniqueVariant != null) { + // Return the file content + output = + uniqueVariant.getRepresentation( + getMetadataService().getDefaultMediaType(), getTimeToLive()); + output.setLocationRef(request.getResourceRef()); + Entity.updateMetadata(entity.getName(), output, true, getMetadataService()); + } + } + } + + if (output == null) { + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + } else { + output.setLocationRef(request.getResourceRef()); + response.setEntity(output); + response.setStatus(Status.SUCCESS_OK); + } + } + + @Override + protected void handleLocal(Request request, Response response, String decodedPath) { + if (Method.GET.equals(request.getMethod()) || Method.HEAD.equals(request.getMethod())) { + handleEntityGet(request, response, getEntity(decodedPath)); + } else { + response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + response.getAllowedMethods().add(Method.GET); + response.getAllowedMethods().add(Method.HEAD); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/FileClientHelper.java b/org.restlet/src/main/java/org/restlet/engine/local/FileClientHelper.java index 381493d0de..ea821e2752 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/FileClientHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/FileClientHelper.java @@ -1,22 +1,30 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; -import org.restlet.Client; -import org.restlet.Request; -import org.restlet.Response; -import org.restlet.data.*; -import org.restlet.engine.io.IoUtils; -import org.restlet.representation.Representation; -import org.restlet.representation.Variant; -import org.restlet.resource.Directory; +import static java.lang.String.format; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.util.logging.Level.WARNING; +import static org.restlet.data.Method.DELETE; +import static org.restlet.data.Method.GET; +import static org.restlet.data.Method.HEAD; +import static org.restlet.data.Method.PUT; +import static org.restlet.data.Protocol.FILE; +import static org.restlet.data.Range.isBytesRange; +import static org.restlet.data.Status.CLIENT_ERROR_BAD_REQUEST; +import static org.restlet.data.Status.CLIENT_ERROR_FORBIDDEN; +import static org.restlet.data.Status.CLIENT_ERROR_METHOD_NOT_ALLOWED; +import static org.restlet.data.Status.CLIENT_ERROR_NOT_ACCEPTABLE; +import static org.restlet.data.Status.SERVER_ERROR_INTERNAL; +import static org.restlet.data.Status.SUCCESS_CREATED; +import static org.restlet.data.Status.SUCCESS_NO_CONTENT; +import static org.restlet.data.Status.SUCCESS_OK; import java.io.File; import java.io.FileFilter; @@ -25,20 +33,31 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; - -import static java.lang.String.format; -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; -import static java.util.logging.Level.WARNING; -import static org.restlet.data.Method.*; -import static org.restlet.data.Protocol.FILE; -import static org.restlet.data.Range.isBytesRange; -import static org.restlet.data.Status.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import org.restlet.Client; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.CharacterSet; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.LocalReference; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; +import org.restlet.data.Range; +import org.restlet.data.Status; +import org.restlet.engine.io.IoUtils; +import org.restlet.representation.Representation; +import org.restlet.representation.Variant; +import org.restlet.resource.Directory; /** - * Connector to the file resources accessible. Here is the list of parameters - * that are supported. They should be set in the Client's context before it is - * started: + * Connector to the file resources accessible. Here is the list of parameters that are supported. + * They should be set in the Client's context before it is started: + * * * * @@ -62,530 +81,540 @@ * deletion of the temporary file created. * *
list of supported parameters
- * + * * @author Jerome Louvel * @author Thierry Boileau */ public class FileClientHelper extends EntityClientHelper { - /** - * Constructor. - * - * @param client The client to help. - */ - public FileClientHelper(Client client) { - super(client); - getProtocols().add(FILE); - } - - /** - * Check that all extensions of the file correspond to a known metadata. - * - * @param file The file whose extensions are checked. - * @return True if all extensions of the file are known by the metadata service. - */ - protected boolean checkExtensionsConsistency(File file) { - boolean knownExtension = true; - - Collection set = Entity.getExtensions(file.getName(), getMetadataService()); - Iterator iterator = set.iterator(); - while (iterator.hasNext() && knownExtension) { - knownExtension = getMetadataService().getMetadata(iterator.next()) != null; - } - - return knownExtension; - } - - /** - * Checks that the URI and the representation are compatible. The whole set of - * metadata of the representation must be included in the set of those of the - * URI - * - * @param fileName The name of the resource - * @param representation The provided representation. - * @return True if the metadata of the representation are compatible with the - * metadata extracted from the filename - */ - private boolean checkMetadataConsistency(String fileName, Representation representation) { - if (representation != null) { - Variant var = new Variant(); - Entity.updateMetadata(fileName, var, false, getMetadataService()); - - // "var" contains the theoretical correct metadata - if (!var.getLanguages().isEmpty() && !representation.getLanguages().isEmpty() - && !new HashSet<>(var.getLanguages()).containsAll(representation.getLanguages())) { - return false; - } - - if ((var.getMediaType() != null) && (representation.getMediaType() != null) - && !(var.getMediaType().includes(representation.getMediaType()))) { - return false; - } - - if (!var.getEncodings().isEmpty() && !representation.getEncodings().isEmpty() - && !new HashSet<>(var.getEncodings()).containsAll(representation.getEncodings())) { - return false; - } - } - return true; - } - - @Override - public Entity getEntity(String decodedPath) { - return new FileEntity(getFileWithLocalizedPath(decodedPath), getMetadataService()); - } - - /** - * Returns a new {@link File} instance for the given path name.
- * It ensures to translate the "/" to the local supported file separator (mainly - * useful for Windows OS). - * - * @param path The Path of the file - * @return a new {@link File} instance for the given path name. - */ - private static File getFileWithLocalizedPath(final String path) { - return new File(LocalReference.localizePath(path)); - } - - /** - * Returns the name of the extension to use to store the temporary content while - * uploading content via the PUT method. Defaults to "tmp". - * - * @return The name of the extension to use to store the temporary content. - */ - public String getTemporaryExtension() { - return getHelpedParameters().getFirstValue("temporaryExtension", "tmp"); - } - - @Override - protected void handleLocal(Request request, Response response, String decodedPath) { - String scheme = request.getResourceRef().getScheme(); - - if (!FILE.getSchemeName().equalsIgnoreCase(scheme)) { - throw new IllegalArgumentException( - format("Protocol \"%s\" not supported by the connector. Only FILE is supported.", scheme)); - } - - handleFile(request, response, decodedPath); - } - - protected void handleFile(Request request, Response response, String decodedPath) { - final Directory directory = (Directory) request.getAttributes().get("org.restlet.directory"); - final File fileWithLocalizedPath = getFileWithLocalizedPath(decodedPath); - - if (!isFileInDirectory(directory, fileWithLocalizedPath)) { - response.setStatus(CLIENT_ERROR_FORBIDDEN); - } else if (GET.equals(request.getMethod()) || HEAD.equals(request.getMethod())) { - handleEntityGet(request, response, getEntity(decodedPath)); - } else if (PUT.equals(request.getMethod())) { - handleFilePut(request, response, decodedPath, fileWithLocalizedPath); - } else if (DELETE.equals(request.getMethod())) { - handleFileDelete(response, fileWithLocalizedPath); - } else { - response.setStatus(CLIENT_ERROR_METHOD_NOT_ALLOWED); - response.getAllowedMethods().add(GET); - response.getAllowedMethods().add(HEAD); - response.getAllowedMethods().add(PUT); - response.getAllowedMethods().add(DELETE); - } - } - - /** - * Indicates whether the given file is located inside the root directory. - * - * @param directory The root directory - * @param file The file. - * @return True if the path is located under the root directory, false - * otherwise. - */ - private static boolean isFileInDirectory(final Directory directory, final File file) { - boolean result = true; - - if (directory != null) { - final String fileAbsolute = directory.getRootRef().getPath(true); - final String filePath; - - if (fileAbsolute.indexOf(':') == 2 || fileAbsolute.indexOf('|') == 2) { - filePath = fileAbsolute.substring(1); - } else { - filePath = fileAbsolute; - } - - final Path rootDirectoryPath = Paths.get(filePath).normalize(); - final Path actualFilePath = file.toPath().normalize(); - result = !rootDirectoryPath.relativize(actualFilePath).toString().startsWith(".."); - } - - return result; - } - - /** - * Handles a DELETE call for the FILE protocol. - * - * @param response The response to update. - * @param file The file or directory to delete. - */ - protected void handleFileDelete(Response response, File file) { - if (file.isDirectory()) { - final File[] files = file.listFiles(); - if (files == null || files.length == 0) { - if (IoUtils.delete(file)) { - response.setStatus(SUCCESS_NO_CONTENT); - } else { - response.setStatus(SERVER_ERROR_INTERNAL, "Couldn't delete the directory"); - } - } else { - response.setStatus(CLIENT_ERROR_FORBIDDEN, "Couldn't delete the non-empty directory"); - } - } else { - if (IoUtils.delete(file)) { - response.setStatus(SUCCESS_NO_CONTENT); - } else { - response.setStatus(SERVER_ERROR_INTERNAL, "Couldn't delete the file"); - } - } - } - - /** - * Handles a PUT call for the FILE protocol. - * - * @param request The request to update. - * @param response The response to update. - * @param path The encoded path of the requested file or directory. - * @param file The requested file or directory. - */ - protected void handleFilePut(Request request, Response response, String path, File file) { - response.setStatus(doHandleFilePut(request, path, file)); - } - - private Status doHandleFilePut(Request request, String path, File file) { - // Handle directory - if (file.exists()) { - if (file.isDirectory()) { - return new Status(CLIENT_ERROR_FORBIDDEN, "Can't put a new representation of a directory"); - } - } else if (path.endsWith("/")) { - // It seems the request targets a directory - // Create a new directory and its parents if necessary - if (file.mkdirs()) { - return SUCCESS_NO_CONTENT; - } else { - getLogger().warning("Unable to create the new directory"); - return new Status(SERVER_ERROR_INTERNAL, "Unable to create the new directory"); - } - } - - // Several checks : first the consistency of the metadata and the filename - if (!checkMetadataConsistency(file.getName(), request.getEntity())) { - // Ask the client to reiterate properly its request - return new Status(CLIENT_ERROR_BAD_REQUEST, "The metadata are not consistent with the URI"); - } - - // We look for the possible variants - // Set up base name as the longest part of the name without known extensions - // (beginning from the left) - final String baseName = Entity.getBaseName(file.getName(), getMetadataService()); - - // Look for resources with the same base name - FileFilter filter = new FileFilter() { - public boolean accept(File file) { - return file.isFile() && baseName.equals(Entity.getBaseName(file.getName(), getMetadataService())); - } - }; - - File[] files = file.getParentFile().listFiles(filter); - File uniqueVariant = null; - List variantsList = new ArrayList(); - - if (files != null && files.length > 0) { - // Set the list of extensions, due to the file name and the - // default metadata. - // TODO It seems we could handle more clearly the equivalence - // between the file name space and the target resource (URI - // completed by default metadata) - Variant variant = new Variant(); - Entity.updateMetadata(file.getName(), variant, false, getMetadataService()); - Collection extensions = Entity.getExtensions(variant, getMetadataService()); - - for (File entry : files) { - Collection entryExtensions = Entity.getExtensions(entry.getName(), getMetadataService()); - - if (entryExtensions.containsAll(extensions)) { - variantsList.add(entry); - - if (extensions.containsAll(entryExtensions)) { - // The right representation has been found. - uniqueVariant = entry; - } - } - } - } - - if (uniqueVariant != null) { - file = uniqueVariant; - } else { - if (!variantsList.isEmpty()) { - // Negotiated resource (several variants, but not the right one). - // Check if the request could be completed or not. - // The request could be more precise - return new Status(CLIENT_ERROR_NOT_ACCEPTABLE, - "Unable to process properly the request. Several variants exist but none of them suits precisely."); - } - - // This resource does not exist, yet. Complete it with the - // default metadata - Entity.updateMetadata(file.getName(), request.getEntity(), true, getMetadataService()); - - // Update the URI - StringBuilder fileName = new StringBuilder(baseName); - - for (Language language : request.getEntity().getLanguages()) { - updateFileExtension(fileName, language); - } - - for (Encoding encoding : request.getEntity().getEncodings()) { - updateFileExtension(fileName, encoding); - } - - // It is important to finish with the media type as it is - // often leveraged by operating systems to detect file type - updateFileExtension(fileName, request.getEntity().getMediaType()); - - file = new File(file.getParentFile(), fileName.toString()); - } - - // Before putting the file representation, we check that all the extensions are - // known - if (!checkExtensionsConsistency(file)) { - return new Status(SERVER_ERROR_INTERNAL, - "Unable to process properly the URI. At least one extension is not known by the server."); - } - - // This helper supports only single "bytes" range. - Range range = (!request.getRanges().isEmpty() && isBytesRange(request.getRanges().get(0))) - ? request.getRanges().get(0) - : null; - - if (file.exists()) { - // The PUT call is handled in two phases: - // 1- write a temporary file - // 2- rename the target file - if (range != null) { - return updateFileWithPartialContent(request, file, range); - } - return replaceFile(request, file); - } else { - // The file does not exist yet. - File parent = file.getParentFile(); - - if ((parent != null) && !parent.exists()) { - // Create the parent directories then the new file - if (!parent.mkdirs()) { - String message = "Unable to create the parent directory"; - getLogger().warning(message); - return new Status(SERVER_ERROR_INTERNAL, message); - } - } - - // Create the new file - if (range != null) { - return createFileWithPartialContent(request, file, range); - } - return createFile(request, file); - } - } - - private Status updateFileWithPartialContent(Request request, File file, Range range) { - File tmp = null; - - // Replace the content of the file. First, create a temporary file - try { - // The temporary file used for partial PUT. - tmp = new File(file.getCanonicalPath() + "." + getTemporaryExtension()); - - cleanTemporaryFileIfUploadNotResumed(tmp); - - if (!tmp.exists()) { - // Copy the target file. - Files.copy(file.toPath(), tmp.toPath()); - } - - try (RandomAccessFile raf = new RandomAccessFile(tmp, "rwd")) { - // Go to the desired offset. - if (range.getIndex() == Range.INDEX_LAST) { - if (raf.length() <= range.getSize()) { - raf.seek(range.getSize()); - } else { - raf.seek(raf.length() - range.getSize()); - } - } else { - raf.seek(range.getIndex()); - } - - // Write the entity to the temporary file. - if (request.isEntityAvailable()) { - IoUtils.copy(request.getEntity().getStream(), raf); - } - } catch (IOException ioe) { - getLogger().log(WARNING, "Unable to close the temporary file", ioe); - cleanTemporaryFileIfUploadNotResumed(tmp); - return new Status(SERVER_ERROR_INTERNAL, ioe); - } - - return replaceFileByTemporaryFile(request, file, tmp); - } catch (IOException ioe) { - getLogger().log(WARNING, "Unable to create the temporary file", ioe); - if (tmp != null) { - cleanTemporaryFileIfUploadNotResumed(tmp); - } - return new Status(SERVER_ERROR_INTERNAL, "Unable to create a temporary file"); - } - } - - private Status replaceFile(Request request, File file) { - File tmp = null; - try { - tmp = File.createTempFile("restlet-upload", "bin"); - if (request.isEntityAvailable()) { - Files.copy(request.getEntity().getStream(), tmp.toPath(), REPLACE_EXISTING); - } - } catch (IOException ioe) { - getLogger().log(WARNING, "Unable to create the temporary file", ioe); - cleanTemporaryFileIfUploadNotResumed(tmp); - return new Status(SERVER_ERROR_INTERNAL, "Unable to create a temporary file"); - } - return replaceFileByTemporaryFile(request, file, tmp); - } - - private Status replaceFileByTemporaryFile(Request request, File file, File tmp) { - if (!tmp.exists()) { - return new Status(SERVER_ERROR_INTERNAL, "Can't replace the existing file without new content."); - } - - // Then delete the existing file - if (!IoUtils.delete(file)) { - cleanTemporaryFileIfUploadNotResumed(tmp); - return new Status(SERVER_ERROR_INTERNAL, "Unable to delete the existing file"); - } - - // Finally move the temporary file to the existing file location - if (tmp.renameTo(file)) { - if (request.isEntityAvailable()) { - return SUCCESS_NO_CONTENT; - } - return SUCCESS_OK; - } - - // Many aspects of the behavior of the method "renameTo" are inherently - // platform-dependent. - // The rename operation might not be able to move a file from one file system to - // another. - if (!tmp.exists()) { - return new Status(SERVER_ERROR_INTERNAL, "Unable to move the temporary file to replace the existing file"); - } - try { - Files.move(tmp.toPath(), file.toPath(), REPLACE_EXISTING); - } catch (IOException e) { - return new Status(SERVER_ERROR_INTERNAL, e, - "Unable to move the temporary file to replace the existing file"); - } - return SUCCESS_OK; - } - - private Status createFileWithPartialContent(Request request, File file, Range range) { - // This is a partial PUT - try (RandomAccessFile raf = new RandomAccessFile(file, "rwd")) { - // Go to the desired offset. - if (range.getIndex() == Range.INDEX_LAST) { - if (raf.length() <= range.getSize()) { - raf.seek(range.getSize()); - } else { - raf.seek(raf.length() - range.getSize()); - } - } else { - raf.seek(range.getIndex()); - } - // Write the entity to the file. - if (request.isEntityAvailable()) { - IoUtils.copy(request.getEntity().getStream(), raf); - return SUCCESS_CREATED; - } - return SUCCESS_NO_CONTENT; - } catch (IOException ioe) { - getLogger().log(WARNING, "Unable to create the new file", ioe); - return new Status(SERVER_ERROR_INTERNAL, ioe); - } - } - - private Status createFile(Request request, File file) { - // This is simple PUT of the full entity - try { - if (request.isEntityAvailable()) { - Files.copy(request.getEntity().getStream(), file.toPath()); - return SUCCESS_CREATED; - } - if (file.createNewFile()) { - return SUCCESS_NO_CONTENT; - } - } catch (IOException ioe) { - getLogger().log(WARNING, "Unable to create the new file", ioe); - return new Status(SERVER_ERROR_INTERNAL, ioe); - } - - String message = "Unable to create the new file"; - getLogger().warning(message); - return new Status(SERVER_ERROR_INTERNAL, message); - } - - private void cleanTemporaryFileIfUploadNotResumed(File tmp) { - if (tmp.exists() && !isResumeUpload()) { - IoUtils.delete(tmp); - } - } - - /** - * Indicates if a failed upload can be resumed. This will prevent the deletion - * of the temporary file created. Defaults to "false". - * - * @return True if a failed upload can be resumed, false otherwise. - */ - public boolean isResumeUpload() { - return Boolean.parseBoolean(getHelpedParameters().getFirstValue("resumeUpload", "false")); - } - - /** - * Complete the given file name with the extension corresponding to the given - * metadata. - * - * @param fileName The file name to complete. - * @param metadata The metadata. - */ - private void updateFileExtension(StringBuilder fileName, Metadata metadata) { - boolean defaultMetadata = true; - - if (getMetadataService() != null) { - if (metadata instanceof Language language) { - defaultMetadata = language.equals(getMetadataService().getDefaultLanguage()); - } else if (metadata instanceof MediaType mediaType) { - defaultMetadata = mediaType.equals(getMetadataService().getDefaultMediaType()); - } else if (metadata instanceof CharacterSet characterSet) { - defaultMetadata = characterSet.equals(getMetadataService().getDefaultCharacterSet()); - } else if (metadata instanceof Encoding encoding) { - defaultMetadata = encoding.equals(getMetadataService().getDefaultEncoding()); - } - } - - // We only add an extension for metadata that differs from default ones - if (!defaultMetadata) { - String extension = getMetadataService().getExtension(metadata); - - if (extension != null) { - fileName.append(".").append(extension); - } else { - if (metadata.getParent() != null) { - updateFileExtension(fileName, metadata.getParent()); - } - } - } - } + /** + * Constructor. + * + * @param client The client to help. + */ + public FileClientHelper(Client client) { + super(client); + getProtocols().add(FILE); + } + + /** + * Check that all extensions of the file correspond to a known metadata. + * + * @param file The file whose extensions are checked. + * @return True if the metadata service knows all extensions of the file. + */ + protected boolean checkExtensionsConsistency(File file) { + boolean knownExtension = true; + + Collection set = Entity.getExtensions(file.getName(), getMetadataService()); + Iterator iterator = set.iterator(); + while (iterator.hasNext() && knownExtension) { + knownExtension = getMetadataService().getMetadata(iterator.next()) != null; + } + + return knownExtension; + } + + /** + * Checks that the URI and the representation are compatible. The whole set of metadata of the + * representation must be included in the set of those of the URI + * + * @param fileName The name of the resource + * @param representation The provided representation. + * @return True if the metadata of the representation is compatible with the metadata extracted + * from the filename + */ + private boolean checkMetadataConsistency(String fileName, Representation representation) { + if (representation != null) { + Variant variant = new Variant(); + Entity.updateMetadata(fileName, variant, false, getMetadataService()); + + // "variant" contains the theoretical correct metadata + if (!variant.getLanguages().isEmpty() + && !representation.getLanguages().isEmpty() + && !new HashSet<>(variant.getLanguages()) + .containsAll(representation.getLanguages())) { + return false; + } + + if ((variant.getMediaType() != null) + && (representation.getMediaType() != null) + && !(variant.getMediaType().includes(representation.getMediaType()))) { + return false; + } + + return variant.getEncodings().isEmpty() + || representation.getEncodings().isEmpty() + || new HashSet<>(variant.getEncodings()) + .containsAll(representation.getEncodings()); + } + return true; + } + + @Override + public Entity getEntity(String decodedPath) { + return new FileEntity(getFileWithLocalizedPath(decodedPath), getMetadataService()); + } + + /** + * Returns a new {@link File} instance for the given path name.
+ * It ensures to translate the "/" to the local supported file separator (mainly useful for + * Windows OS). + * + * @param path The Path of the file + * @return a new {@link File} instance for the given path name. + */ + private static File getFileWithLocalizedPath(final String path) { + return new File(LocalReference.localizePath(path)); + } + + /** + * Returns the name of the extension to use to store the temporary content while uploading + * content via the PUT method. Defaults to "tmp". + * + * @return The name of the extension to use to store the temporary content. + */ + public String getTemporaryExtension() { + return getHelpedParameters().getFirstValue("temporaryExtension", "tmp"); + } + + @Override + protected void handleLocal(Request request, Response response, String decodedPath) { + String scheme = request.getResourceRef().getScheme(); + + if (!FILE.getSchemeName().equalsIgnoreCase(scheme)) { + throw new IllegalArgumentException( + format( + "Protocol \"%s\" not supported by the connector. Only FILE is supported.", + scheme)); + } + + handleFile(request, response, decodedPath); + } + + protected void handleFile(Request request, Response response, String decodedPath) { + final Directory directory = + (Directory) request.getAttributes().get("org.restlet.directory"); + final File fileWithLocalizedPath = getFileWithLocalizedPath(decodedPath); + + if (!isFileInDirectory(directory, fileWithLocalizedPath)) { + response.setStatus(CLIENT_ERROR_FORBIDDEN); + } else if (GET.equals(request.getMethod()) || HEAD.equals(request.getMethod())) { + handleEntityGet(request, response, getEntity(decodedPath)); + } else if (PUT.equals(request.getMethod())) { + handleFilePut(request, response, decodedPath, fileWithLocalizedPath); + } else if (DELETE.equals(request.getMethod())) { + handleFileDelete(response, fileWithLocalizedPath); + } else { + response.setStatus(CLIENT_ERROR_METHOD_NOT_ALLOWED); + response.getAllowedMethods().add(GET); + response.getAllowedMethods().add(HEAD); + response.getAllowedMethods().add(PUT); + response.getAllowedMethods().add(DELETE); + } + } + + /** + * Indicates whether the given file is located inside the root directory. + * + * @param directory The root directory + * @param file The file. + * @return True if the path is located under the root directory, false otherwise. + */ + private static boolean isFileInDirectory(final Directory directory, final File file) { + boolean result = true; + + if (directory != null) { + final String fileAbsolute = directory.getRootRef().getPath(true); + final String filePath; + + if (fileAbsolute.indexOf(':') == 2 || fileAbsolute.indexOf('|') == 2) { + filePath = fileAbsolute.substring(1); + } else { + filePath = fileAbsolute; + } + + final Path rootDirectoryPath = Paths.get(filePath).normalize(); + final Path actualFilePath = file.toPath().normalize(); + result = !rootDirectoryPath.relativize(actualFilePath).toString().startsWith(".."); + } + + return result; + } + + /** + * Handles a DELETE call for the FILE protocol. + * + * @param response The response to update. + * @param file The file or directory to delete. + */ + protected void handleFileDelete(Response response, File file) { + if (file.isDirectory()) { + final File[] files = file.listFiles(); + if (files == null || files.length == 0) { + if (IoUtils.delete(file)) { + response.setStatus(SUCCESS_NO_CONTENT); + } else { + response.setStatus(SERVER_ERROR_INTERNAL, "Couldn't delete the directory"); + } + } else { + response.setStatus( + CLIENT_ERROR_FORBIDDEN, "Couldn't delete the non-empty directory"); + } + } else { + if (IoUtils.delete(file)) { + response.setStatus(SUCCESS_NO_CONTENT); + } else { + response.setStatus(SERVER_ERROR_INTERNAL, "Couldn't delete the file"); + } + } + } + + /** + * Handles a PUT call for the FILE protocol. + * + * @param request The request to update. + * @param response The response to update. + * @param path The encoded path of the requested file or directory. + * @param file The requested file or directory. + */ + protected void handleFilePut(Request request, Response response, String path, File file) { + response.setStatus(doHandleFilePut(request, path, file)); + } + + private Status doHandleFilePut(Request request, String path, File file) { + // Handle directory + if (file.exists()) { + if (file.isDirectory()) { + return new Status( + CLIENT_ERROR_FORBIDDEN, "Can't put a new representation of a directory"); + } + } else if (path.endsWith("/")) { + // It seems the request targets a directory + // Create a new directory and its parents if necessary + if (file.mkdirs()) { + return SUCCESS_NO_CONTENT; + } else { + getLogger().warning("Unable to create the new directory"); + return new Status(SERVER_ERROR_INTERNAL, "Unable to create the new directory"); + } + } + + // Several checks: first the consistency of the metadata and the filename + if (!checkMetadataConsistency(file.getName(), request.getEntity())) { + // Ask the client to properly reiterate its request + return new Status( + CLIENT_ERROR_BAD_REQUEST, "The metadata is not consistent with the URI"); + } + + // We look for the possible variants + // Set up base name as the longest part of the name without known extensions + // (beginning from the left) + final String baseName = Entity.getBaseName(file.getName(), getMetadataService()); + + // Look for resources with the same base name + FileFilter filter = + file1 -> + file1.isFile() + && baseName.equals( + Entity.getBaseName(file1.getName(), getMetadataService())); + + File[] files = file.getParentFile().listFiles(filter); + File uniqueVariant = null; + List variantsList = new ArrayList<>(); + + if (files != null && files.length > 0) { + // Set the list of extensions, due to the file name and the + // default metadata. + // TODO It seems we could handle more clearly the equivalence + // between the file name space and the target resource (URI + // completed by default metadata) + Variant variant = new Variant(); + Entity.updateMetadata(file.getName(), variant, false, getMetadataService()); + Collection extensions = Entity.getExtensions(variant, getMetadataService()); + + for (File entry : files) { + Collection entryExtensions = + Entity.getExtensions(entry.getName(), getMetadataService()); + + if (entryExtensions.containsAll(extensions)) { + variantsList.add(entry); + + if (extensions.containsAll(entryExtensions)) { + // The right representation has been found. + uniqueVariant = entry; + } + } + } + } + + if (uniqueVariant != null) { + file = uniqueVariant; + } else { + if (!variantsList.isEmpty()) { + // Negotiated resource (several variants, but not the right one). + // Check if the request could be completed or not. + // The request could be more precise + return new Status( + CLIENT_ERROR_NOT_ACCEPTABLE, + "Unable to process properly the request. Several variants exist but none of them suits precisely."); + } + + // This resource does not exist, yet. Complete it with the + // default metadata + Entity.updateMetadata(file.getName(), request.getEntity(), true, getMetadataService()); + + // Update the URI + StringBuilder fileName = new StringBuilder(baseName); + + for (Language language : request.getEntity().getLanguages()) { + updateFileExtension(fileName, language); + } + + for (Encoding encoding : request.getEntity().getEncodings()) { + updateFileExtension(fileName, encoding); + } + + // It is important to finish with the media type as it is + // often leveraged by operating systems to detect a file type + updateFileExtension(fileName, request.getEntity().getMediaType()); + + file = new File(file.getParentFile(), fileName.toString()); + } + + // Before putting the file representation, we check that all the extensions are + // known + if (!checkExtensionsConsistency(file)) { + return new Status( + SERVER_ERROR_INTERNAL, + "Unable to process properly the URI. At least one extension is not known by the server."); + } + + // This helper supports only a single "bytes" range. + Range range = + (!request.getRanges().isEmpty() && isBytesRange(request.getRanges().getFirst())) + ? request.getRanges().getFirst() + : null; + + if (file.exists()) { + if (range != null) { + return updateFileWithPartialContent(request, file, range); + } + return replaceFile(request, file); + } else { + // The file does not exist yet. + File parent = file.getParentFile(); + + if ((parent != null) && !parent.exists() && !parent.mkdirs()) { + String message = "Unable to create the parent directory"; + getLogger().warning(message); + return new Status(SERVER_ERROR_INTERNAL, message); + } + + // Create the new file + if (range != null) { + return createFileWithPartialContent(request, file, range); + } + return createFile(request, file); + } + } + + /** The update is handled in two phases: write a temporary file, replace the target file. */ + private Status updateFileWithPartialContent(Request request, File file, Range range) { + File tmp = null; + + // Replace the content of the file. First, create a temporary file + try { + // The temporary file used for partial PUT. + tmp = new File(file.getCanonicalPath() + "." + getTemporaryExtension()); + + cleanTemporaryFileIfUploadNotResumed(tmp); + + if (!tmp.exists()) { + // Copy the target file. + Files.copy(file.toPath(), tmp.toPath()); + } + + updateRangeInFile(request, range, tmp); + + return replaceFileByTemporaryFile(request, file, tmp); + } catch (IOException ioe) { + getLogger().log(WARNING, "Unable to create the temporary file", ioe); + if (tmp != null) { + cleanTemporaryFileIfUploadNotResumed(tmp); + } + return new Status(SERVER_ERROR_INTERNAL, "Unable to create a temporary file"); + } + } + + private void updateRangeInFile(final Request request, final Range range, final File tmp) + throws IOException { + try (RandomAccessFile raf = new RandomAccessFile(tmp, "rwd")) { + // Go to the desired offset. + if (range.getIndex() == Range.INDEX_LAST) { + if (raf.length() <= range.getSize()) { + raf.seek(range.getSize()); + } else { + raf.seek(raf.length() - range.getSize()); + } + } else { + raf.seek(range.getIndex()); + } + + // Write the entity to the temporary file. + if (request.isEntityAvailable()) { + IoUtils.copy(request.getEntity().getStream(), raf); + } + } + } + + private Status replaceFile(Request request, File file) { + File tmp = null; + try { + tmp = File.createTempFile("restlet-upload", "bin"); + if (request.isEntityAvailable()) { + Files.copy(request.getEntity().getStream(), tmp.toPath(), REPLACE_EXISTING); + } + } catch (IOException ioe) { + getLogger().log(WARNING, "Unable to create the temporary file", ioe); + cleanTemporaryFileIfUploadNotResumed(tmp); + return new Status(SERVER_ERROR_INTERNAL, "Unable to create a temporary file"); + } + return replaceFileByTemporaryFile(request, file, tmp); + } + + private Status replaceFileByTemporaryFile(Request request, File file, File tmp) { + if (!tmp.exists()) { + return new Status( + SERVER_ERROR_INTERNAL, "Can't replace the existing file without new content."); + } + + // Then delete the existing file + if (!IoUtils.delete(file)) { + cleanTemporaryFileIfUploadNotResumed(tmp); + return new Status(SERVER_ERROR_INTERNAL, "Unable to delete the existing file"); + } + + // Finally, move the temporary file to the existing file location + if (tmp.renameTo(file)) { + if (request.isEntityAvailable()) { + return SUCCESS_NO_CONTENT; + } + return SUCCESS_OK; + } + + // Many aspects of the behavior of the method "renameTo" are inherently platform-dependent. + // The rename operation might not be able to move a file from one file system to another. + if (!tmp.exists()) { + return new Status( + SERVER_ERROR_INTERNAL, + "Unable to move the temporary file to replace the existing file"); + } + try { + Files.move(tmp.toPath(), file.toPath(), REPLACE_EXISTING); + } catch (IOException e) { + return new Status( + SERVER_ERROR_INTERNAL, + e, + "Unable to move the temporary file to replace the existing file"); + } + return SUCCESS_OK; + } + + private Status createFileWithPartialContent(Request request, File file, Range range) { + // This is a partial PUT + try (RandomAccessFile raf = new RandomAccessFile(file, "rwd")) { + // Go to the desired offset. + if (range.getIndex() == Range.INDEX_LAST) { + if (raf.length() <= range.getSize()) { + raf.seek(range.getSize()); + } else { + raf.seek(raf.length() - range.getSize()); + } + } else { + raf.seek(range.getIndex()); + } + // Write the entity to the file. + if (request.isEntityAvailable()) { + IoUtils.copy(request.getEntity().getStream(), raf); + return SUCCESS_CREATED; + } + return SUCCESS_NO_CONTENT; + } catch (IOException ioe) { + getLogger().log(WARNING, "Unable to create the new file", ioe); + return new Status(SERVER_ERROR_INTERNAL, ioe); + } + } + + private Status createFile(Request request, File file) { + // This is a simple PUT of the full entity + try { + if (request.isEntityAvailable()) { + Files.copy(request.getEntity().getStream(), file.toPath()); + return SUCCESS_CREATED; + } + if (file.createNewFile()) { + return SUCCESS_NO_CONTENT; + } + } catch (IOException ioe) { + getLogger().log(WARNING, "Unable to create the new file", ioe); + return new Status(SERVER_ERROR_INTERNAL, ioe); + } + + String message = "Unable to create the new file"; + getLogger().warning(message); + return new Status(SERVER_ERROR_INTERNAL, message); + } + + private void cleanTemporaryFileIfUploadNotResumed(File tmp) { + if (tmp != null && tmp.exists() && !isResumeUpload()) { + IoUtils.delete(tmp); + } + } + + /** + * Indicates if a failed upload can be resumed. This will prevent the deletion of the temporary + * file created. Defaults to "false". + * + * @return True if a failed upload can be resumed, false otherwise. + */ + public boolean isResumeUpload() { + return Boolean.parseBoolean(getHelpedParameters().getFirstValue("resumeUpload", "false")); + } + + /** + * Complete the given file name with the extension corresponding to the given metadata. + * + * @param fileName The file name to complete. + * @param metadata The metadata. + */ + private void updateFileExtension(StringBuilder fileName, Metadata metadata) { + boolean defaultMetadata = true; + + if (getMetadataService() != null) { + if (metadata instanceof Language language) { + defaultMetadata = language.equals(getMetadataService().getDefaultLanguage()); + } else if (metadata instanceof MediaType mediaType) { + defaultMetadata = mediaType.equals(getMetadataService().getDefaultMediaType()); + } else if (metadata instanceof CharacterSet characterSet) { + defaultMetadata = + characterSet.equals(getMetadataService().getDefaultCharacterSet()); + } else if (metadata instanceof Encoding encoding) { + defaultMetadata = encoding.equals(getMetadataService().getDefaultEncoding()); + } + } + + // We only add an extension for metadata that differs from default ones + if (!defaultMetadata) { + String extension = getMetadataService().getExtension(metadata); + + if (extension != null) { + fileName.append(".").append(extension); + } else { + if (metadata.getParent() != null) { + updateFileExtension(fileName, metadata.getParent()); + } + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/FileEntity.java b/org.restlet/src/main/java/org/restlet/engine/local/FileEntity.java index 43390c1f1c..ee6d539181 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/FileEntity.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/FileEntity.java @@ -1,94 +1,91 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import org.restlet.data.MediaType; import org.restlet.representation.FileRepresentation; import org.restlet.representation.Representation; import org.restlet.service.MetadataService; -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -/** - * Local entity based on a regular {@link File}. - */ +/** Local entity based on a regular {@link File}. */ public class FileEntity extends Entity { - /** The underlying regular file. */ - private final File file; - - /** - * Constructor. - * - * @param file The underlying file. - * @param metadataService The metadata service to use. - */ - public FileEntity(File file, MetadataService metadataService) { - super(metadataService); - this.file = file; - } - - @Override - public boolean exists() { - return getFile().exists(); - } - - @Override - public List getChildren() { - List result = null; - - if (isDirectory()) { - result = new ArrayList(); - - for (File f : getFile().listFiles()) { - result.add(new FileEntity(f, getMetadataService())); - } - } - - return result; - } - - /** - * Returns the underlying regular file. - * - * @return The underlying regular file. - */ - public File getFile() { - return file; - } - - @Override - public String getName() { - return getFile().getName(); - } - - @Override - public Entity getParent() { - File parentFile = getFile().getParentFile(); - return (parentFile == null) ? null : new FileEntity(parentFile, getMetadataService()); - } - - @Override - public Representation getRepresentation(MediaType defaultMediaType, int timeToLive) { - return new FileRepresentation(getFile(), defaultMediaType, timeToLive); - } - - @Override - public boolean isDirectory() { - return getFile().isDirectory(); - } - - @Override - public boolean isNormal() { - return getFile().isFile(); - } + /** The underlying regular file. */ + private final File file; + + /** + * Constructor. + * + * @param file The underlying file. + * @param metadataService The metadata service to use. + */ + public FileEntity(File file, MetadataService metadataService) { + super(metadataService); + this.file = file; + } + + @Override + public boolean exists() { + return getFile().exists(); + } + + @Override + public List getChildren() { + List result = null; + + if (isDirectory()) { + result = new ArrayList<>(); + + for (File f : Objects.requireNonNull(getFile().listFiles())) { + result.add(new FileEntity(f, getMetadataService())); + } + } + + return result; + } + + /** + * Returns the underlying regular file. + * + * @return The underlying regular file. + */ + public File getFile() { + return file; + } + + @Override + public String getName() { + return getFile().getName(); + } + + @Override + public Entity getParent() { + File parentFile = getFile().getParentFile(); + return (parentFile == null) ? null : new FileEntity(parentFile, getMetadataService()); + } + + @Override + public Representation getRepresentation(MediaType defaultMediaType, int timeToLive) { + return new FileRepresentation(getFile(), defaultMediaType, timeToLive); + } + + @Override + public boolean isDirectory() { + return getFile().isDirectory(); + } + + @Override + public boolean isNormal() { + return getFile().isFile(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/LocalClientHelper.java b/org.restlet/src/main/java/org/restlet/engine/local/LocalClientHelper.java index bf78dfc8d8..cb29e1ec7f 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/LocalClientHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/LocalClientHelper.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; import org.restlet.Client; @@ -16,9 +15,10 @@ import org.restlet.engine.connector.ClientHelper; /** - * Connector to the local resources accessible via file system, class loaders - * and similar mechanisms. Here is the list of parameters that are supported. - * They should be set in the Client's context before it is started: + * Connector to the local resources accessible via file system, class loaders, and similar + * mechanisms. Here is the list of parameters that are supported. They should be set in the Client's + * context before it is started: + * * * * @@ -43,76 +43,76 @@ * default language should be set, "" can be used. * *
list of supported parameters
- * + * * @see org.restlet.data.LocalReference * @author Jerome Louvel * @author Thierry Boileau */ public abstract class LocalClientHelper extends ClientHelper { - /** - * Constructor. Note that the common list of metadata associations based on - * extensions is added, see the addCommonExtensions() method. - * - * @param client The client to help. - */ - public LocalClientHelper(Client client) { - super(client); - } + /** + * Constructor. Note that the common list of metadata associations based on extensions is added, + * see the addCommonExtensions() method. + * + * @param client The client to help. + */ + protected LocalClientHelper(Client client) { + super(client); + } - /** - * Returns the default language. When no metadata service is available (simple - * client connector with no parent application), falls back on this default - * language. - * - * @return The default language. - */ - public String getDefaultLanguage() { - return getHelpedParameters().getFirstValue("defaultLanguage", ""); - } + /** + * Returns the default language. When no metadata service is available (simple client connector + * with no parent application), it falls back on this default language. + * + * @return The default language. + */ + public String getDefaultLanguage() { + return getHelpedParameters().getFirstValue("defaultLanguage", ""); + } - /** - * Returns the time to live for a file representation before it expires (in - * seconds). - * - * @return The time to live for a file representation before it expires (in - * seconds). - */ - public int getTimeToLive() { - return Integer.parseInt(getHelpedParameters().getFirstValue("timeToLive", "600")); - } + /** + * Returns the time to live for a file representation before it expires (in seconds). + * + * @return The time to live for a file representation before it expires (in seconds). + */ + public int getTimeToLive() { + return Integer.parseInt(getHelpedParameters().getFirstValue("timeToLive", "600")); + } - /** - * Handles a call. Note that this implementation will systematically normalize - * and URI-decode the resource reference. - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - public final void handle(Request request, Response response) { - // Ensure that all ".." and "." are normalized into the path - // to prevent unauthorized access to user directories. - request.getResourceRef().normalize(); + /** + * Handles a call. Note that this implementation will systematically normalize and URI-decode + * the resource reference. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public final void handle(Request request, Response response) { + // Ensure that all ".." and "." are normalized into the path + // to prevent unauthorized access to user directories. + request.getResourceRef().normalize(); - // As the path may be percent-encoded, it has to be percent-decoded. - // Then, all generated URIs must be encoded. - String path = request.getResourceRef().getPath(); - String decodedPath = Reference.decode(path); + // As the path may be percent-encoded, it has to be percent-decoded. + // Then, all generated URIs must be encoded. + String path = request.getResourceRef().getPath(); + String decodedPath = Reference.decode(path); - if (decodedPath != null) { - // Continue the local handling - handleLocal(request, response, decodedPath); - } else { - getLogger().warning("Unable to get the path of this local URI: " + request.getResourceRef()); - } - } + if (decodedPath != null) { + // Continue the local handling + handleLocal(request, response, decodedPath); + } else { + getLogger() + .warning( + "Unable to get the path of this local URI: " + + request.getResourceRef()); + } + } - /** - * Handles a local call. - * - * @param request The request to handle. - * @param response The response to update. - * @param decodedPath The decoded local path. - */ - protected abstract void handleLocal(Request request, Response response, String decodedPath); + /** + * Handles a local call. + * + * @param request The request to handle. + * @param response The response to update. + * @param decodedPath The decoded local path. + */ + protected abstract void handleLocal(Request request, Response response, String decodedPath); } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/RiapClientHelper.java b/org.restlet/src/main/java/org/restlet/engine/local/RiapClientHelper.java index e5668a6349..8d763fa909 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/RiapClientHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/RiapClientHelper.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; import org.restlet.Client; @@ -18,51 +17,58 @@ /** * Client connector for RIAP calls. Only the "component" authority is supported. - * + * * @author Thierry Boileau * @see Protocol#RIAP */ public class RiapClientHelper extends ClientHelper { - /** - * Constructor. - * - * @param client The client to help. - */ - public RiapClientHelper(Client client) { - super(client); - getProtocols().add(Protocol.RIAP); - } - - /** - * Handles a call. - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - public void handle(Request request, Response response) { - final String scheme = request.getResourceRef().getScheme(); + /** + * Constructor. + * + * @param client The client to help. + */ + public RiapClientHelper(Client client) { + super(client); + getProtocols().add(Protocol.RIAP); + } - if (Protocol.RIAP.getSchemeName().equalsIgnoreCase(scheme)) { - // Support only the "component" authority - LocalReference ref = new LocalReference(request.getResourceRef()); + /** + * Handles a call. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public void handle(Request request, Response response) { + final String scheme = request.getResourceRef().getScheme(); - if (ref.getRiapAuthorityType() == LocalReference.RIAP_COMPONENT) { - if (RiapServerHelper.instance != null && RiapServerHelper.instance.getContext() != null - && RiapServerHelper.instance.getContext().getClientDispatcher() != null) { - RiapServerHelper.instance.getContext().getClientDispatcher().handle(request, response); - } else { - super.handle(request, response); - } - } else { - throw new IllegalArgumentException("Authority \"" + ref.getAuthority() - + "\" not supported by the connector. Only \"component\" is supported."); - } - } else { - throw new IllegalArgumentException( - "Protocol \"" + scheme + "\" not supported by the connector. Only RIAP is supported."); - } + if (Protocol.RIAP.getSchemeName().equalsIgnoreCase(scheme)) { + // Support only the "component" authority + LocalReference ref = new LocalReference(request.getResourceRef()); - } + if (ref.getRiapAuthorityType() == LocalReference.RIAP_COMPONENT) { + if (RiapServerHelper.instance != null + && RiapServerHelper.instance.getContext() != null + && RiapServerHelper.instance.getContext().getClientDispatcher() != null) { + RiapServerHelper.instance + .getContext() + .getClientDispatcher() + .handle(request, response); + } else { + super.handle(request, response); + } + } else { + throw new IllegalArgumentException( + "Authority \"" + + ref.getAuthority() + + "\" not supported by the connector. Only \"component\" is supported."); + } + } else { + throw new IllegalArgumentException( + "Protocol \"" + + scheme + + "\" not supported by the connector. Only RIAP is supported."); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/RiapServerHelper.java b/org.restlet/src/main/java/org/restlet/engine/local/RiapServerHelper.java index cd34b0046f..7d9d3cbd71 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/RiapServerHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/RiapServerHelper.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; import org.restlet.Server; @@ -14,32 +13,31 @@ import org.restlet.engine.connector.ServerHelper; /** - * Server connector handling RIAP calls. By design, there is only one instance - * by JVM. - * + * Server connector handling RIAP calls. By design, there is only one instance by JVM. + * * @author Thierry Boileau */ public class RiapServerHelper extends ServerHelper { - /** The unique registered helper. */ - public static RiapServerHelper instance = null; + /** The unique registered helper. */ + public static RiapServerHelper instance = null; - /** - * Constructor. - * - * @param server The server to help. - */ - public RiapServerHelper(Server server) { - super(server); - getProtocols().add(Protocol.RIAP); + /** + * Constructor. + * + * @param server The server to help. + */ + public RiapServerHelper(Server server) { + super(server); + getProtocols().add(Protocol.RIAP); - // Lazy initialization with double-check. - if (server != null && RiapServerHelper.instance == null) { - synchronized (this.getClass()) { - if (RiapServerHelper.instance == null) { - RiapServerHelper.instance = this; - } - } - } - } + // Lazy initialization with double-check. + if (server != null && RiapServerHelper.instance == null) { + synchronized (RiapServerHelper.class) { + if (RiapServerHelper.instance == null) { + RiapServerHelper.instance = this; + } + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/ZipClientHelper.java b/org.restlet/src/main/java/org/restlet/engine/local/ZipClientHelper.java index da75259526..9753645986 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/ZipClientHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/ZipClientHelper.java @@ -1,319 +1,341 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; import org.restlet.Client; import org.restlet.Request; import org.restlet.Response; -import org.restlet.data.*; +import org.restlet.data.LocalReference; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.data.ReferenceList; +import org.restlet.data.Status; import org.restlet.engine.io.IoUtils; import org.restlet.representation.Representation; import org.restlet.service.MetadataService; -import java.io.*; -import java.util.Collection; -import java.util.Enumeration; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; - /** - * ZIP and JAR client connector. Only works for archives available as local - * files.
+ * ZIP and JAR client connector. Only works for archives available as local files.
*
- * Handles GET, HEAD and PUT request on resources referenced as : - * zip:file:// + * Handles GET, HEAD, and PUT request on resources referenced as: zip:file:// * * @author Remi Dewitte remi@gide.net */ public class ZipClientHelper extends LocalClientHelper { - /** - * Constructor. - * - * @param client The helped client. - */ - public ZipClientHelper(Client client) { - super(client); - getProtocols().add(Protocol.ZIP); - getProtocols().add(Protocol.JAR); - } + /** + * Constructor. + * + * @param client The helped client. + */ + public ZipClientHelper(Client client) { + super(client); + getProtocols().add(Protocol.ZIP); + getProtocols().add(Protocol.JAR); + } - /** - * Handles a call for a local entity. By default, only GET and HEAD methods are - * implemented. - * - * @param request The request to handle. - * @param response The response to update. - * @param decodedPath The URL decoded entity path. - */ - @Override - protected void handleLocal(Request request, Response response, String decodedPath) { - int spi = decodedPath.indexOf("!/"); - String fileUri; - String entryName; - if (spi != -1) { - fileUri = decodedPath.substring(0, spi); - entryName = decodedPath.substring(spi + 2); - } else { - fileUri = decodedPath; - entryName = ""; - } + /** + * Handles a call for a local entity. By default, only GET and HEAD methods are implemented. + * + * @param request The request to handle. + * @param response The response to update. + * @param decodedPath The URL decoded entity path. + */ + @Override + protected void handleLocal(Request request, Response response, String decodedPath) { + int spi = decodedPath.indexOf("!/"); + String fileUri; + String entryName; + if (spi != -1) { + fileUri = decodedPath.substring(0, spi); + entryName = decodedPath.substring(spi + 2); + } else { + fileUri = decodedPath; + entryName = ""; + } - LocalReference fileRef = new LocalReference(fileUri); - if (Protocol.FILE.equals(fileRef.getSchemeProtocol())) { - final File file = fileRef.getFile(); - if (Method.GET.equals(request.getMethod()) || Method.HEAD.equals(request.getMethod())) { - handleGet(request, response, file, entryName, getMetadataService()); - } else if (Method.PUT.equals(request.getMethod())) { - handlePut(request, response, file, entryName); - } else { - response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - response.getAllowedMethods().add(Method.GET); - response.getAllowedMethods().add(Method.HEAD); - response.getAllowedMethods().add(Method.PUT); - } - } else { - response.setStatus(Status.SERVER_ERROR_NOT_IMPLEMENTED, "Only works on local files."); - } - } + LocalReference fileRef = new LocalReference(fileUri); + if (Protocol.FILE.equals(fileRef.getSchemeProtocol())) { + final File file = fileRef.getFile(); + if (Method.GET.equals(request.getMethod()) || Method.HEAD.equals(request.getMethod())) { + handleGet(request, response, file, entryName, getMetadataService()); + } else if (Method.PUT.equals(request.getMethod())) { + handlePut(request, response, file, entryName); + } else { + response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + response.getAllowedMethods().add(Method.GET); + response.getAllowedMethods().add(Method.HEAD); + response.getAllowedMethods().add(Method.PUT); + } + } else { + response.setStatus(Status.SERVER_ERROR_NOT_IMPLEMENTED, "Only works on local files."); + } + } - /** - * Handles a GET call. - * - * @param request The request to answer. - * @param response The response to update. - * @param file The Zip archive file. - * @param entryName The Zip archive entry name. - * @param metadataService The metadata service. - */ - protected void handleGet(Request request, Response response, File file, String entryName, - final MetadataService metadataService) { + /** + * Handles a GET call. + * + * @param request The request to answer. + * @param response The response to update. + * @param file The Zip archive file. + * @param entryName The Zip archive entry name. + * @param metadataService The metadata service. + */ + protected void handleGet( + Request request, + Response response, + File file, + String entryName, + final MetadataService metadataService) { - if (!file.exists()) { - response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); - } else { - ZipFile zipFile; + if (!file.exists()) { + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + } else { + ZipFile zipFile; - try { - zipFile = new ZipFile(file); - } catch (Exception e) { - response.setStatus(Status.SERVER_ERROR_INTERNAL, e); - return; - } + try { + zipFile = new ZipFile(file); + } catch (Exception e) { + response.setStatus(Status.SERVER_ERROR_INTERNAL, e); + return; + } - Entity entity = new ZipEntryEntity(zipFile, entryName, metadataService); - if (!entity.exists()) { - response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); - } else { - final Representation output; + Entity entity = new ZipEntryEntity(zipFile, entryName, metadataService); + if (!entity.exists()) { + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + } else { + final Representation output; - if (entity.isDirectory()) { - // Return the directory listing - final Collection children = entity.getChildren(); - final ReferenceList rl = new ReferenceList(children.size()); - String fileUri = LocalReference.createFileReference(file).toString(); - String scheme = request.getResourceRef().getScheme(); - String baseUri = scheme + ":" + fileUri + "!/"; + if (entity.isDirectory()) { + // Return the directory listing + final Collection children = entity.getChildren(); + final ReferenceList rl = new ReferenceList(children.size()); + String fileUri = LocalReference.createFileReference(file).toString(); + String scheme = request.getResourceRef().getScheme(); + String baseUri = scheme + ":" + fileUri + "!/"; - for (final Entity entry : children) { - rl.add(baseUri + entry.getName()); - } + for (final Entity entry : children) { + rl.add(baseUri + entry.getName()); + } - output = rl.getTextRepresentation(); + output = rl.getTextRepresentation(); - try { - zipFile.close(); - } catch (IOException e) { - // Do something ??? - } - } else { - // Return the file content - output = entity.getRepresentation(metadataService.getDefaultMediaType(), getTimeToLive()); - output.setLocationRef(request.getResourceRef()); - Entity.updateMetadata(entity.getName(), output, true, getMetadataService()); - } + try { + zipFile.close(); + } catch (IOException e) { + // Do something ??? + } + } else { + // Return the file content + output = + entity.getRepresentation( + metadataService.getDefaultMediaType(), getTimeToLive()); + output.setLocationRef(request.getResourceRef()); + Entity.updateMetadata(entity.getName(), output, true, getMetadataService()); + } - response.setStatus(Status.SUCCESS_OK); - response.setEntity(output); - } - } - } + response.setStatus(Status.SUCCESS_OK); + response.setEntity(output); + } + } + } - /** - * Handles a PUT call. - * - * @param request The request to answer. - * @param response The response to update. - * @param file The Zip archive file. - * @param entryName The Zip archive entry name. - */ - protected void handlePut(Request request, Response response, File file, String entryName) { - boolean zipExists = file.exists(); - ZipOutputStream zipOut = null; + /** + * Handles a PUT call. + * + * @param request The request to answer. + * @param response The response to update. + * @param file The Zip archive file. + * @param entryName The Zip archive entry name. + */ + protected void handlePut(Request request, Response response, File file, String entryName) { + boolean zipExists = file.exists(); + ZipOutputStream zipOut = null; - if ("".equals(entryName) && request.getEntity() != null && request.getEntity().getDisposition() != null) { - entryName = request.getEntity().getDisposition().getFilename(); - } - if (entryName == null) { - response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Must specify an entry name."); - return; - } - // boolean canAppend = true; - boolean canAppend = !zipExists; - boolean isDirectory = entryName.endsWith("/"); - boolean wrongReplace = false; - try { - if (zipExists) { - ZipFile zipFile = new ZipFile(file); - // Already exists ? - canAppend &= null == zipFile.getEntry(entryName); - // Directory with the same name ? - if (isDirectory) { - wrongReplace = null != zipFile.getEntry(entryName.substring(0, entryName.length() - 1)); - } else { - wrongReplace = null != zipFile.getEntry(entryName + "/"); - } + if ("".equals(entryName) + && request.getEntity() != null + && request.getEntity().getDisposition() != null) { + entryName = request.getEntity().getDisposition().getFilename(); + } + if (entryName == null) { + response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Must specify an entry name."); + return; + } + // boolean canAppend = true; + boolean canAppend = !zipExists; + boolean isDirectory = entryName.endsWith("/"); + boolean wrongReplace = false; + try { + if (zipExists) { + ZipFile zipFile = new ZipFile(file); + // Already exists ? + canAppend &= null == zipFile.getEntry(entryName); + // Directory with the same name ? + if (isDirectory) { + wrongReplace = + null + != zipFile.getEntry( + entryName.substring(0, entryName.length() - 1)); + } else { + wrongReplace = null != zipFile.getEntry(entryName + "/"); + } - canAppend &= !wrongReplace; - zipFile.close(); - } + canAppend &= !wrongReplace; + zipFile.close(); + } - Representation entity; - if (isDirectory) { - entity = null; - } else { - entity = request.getEntity(); - } + Representation entity; + if (isDirectory) { + entity = null; + } else { + entity = request.getEntity(); + } - if (canAppend) { - try { - // zipOut = new ZipOutputStream(new BufferedOutputStream(new - // FileOutputStream(file, true))); - zipOut = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file))); - writeEntityStream(entity, zipOut, entryName); - zipOut.close(); - } catch (Exception e) { - response.setStatus(Status.SERVER_ERROR_INTERNAL, e); - return; - } finally { - if (zipOut != null) { - zipOut.close(); - } - } - response.setStatus(Status.SUCCESS_CREATED); - } else { - if (wrongReplace) { - response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, - "Directory cannot be replaced by a file or file by a directory."); - } else { - File writeTo; - ZipFile zipFile = null; - try { - writeTo = File.createTempFile("restlet_zip_", "zip"); - zipFile = new ZipFile(file); - zipOut = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(writeTo))); - Enumeration entries = zipFile.entries(); - boolean replaced = false; - while (entries.hasMoreElements()) { - ZipEntry e = entries.nextElement(); - if (!replaced && entryName.equals(e.getName())) { - writeEntityStream(entity, zipOut, entryName); - replaced = true; - } else { - zipOut.putNextEntry(e); - try (final BufferedInputStream zipStream = new BufferedInputStream( - zipFile.getInputStream(e))) { - IoUtils.copy(zipStream, zipOut); - } - zipOut.closeEntry(); - } - } - if (!replaced) { - writeEntityStream(entity, zipOut, entryName); - } - zipFile.close(); - zipOut.close(); - } finally { - try { - if (zipFile != null) { - zipFile.close(); - } - } finally { - if (zipOut != null) { - zipOut.close(); - } - } - } + if (canAppend) { + try { + // zipOut = new ZipOutputStream(new BufferedOutputStream(new + // FileOutputStream(file, true))); + zipOut = + new ZipOutputStream( + new BufferedOutputStream(new FileOutputStream(file))); + writeEntityStream(entity, zipOut, entryName); + zipOut.close(); + } catch (Exception e) { + response.setStatus(Status.SERVER_ERROR_INTERNAL, e); + return; + } finally { + if (zipOut != null) { + zipOut.close(); + } + } + response.setStatus(Status.SUCCESS_CREATED); + } else { + if (wrongReplace) { + response.setStatus( + Status.CLIENT_ERROR_BAD_REQUEST, + "Directory cannot be replaced by a file or file by a directory."); + } else { + File writeTo; + ZipFile zipFile = null; + try { + writeTo = File.createTempFile("restlet_zip_", "zip"); + zipFile = new ZipFile(file); + zipOut = + new ZipOutputStream( + new BufferedOutputStream(new FileOutputStream(writeTo))); + Enumeration entries = zipFile.entries(); + boolean replaced = false; + while (entries.hasMoreElements()) { + ZipEntry e = entries.nextElement(); + if (!replaced && entryName.equals(e.getName())) { + writeEntityStream(entity, zipOut, entryName); + replaced = true; + } else { + zipOut.putNextEntry(e); + try (final BufferedInputStream zipStream = + new BufferedInputStream(zipFile.getInputStream(e))) { + IoUtils.copy(zipStream, zipOut); + } + zipOut.closeEntry(); + } + } + if (!replaced) { + writeEntityStream(entity, zipOut, entryName); + } + zipFile.close(); + zipOut.close(); + } finally { + try { + if (zipFile != null) { + zipFile.close(); + } + } finally { + if (zipOut != null) { + zipOut.close(); + } + } + } - if (!(IoUtils.delete(file) && writeTo.renameTo(file))) { - if (!file.exists()) { - file.createNewFile(); - } - FileInputStream fis = null; - FileOutputStream fos = null; - try { - fis = new FileInputStream(writeTo); - fos = new FileOutputStream(file); - // ByteUtils.write(fis.getChannel(), - // fos.getChannel()); - IoUtils.copy(fis, fos); - response.setStatus(Status.SUCCESS_OK); - } finally { - try { - if (fis != null) { - fis.close(); - } - } finally { - if (fos != null) { - fos.close(); - } - } - } - } else { - response.setStatus(Status.SUCCESS_OK); - } - } - } - } catch (Exception e) { - response.setStatus(Status.SERVER_ERROR_INTERNAL, e); - } - } + if (!(IoUtils.delete(file) && writeTo.renameTo(file))) { + if (!file.exists()) { + file.createNewFile(); + } + FileInputStream fis = null; + FileOutputStream fos = null; + try { + fis = new FileInputStream(writeTo); + fos = new FileOutputStream(file); + // ByteUtils.write(fis.getChannel(), + // fos.getChannel()); + IoUtils.copy(fis, fos); + response.setStatus(Status.SUCCESS_OK); + } finally { + try { + if (fis != null) { + fis.close(); + } + } finally { + if (fos != null) { + fos.close(); + } + } + } + } else { + response.setStatus(Status.SUCCESS_OK); + } + } + } + } catch (Exception e) { + response.setStatus(Status.SERVER_ERROR_INTERNAL, e); + } + } - /** - * Writes an entity to a given ZIP output stream with a given ZIP entry name. - * - * @param entity The entity to write. - * @param out The ZIP output stream. - * @param entryName The ZIP entry name. - * @return True if the writing was successful. - * @throws IOException - */ - private boolean writeEntityStream(Representation entity, ZipOutputStream out, String entryName) throws IOException { - if (entity != null && !entryName.endsWith("/")) { - ZipEntry entry = new ZipEntry(entryName); - if (entity.getModificationDate() != null) { - entry.setTime(entity.getModificationDate().getTime()); - } else { - entry.setTime(System.currentTimeMillis()); - } - out.putNextEntry(entry); - try (final BufferedInputStream entityStream = new BufferedInputStream(entity.getStream())) { - IoUtils.copy(entityStream, out); - } - out.closeEntry(); - return true; - } + /** + * Writes an entity to a given ZIP output stream with a given ZIP entry name. + * + * @param entity The entity to write. + * @param out The ZIP output stream. + * @param entryName The ZIP entry name. + * @return True if the writing was successful. + * @throws IOException + */ + private boolean writeEntityStream(Representation entity, ZipOutputStream out, String entryName) + throws IOException { + if (entity != null && !entryName.endsWith("/")) { + ZipEntry entry = new ZipEntry(entryName); + if (entity.getModificationDate() != null) { + entry.setTime(entity.getModificationDate().getTime()); + } else { + entry.setTime(System.currentTimeMillis()); + } + out.putNextEntry(entry); + try (final BufferedInputStream entityStream = + new BufferedInputStream(entity.getStream())) { + IoUtils.copy(entityStream, out); + } + out.closeEntry(); + return true; + } - out.putNextEntry(new ZipEntry(entryName)); - out.closeEntry(); - return false; - } + out.putNextEntry(new ZipEntry(entryName)); + out.closeEntry(); + return false; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/ZipEntryEntity.java b/org.restlet/src/main/java/org/restlet/engine/local/ZipEntryEntity.java index ac5e0a6937..ebaff61f99 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/ZipEntryEntity.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/ZipEntryEntity.java @@ -1,130 +1,121 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; -import org.restlet.data.MediaType; -import org.restlet.representation.Representation; -import org.restlet.service.MetadataService; - import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import org.restlet.data.MediaType; +import org.restlet.representation.Representation; +import org.restlet.service.MetadataService; /** * Local entity based on an entry in a Zip archive. - * + * * @author Remi Dewitte remi@gide.net */ public class ZipEntryEntity extends Entity { - /** The Zip entry. */ - protected final ZipEntry entry; - - /** The Zip file. */ - protected final ZipFile zipFile; - - /** - * Constructor. - * - * @param zipFile The Zip file. - * @param entryName The Zip entry name. - * @param metadataService The metadata service to use. - */ - public ZipEntryEntity(ZipFile zipFile, String entryName, MetadataService metadataService) { - super(metadataService); - this.zipFile = zipFile; - ZipEntry entry = zipFile.getEntry(entryName); - if (entry == null) - this.entry = new ZipEntry(entryName); - else { - // Checking we don't have a directory - ZipEntry entryDir = zipFile.getEntry(entryName + "/"); - if (entryDir != null) - this.entry = entryDir; - else - this.entry = entry; - } - } - - /** - * Constructor. - * - * @param zipFile The Zip file. - * @param entry The Zip entry. - * @param metadataService The metadata service to use. - */ - public ZipEntryEntity(ZipFile zipFile, ZipEntry entry, MetadataService metadataService) { - super(metadataService); - this.zipFile = zipFile; - this.entry = entry; - } - - @Override - public boolean exists() { - if ("".equals(getName())) - return true; - // ZipEntry re = zipFile.getEntry(entry.getName()); - // return re != null; - return entry.getSize() != -1; - } - - @Override - public List getChildren() { - List result = null; - - if (isDirectory()) { - result = new ArrayList(); - Enumeration entries = zipFile.entries(); - String n = entry.getName(); - while (entries.hasMoreElements()) { - ZipEntry e = entries.nextElement(); - if (e.getName().startsWith(n) && e.getName().length() != n.length()) - result.add(new ZipEntryEntity(zipFile, e, getMetadataService())); - } - } - - return result; - } - - @Override - public String getName() { - return entry.getName(); - } - - @Override - public Entity getParent() { - if (entry.getName().isEmpty()) - return null; - - String n = entry.getName(); - String pn = n.substring(0, n.lastIndexOf('/') + 1); - return new ZipEntryEntity(zipFile, zipFile.getEntry(pn), getMetadataService()); - } - - @Override - public Representation getRepresentation(MediaType defaultMediaType, int timeToLive) { - return new ZipEntryRepresentation(defaultMediaType, zipFile, entry, timeToLive); - } - - @Override - public boolean isDirectory() { - if (entry.getName().isEmpty()) - return true; - return entry.isDirectory(); - } - - @Override - public boolean isNormal() { - return !entry.isDirectory(); - } - + /** The Zip entry. */ + protected final ZipEntry entry; + + /** The Zip file. */ + protected final ZipFile zipFile; + + /** + * Constructor. + * + * @param zipFile The Zip file. + * @param entryName The Zip entry name. + * @param metadataService The metadata service to use. + */ + public ZipEntryEntity(ZipFile zipFile, String entryName, MetadataService metadataService) { + super(metadataService); + this.zipFile = zipFile; + ZipEntry entry = zipFile.getEntry(entryName); + if (entry == null) this.entry = new ZipEntry(entryName); + else { + // Checking we don't have a directory + ZipEntry entryDir = zipFile.getEntry(entryName + "/"); + if (entryDir != null) this.entry = entryDir; + else this.entry = entry; + } + } + + /** + * Constructor. + * + * @param zipFile The Zip file. + * @param entry The Zip entry. + * @param metadataService The metadata service to use. + */ + public ZipEntryEntity(ZipFile zipFile, ZipEntry entry, MetadataService metadataService) { + super(metadataService); + this.zipFile = zipFile; + this.entry = entry; + } + + @Override + public boolean exists() { + if ("".equals(getName())) return true; + // ZipEntry re = zipFile.getEntry(entry.getName()); + // return re != null; + return entry.getSize() != -1; + } + + @Override + public List getChildren() { + List result = null; + + if (isDirectory()) { + result = new ArrayList<>(); + Enumeration entries = zipFile.entries(); + String n = entry.getName(); + while (entries.hasMoreElements()) { + ZipEntry e = entries.nextElement(); + if (e.getName().startsWith(n) && e.getName().length() != n.length()) + result.add(new ZipEntryEntity(zipFile, e, getMetadataService())); + } + } + + return result; + } + + @Override + public String getName() { + return entry.getName(); + } + + @Override + public Entity getParent() { + if (entry.getName().isEmpty()) return null; + + String n = entry.getName(); + String pn = n.substring(0, n.lastIndexOf('/') + 1); + return new ZipEntryEntity(zipFile, zipFile.getEntry(pn), getMetadataService()); + } + + @Override + public Representation getRepresentation(MediaType defaultMediaType, int timeToLive) { + return new ZipEntryRepresentation(defaultMediaType, zipFile, entry, timeToLive); + } + + @Override + public boolean isDirectory() { + if (entry.getName().isEmpty()) return true; + return entry.isDirectory(); + } + + @Override + public boolean isNormal() { + return !entry.isDirectory(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/ZipEntryRepresentation.java b/org.restlet/src/main/java/org/restlet/engine/local/ZipEntryRepresentation.java index be51e61604..0dcafbf827 100644 --- a/org.restlet/src/main/java/org/restlet/engine/local/ZipEntryRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/engine/local/ZipEntryRepresentation.java @@ -1,83 +1,80 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.local; -import org.restlet.data.Disposition; -import org.restlet.data.MediaType; -import org.restlet.engine.io.IoUtils; -import org.restlet.representation.StreamRepresentation; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Date; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import org.restlet.data.Disposition; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; +import org.restlet.representation.StreamRepresentation; /** * An entry in a Zip/JAR file. - * - * It is very important {@link #release()} is called to close the underlying Zip - * file. - * + * + *

It is very important {@link #release()} is called to close the underlying Zip file. + * * @author Remi Dewitte remi@gide.net */ public class ZipEntryRepresentation extends StreamRepresentation { - /** The Zip entry. */ - protected final ZipEntry entry; - - /** The Zip file. */ - protected final ZipFile zipFile; + /** The Zip entry. */ + protected final ZipEntry entry; - /** - * Constructor. - * - * @param mediaType The entry media type. - * @param zipFile The parent Zip archive file. - * @param entry The Zip entry. - * @param timeToLive The time to live before it expires (in seconds). - */ - public ZipEntryRepresentation(MediaType mediaType, ZipFile zipFile, ZipEntry entry, int timeToLive) { - super(mediaType); - this.zipFile = zipFile; - this.entry = entry; - Disposition disposition = new Disposition(); - disposition.setFilename(entry.getName()); - this.setDisposition(disposition); - setSize(entry.getSize()); - setModificationDate(new Date(entry.getTime())); + /** The Zip file. */ + protected final ZipFile zipFile; - if (timeToLive == 0) { - setExpirationDate(null); - } else if (timeToLive > 0) { - setExpirationDate(new Date(System.currentTimeMillis() + (1000L * timeToLive))); - } - } + /** + * Constructor. + * + * @param mediaType The entry media type. + * @param zipFile The parent Zip archive file. + * @param entry The Zip entry. + * @param timeToLive The time to live before it expires (in seconds). + */ + public ZipEntryRepresentation( + MediaType mediaType, ZipFile zipFile, ZipEntry entry, int timeToLive) { + super(mediaType); + this.zipFile = zipFile; + this.entry = entry; + Disposition disposition = new Disposition(); + disposition.setFilename(entry.getName()); + this.setDisposition(disposition); + setSize(entry.getSize()); + setModificationDate(new Date(entry.getTime())); - @Override - public InputStream getStream() throws IOException { - return zipFile.getInputStream(entry); - } + if (timeToLive == 0) { + setExpirationDate(null); + } else if (timeToLive > 0) { + setExpirationDate(new Date(System.currentTimeMillis() + (1000L * timeToLive))); + } + } - @Override - public void release() { - try { - zipFile.close(); - } catch (IOException e) { - } - } + @Override + public InputStream getStream() throws IOException { + return zipFile.getInputStream(entry); + } - @Override - public void write(OutputStream outputStream) throws IOException { - IoUtils.copy(getStream(), outputStream); - } + @Override + public void release() { + try { + zipFile.close(); + } catch (IOException e) { + } + } + @Override + public void write(OutputStream outputStream) throws IOException { + IoUtils.copy(getStream(), outputStream); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/local/package-info.java b/org.restlet/src/main/java/org/restlet/engine/local/package-info.java new file mode 100644 index 0000000000..8e20284f25 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/local/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports local connectors and resources. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.local; diff --git a/org.restlet/src/main/java/org/restlet/engine/local/package.html b/org.restlet/src/main/java/org/restlet/engine/local/package.html deleted file mode 100644 index 00ba49b8e2..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/local/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Supports local connectors and resources. -

-@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/log/AccessLogFileHandler.java b/org.restlet/src/main/java/org/restlet/engine/log/AccessLogFileHandler.java index f786238b5b..2e1b9d4d1a 100644 --- a/org.restlet/src/main/java/org/restlet/engine/log/AccessLogFileHandler.java +++ b/org.restlet/src/main/java/org/restlet/engine/log/AccessLogFileHandler.java @@ -1,95 +1,92 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.log; import java.io.IOException; /** - * Log file handler that uses the {@link AccessLogFormatter} by default. Also - * useful in configuration files to differentiate from the - * {@link java.util.logging.FileHandler}. - * + * Log file handler that uses the {@link AccessLogFormatter} by default. Also, useful in + * configuration files to differentiate from the {@link java.util.logging.FileHandler}. + * * @author Jerome Louvel */ public class AccessLogFileHandler extends java.util.logging.FileHandler { - /** - * Constructor. - * - * @throws IOException - * @throws SecurityException - */ - public AccessLogFileHandler() throws IOException, SecurityException { - super(); - init(); - } - - /** - * Constructor. - * - * @param pattern The name of the output file. - * @throws IOException - * @throws SecurityException - */ - public AccessLogFileHandler(String pattern) throws IOException, SecurityException { - super(pattern); - init(); - } + /** + * Constructor. + * + * @throws IOException + * @throws SecurityException + */ + public AccessLogFileHandler() throws IOException, SecurityException { + super(); + init(); + } - /** - * Constructor. - * - * @param pattern The name of the output file. - * @param append Specifies append mode. - * @throws IOException - * @throws SecurityException - */ - public AccessLogFileHandler(String pattern, boolean append) throws IOException, SecurityException { - super(pattern, append); - init(); - } + /** + * Constructor. + * + * @param pattern The name of the output file. + * @throws IOException + * @throws SecurityException + */ + public AccessLogFileHandler(String pattern) throws IOException, SecurityException { + super(pattern); + init(); + } - /** - * Constructor. - * - * @param pattern The name of the output file. - * @param limit The maximum number of bytes to write to any one file. - * @param count The number of files to use. - * @throws IOException - * @throws SecurityException - */ - public AccessLogFileHandler(String pattern, int limit, int count) throws IOException, SecurityException { - super(pattern, limit, count); - init(); - } + /** + * Constructor. + * + * @param pattern The name of the output file. + * @param append Specifies append mode. + * @throws IOException + * @throws SecurityException + */ + public AccessLogFileHandler(String pattern, boolean append) + throws IOException, SecurityException { + super(pattern, append); + init(); + } - /** - * Constructor. - * - * @param pattern The name of the output file. - * @param limit The maximum number of bytes to write to any one file. - * @param count The number of files to use. - * @param append Specifies append mode. - * @throws IOException - * @throws SecurityException - */ - public AccessLogFileHandler(String pattern, int limit, int count, boolean append) - throws IOException, SecurityException { - super(pattern, limit, count, append); - init(); - } + /** + * Constructor. + * + * @param pattern The name of the output file. + * @param limit The maximum number of bytes to write to any one file. + * @param count The number of files to use. + * @throws IOException + * @throws SecurityException + */ + public AccessLogFileHandler(String pattern, int limit, int count) + throws IOException, SecurityException { + super(pattern, limit, count); + init(); + } - /** - * Initialization code common to all constructors. - */ - protected void init() { - setFormatter(new AccessLogFormatter()); - } + /** + * Constructor. + * + * @param pattern The name of the output file. + * @param limit The maximum number of bytes to write to any one file. + * @param count The number of files to use. + * @param append Specifies append mode. + * @throws IOException + * @throws SecurityException + */ + public AccessLogFileHandler(String pattern, int limit, int count, boolean append) + throws IOException, SecurityException { + super(pattern, limit, count, append); + init(); + } + /** Initialization code common to all constructors. */ + protected void init() { + setFormatter(new AccessLogFormatter()); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/log/AccessLogFormatter.java b/org.restlet/src/main/java/org/restlet/engine/log/AccessLogFormatter.java index 5166e0bd21..76f0255746 100644 --- a/org.restlet/src/main/java/org/restlet/engine/log/AccessLogFormatter.java +++ b/org.restlet/src/main/java/org/restlet/engine/log/AccessLogFormatter.java @@ -1,28 +1,25 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.log; import java.util.logging.Formatter; import java.util.logging.LogRecord; /** - * Log record formatter which simply outputs the message on a new line. Useful - * for Web-style logs. - * + * Log record formatter which simply outputs the message on a new line. Useful for Web-style logs. + * * @author Jerome Louvel */ public class AccessLogFormatter extends Formatter { - @Override - public String format(LogRecord logRecord) { - return logRecord.getMessage() + '\n'; - } - + @Override + public String format(LogRecord logRecord) { + return logRecord.getMessage() + '\n'; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/log/DefaultAccessLogFormatter.java b/org.restlet/src/main/java/org/restlet/engine/log/DefaultAccessLogFormatter.java index 4a77c7cf99..30518a7133 100644 --- a/org.restlet/src/main/java/org/restlet/engine/log/DefaultAccessLogFormatter.java +++ b/org.restlet/src/main/java/org/restlet/engine/log/DefaultAccessLogFormatter.java @@ -1,42 +1,38 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.log; -import org.restlet.engine.Engine; - import java.util.logging.Handler; +import org.restlet.engine.Engine; /** - * Access log record formatter which writes a header describing the default log - * format. - * + * Access log record formatter which writes a header describing the default log format. + * * @author Jerome Louvel */ public class DefaultAccessLogFormatter extends AccessLogFormatter { - @Override - public String getHead(Handler h) { - final StringBuilder sb = new StringBuilder(); - sb.append("#Software: Restlet Framework ").append(Engine.VERSION).append('\n'); - sb.append("#Version: 1.0\n"); - sb.append("#Date: "); - final long currentTime = System.currentTimeMillis(); - sb.append(String.format("%tF", currentTime)); - sb.append(' '); - sb.append(String.format("%tT", currentTime)); - sb.append('\n'); - sb.append("#Fields: "); - sb.append("date time c-ip cs-username s-ip s-port cs-method "); - sb.append("cs-uri-stem cs-uri-query sc-status sc-bytes cs-bytes "); - sb.append("time-taken cs-host cs(User-Agent) cs(Referrer)\n"); - return sb.toString(); - } - + @Override + public String getHead(Handler h) { + final StringBuilder sb = new StringBuilder(); + sb.append("#Software: Restlet Framework ").append(Engine.VERSION).append('\n'); + sb.append("#Version: 1.0\n"); + sb.append("#Date: "); + final long currentTime = System.currentTimeMillis(); + sb.append(String.format("%tF", currentTime)); + sb.append(' '); + sb.append(String.format("%tT", currentTime)); + sb.append('\n'); + sb.append("#Fields: "); + sb.append("date time c-ip cs-username s-ip s-port cs-method "); + sb.append("cs-uri-stem cs-uri-query sc-status sc-bytes cs-bytes "); + sb.append("time-taken cs-host cs(User-Agent) cs(Referrer)\n"); + return sb.toString(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/log/IdentClient.java b/org.restlet/src/main/java/org/restlet/engine/log/IdentClient.java index 7b42ad3c24..86c006dea3 100644 --- a/org.restlet/src/main/java/org/restlet/engine/log/IdentClient.java +++ b/org.restlet/src/main/java/org/restlet/engine/log/IdentClient.java @@ -1,18 +1,13 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.log; -import org.restlet.Context; -import org.restlet.engine.io.IoUtils; -import org.restlet.engine.util.StringUtils; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -20,101 +15,107 @@ import java.net.Socket; import java.util.StringTokenizer; import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.engine.io.IoUtils; +import org.restlet.engine.util.StringUtils; /** * Simple IDENT client. Follow the RFC 1413. - * + * * @author Jerome Louvel */ public class IdentClient { - /** The timeout while attempting to connect to the Ident server. */ - private static final int CONNECT_TIMEOUT = 100; - - /** The timeout while communicating with the Ident server. */ - private static final int SO_TIMEOUT = 500; - - /** The remote host type. */ - private volatile String hostType; - - /** The user identifier. */ - private volatile String userIdentifier; - - /** - * Constructor. - * - * @param clientAddress The client IP address. - * @param clientPort The client port (remote). - * @param serverPort The server port (local). - */ - public IdentClient(String clientAddress, int clientPort, int serverPort) { - Socket socket = null; - - if ((clientAddress != null) && (clientPort != -1) && (serverPort != -1)) { - BufferedReader in = null; - try { - // Compose the IDENT request - final String request = clientPort + " , " + serverPort + "\r\n"; - - // Send the request to the remote server - socket = new Socket(); - socket.setSoTimeout(SO_TIMEOUT); - socket.connect(new InetSocketAddress(clientAddress, 113), CONNECT_TIMEOUT); - socket.getOutputStream().write(StringUtils.getAsciiBytes(request)); - - // Read the response - in = new BufferedReader(new InputStreamReader(socket.getInputStream()), IoUtils.BUFFER_SIZE); - final String response = in.readLine(); - - // Parse the response - if (response != null) { - final StringTokenizer st = new StringTokenizer(response, ":"); - - if (st.countTokens() >= 3) { - // Skip the first token - st.nextToken(); - - // Get the command - final String command = st.nextToken().trim(); - if (command.equalsIgnoreCase("USERID") && (st.countTokens() >= 2)) { - // Get the host type - this.hostType = st.nextToken().trim(); - - // Get the remaining text as a user identifier - this.userIdentifier = st.nextToken("").substring(1); - } - } - } - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.FINE, "Unable to complete the IDENT request", ioe); - } finally { - try { - // Always attempt to close the reader, therefore the socket - if (in != null) { - in.close(); - } - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.FINE, "Unable to close the socket", ioe); - } - } - } - } - - /** - * Returns the remote host type. - * - * @return The remote host type. - */ - public String getHostType() { - return this.hostType; - } - - /** - * Returns the user identifier. - * - * @return The user identifier. - */ - public String getUserIdentifier() { - return this.userIdentifier; - } - + /** The timeout while attempting to connect to the Ident server. */ + private static final int CONNECT_TIMEOUT = 100; + + /** The timeout while communicating with the Ident server. */ + private static final int SO_TIMEOUT = 500; + + /** The remote host type. */ + private volatile String hostType; + + /** The user identifier. */ + private volatile String userIdentifier; + + /** + * Constructor. + * + * @param clientAddress The client IP address. + * @param clientPort The client port (remote). + * @param serverPort The server port (local). + */ + public IdentClient(String clientAddress, int clientPort, int serverPort) { + Socket socket = null; + + if ((clientAddress != null) && (clientPort != -1) && (serverPort != -1)) { + BufferedReader in = null; + try { + // Compose the IDENT request + final String request = clientPort + " , " + serverPort + "\r\n"; + + // Send the request to the remote server + socket = new Socket(); + socket.setSoTimeout(SO_TIMEOUT); + socket.connect(new InetSocketAddress(clientAddress, 113), CONNECT_TIMEOUT); + socket.getOutputStream().write(StringUtils.getAsciiBytes(request)); + + // Read the response + in = + new BufferedReader( + new InputStreamReader(socket.getInputStream()), + IoUtils.BUFFER_SIZE); + final String response = in.readLine(); + + // Parse the response + if (response != null) { + final StringTokenizer st = new StringTokenizer(response, ":"); + + if (st.countTokens() >= 3) { + // Skip the first token + st.nextToken(); + + // Get the command + final String command = st.nextToken().trim(); + if (command.equalsIgnoreCase("USERID") && (st.countTokens() >= 2)) { + // Get the host type + this.hostType = st.nextToken().trim(); + + // Get the remaining text as a user identifier + this.userIdentifier = st.nextToken("").substring(1); + } + } + } + } catch (IOException ioe) { + Context.getCurrentLogger() + .log(Level.FINE, "Unable to complete the IDENT request", ioe); + } finally { + try { + // Always attempt to close the reader, therefore the socket + if (in != null) { + in.close(); + } + } catch (IOException ioe) { + Context.getCurrentLogger().log(Level.FINE, "Unable to close the socket", ioe); + } + } + } + } + + /** + * Returns the remote host type. + * + * @return The remote host type. + */ + public String getHostType() { + return this.hostType; + } + + /** + * Returns the user identifier. + * + * @return The user identifier. + */ + public String getUserIdentifier() { + return this.userIdentifier; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/log/LogFilter.java b/org.restlet/src/main/java/org/restlet/engine/log/LogFilter.java index 61b8f7ddec..11944aa64f 100644 --- a/org.restlet/src/main/java/org/restlet/engine/log/LogFilter.java +++ b/org.restlet/src/main/java/org/restlet/engine/log/LogFilter.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.log; +import java.util.logging.Level; +import java.util.logging.Logger; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -16,89 +17,92 @@ import org.restlet.routing.Filter; import org.restlet.service.LogService; -import java.util.logging.Level; -import java.util.logging.Logger; - /** - * Filter logging all calls after their handling by the target Restlet. The - * current format is similar to IIS 6 logs. The logging is based on the - * java.util.logging package. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Filter logging all calls after their handling by the target Restlet. The current format is + * similar to IIS 6 logs. The logging is based on the java.util.logging package. + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class LogFilter extends Filter { - /** The log service. */ - protected volatile LogService logService; - - /** The log service logger. */ - private volatile Logger logLogger; + /** The log service. */ + protected volatile LogService logService; - /** - * Constructor. - * - * @param context The context. - * @param logService The log service descriptor. - */ - public LogFilter(Context context, LogService logService) { - super(context); - this.logService = logService; + /** The log service logger. */ + private volatile Logger logLogger; - if (logService != null) { - if (logService.getLoggerName() != null) { - this.logLogger = Engine.getLogger(logService.getLoggerName()); - } else if ((context != null) && (context.getLogger().getParent() != null)) { - this.logLogger = Engine.getLogger(context.getLogger().getParent().getName() + "." - + LogUtils.getBestClassName(logService.getClass())); - } else { - this.logLogger = Engine.getLogger(LogUtils.getBestClassName(logService.getClass())); - } - } - } + /** + * Constructor. + * + * @param context The context. + * @param logService The log service descriptor. + */ + public LogFilter(Context context, LogService logService) { + super(context); + this.logService = logService; - /** - * Allows filtering after processing by the next Restlet. Logs the call. - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - protected void afterHandle(Request request, Response response) { - try { - if (request.isLoggable() && this.logLogger.isLoggable(Level.INFO)) { - long startTime = (Long) request.getAttributes().get("org.restlet.startTime"); - int duration = (int) (System.currentTimeMillis() - startTime); - this.logLogger.log(Level.INFO, this.logService.getResponseLogMessage(response, duration)); - } - } catch (Throwable e) { - // Error while logging the call, cf issue #931 - getLogger().log(Level.SEVERE, "Cannot log call", e); - } - } + if (logService != null) { + if (logService.getLoggerName() != null) { + this.logLogger = Engine.getLogger(logService.getLoggerName()); + } else if ((context != null) && (context.getLogger().getParent() != null)) { + this.logLogger = + Engine.getLogger( + context.getLogger().getParent().getName() + + "." + + LogUtils.getBestClassName(logService.getClass())); + } else { + this.logLogger = Engine.getLogger(LogUtils.getBestClassName(logService.getClass())); + } + } + } - /** - * Allows filtering before processing by the next Restlet. Saves the start time. - * - * @param request The request to handle. - * @param response The response to update. - * @return The continuation status. - */ - @Override - protected int beforeHandle(Request request, Response response) { - request.getAttributes().put("org.restlet.startTime", System.currentTimeMillis()); + /** + * Allows filtering after processing by the next Restlet. Logs the call. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + protected void afterHandle(Request request, Response response) { + try { + if (request.isLoggable() && this.logLogger.isLoggable(Level.INFO)) { + long startTime = (Long) request.getAttributes().get("org.restlet.startTime"); + int duration = (int) (System.currentTimeMillis() - startTime); + this.logLogger.log( + Level.INFO, this.logService.getResponseLogMessage(response, duration)); + } + } catch (Throwable e) { + // Error while logging the call, cf issue #931 + getLogger().log(Level.SEVERE, "Cannot log call", e); + } + } - // Set the log level for the given request - request.setLoggable(this.logService.isLoggable(request)); + /** + * Allows filtering before processing by the next Restlet. Saves the start time. + * + * @param request The request to handle. + * @param response The response to update. + * @return The continuation status. + */ + @Override + protected int beforeHandle(Request request, Response response) { + request.getAttributes().put("org.restlet.startTime", System.currentTimeMillis()); - if (request.isLoggable() && this.logLogger.isLoggable(Level.FINE)) { - this.logLogger.fine("Processing request to: \"" + ((request.getResourceRef() == null) ? "Unknown URI" - : request.getResourceRef().getTargetRef().toString()) + "\""); - } + // Set the log level for the given request + request.setLoggable(this.logService.isLoggable(request)); - return CONTINUE; - } + if (request.isLoggable() && this.logLogger.isLoggable(Level.FINE)) { + this.logLogger.fine( + "Processing request to: \"" + + ((request.getResourceRef() == null) + ? "Unknown URI" + : request.getResourceRef().getTargetRef().toString()) + + "\""); + } + return CONTINUE; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/log/LogUtils.java b/org.restlet/src/main/java/org/restlet/engine/log/LogUtils.java index bf13715a3f..2fca83a5bb 100644 --- a/org.restlet/src/main/java/org/restlet/engine/log/LogUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/log/LogUtils.java @@ -1,60 +1,54 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.log; /** * Logging related utility methods. - * + * * @author Jerome Louvel */ public class LogUtils { - /** - * Prevent instantiation of the class. - */ - private LogUtils() { - } - - /** - * Return the best class name. If the class is anonymous, then it returns the - * super class name. - * - * @param clazz The class to name. - * @return The class name. - */ - public static String getBestClassName(Class clazz) { - String result = clazz.getSimpleName(); - - if (result.isEmpty()) { - result = getBestClassName(clazz.getSuperclass()); - } - - return result; - } - - /** - * Returns a non-null logger name. It is composed by the canonical class name of - * the owner object suffixed by the owner's hash code. - * - * @param baseName The base logger name to prepend, without a trailing dot. - * @param owner The context owner. - * @return The logger name. - */ - public static String getLoggerName(String baseName, Object owner) { - String result = baseName; - - if ((owner != null) && (owner.getClass().getSimpleName() != null)) { - result += "." + getBestClassName(owner.getClass()); - } - - return result; - } - + /** Prevent instantiation of the class. */ + private LogUtils() {} + + /** + * Return the best class name. If the class is anonymous, then it returns the super class name. + * + * @param clazz The class to name. + * @return The class name. + */ + public static String getBestClassName(Class clazz) { + String result = clazz.getSimpleName(); + + if (result.isEmpty()) { + result = getBestClassName(clazz.getSuperclass()); + } + + return result; + } + + /** + * Returns a non-null logger name. It is composed by the canonical class name of the owner + * object suffixed by the owner's hash code. + * + * @param baseName The base logger name to prepend, without a trailing dot. + * @param owner The context owner. + * @return The logger name. + */ + public static String getLoggerName(String baseName, Object owner) { + String result = baseName; + + if ((owner != null) && (owner.getClass().getSimpleName() != null)) { + result += "." + getBestClassName(owner.getClass()); + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/log/LoggerFacade.java b/org.restlet/src/main/java/org/restlet/engine/log/LoggerFacade.java index 9e5f4c64cb..eef0b9dddd 100644 --- a/org.restlet/src/main/java/org/restlet/engine/log/LoggerFacade.java +++ b/org.restlet/src/main/java/org/restlet/engine/log/LoggerFacade.java @@ -1,98 +1,92 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.log; import java.util.logging.Logger; /** - * Logger facade to the underlying logging framework used by the Restlet - * Framework. By default, it relies on the JULI mechanism built in Java SE. You - * can provide an alternate implementation by extending this class and - * overriding the methods. - * + * Logger facade to the underlying logging framework used by the Restlet Framework. By default, it + * relies on the JULI mechanism built in Java SE. You can provide an alternate implementation by + * extending this class and overriding the methods. + * * @author Jerome Louvel */ public class LoggerFacade { - /** - * Returns an anonymous logger. By default it calls - * {@link Logger#getAnonymousLogger()}. This method should be overridden by - * subclasses. - * - * @return The logger. - */ - public Logger getAnonymousLogger() { - return Logger.getAnonymousLogger(); - } - - /** - * Returns a logger based on the class name of the given object. By default, it - * calls {@link #getLogger(Class, String)} with a null default logger name. - * - * @param clazz The parent class. - * @return The logger. - */ - public final Logger getLogger(Class clazz) { - return getLogger(clazz, null); - } + /** + * Returns an anonymous logger. By default, it calls {@link Logger#getAnonymousLogger()}. This + * method should be overridden by subclasses. + * + * @return The logger. + */ + public Logger getAnonymousLogger() { + return Logger.getAnonymousLogger(); + } - /** - * Returns a logger based on the class name of the given object. - * - * @param clazz The parent class. - * @param defaultLoggerName The default logger name to use if no one can be - * inferred from the class. - * @return The logger. - */ - public final Logger getLogger(Class clazz, String defaultLoggerName) { - String loggerName = null; + /** + * Returns a logger based on the class name of the given object. By default, it calls {@link + * #getLogger(Class, String)} with a null default logger name. + * + * @param clazz The parent class. + * @return The logger. + */ + public final Logger getLogger(Class clazz) { + return getLogger(clazz, null); + } - if (clazz != null) { - loggerName = clazz.getCanonicalName(); - } + /** + * Returns a logger based on the class name of the given object. + * + * @param clazz The parent class. + * @param defaultLoggerName The default logger name to use if no one can be inferred from the + * class. + * @return The logger. + */ + public final Logger getLogger(Class clazz, String defaultLoggerName) { + String loggerName = null; - if (loggerName == null) { - loggerName = defaultLoggerName; - } + if (clazz != null) { + loggerName = clazz.getCanonicalName(); + } - if (loggerName != null) { - return getLogger(loggerName); - } + if (loggerName == null) { + loggerName = defaultLoggerName; + } - return getAnonymousLogger(); - } + if (loggerName != null) { + return getLogger(loggerName); + } - /** - * Returns a logger based on the class name of the given object. By default, it - * calls {@link #getLogger(Class, String)} with the object's class as a first - * parameter. - * - * @param object The parent object. - * @param defaultLoggerName The default logger name to use if no one can be - * inferred from the object class. - * @return The logger. - */ - public final Logger getLogger(Object object, String defaultLoggerName) { - return getLogger(object.getClass(), defaultLoggerName); - } + return getAnonymousLogger(); + } - /** - * Returns a logger based on the given logger name. By default, it calls - * {@link Logger#getLogger(String)}. This method should be overridden by - * subclasses. - * - * @param loggerName The logger name. - * @return The logger. - */ - public Logger getLogger(String loggerName) { - return Logger.getLogger(loggerName); - } + /** + * Returns a logger based on the class name of the given object. By default, it calls {@link + * #getLogger(Class, String)} with the object's class as a first parameter. + * + * @param object The parent object. + * @param defaultLoggerName The default logger name to use if no one can be inferred from the + * object class. + * @return The logger. + */ + public final Logger getLogger(Object object, String defaultLoggerName) { + return getLogger(object.getClass(), defaultLoggerName); + } + /** + * Returns a logger based on the given logger name. By default, it calls {@link + * Logger#getLogger(String)}. This method should be overridden by subclasses. + * + * @param loggerName The logger name. + * @return The logger. + */ + public Logger getLogger(String loggerName) { + return Logger.getLogger(loggerName); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/log/LoggingThreadFactory.java b/org.restlet/src/main/java/org/restlet/engine/log/LoggingThreadFactory.java index d87e9cdc27..738a7a4729 100644 --- a/org.restlet/src/main/java/org/restlet/engine/log/LoggingThreadFactory.java +++ b/org.restlet/src/main/java/org/restlet/engine/log/LoggingThreadFactory.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.log; import java.util.concurrent.ThreadFactory; @@ -15,57 +14,62 @@ /** * Thread factory that logs uncaught exceptions thrown by the created threads. - * + * * @author Jerome Louvel */ public class LoggingThreadFactory implements ThreadFactory { - /** - * Handle uncaught thread exceptions. - */ - private class LoggingExceptionHandler implements Thread.UncaughtExceptionHandler { + /** Handle uncaught thread exceptions. */ + private class LoggingExceptionHandler implements Thread.UncaughtExceptionHandler { - public void uncaughtException(Thread t, Throwable ex) { - logger.log(Level.SEVERE, "Thread: " + t.getName() + " terminated with exception: " + ex.getMessage(), ex); - } - } + public void uncaughtException(Thread t, Throwable ex) { + logger.log( + Level.SEVERE, + ex, + () -> + "Thread: " + + t.getName() + + " terminated with exception: " + + ex.getMessage()); + } + } - /** The associated logger. */ - private final Logger logger; + /** The associated logger. */ + private final Logger logger; - /** Indicates if threads should be created as daemons. */ - private final boolean daemon; + /** Indicates if threads should be created as daemons. */ + private final boolean daemon; - /** - * Constructor. - * - * @param logger The associated logger. - */ - public LoggingThreadFactory(Logger logger) { - this(logger, false); - } + /** + * Constructor. + * + * @param logger The associated logger. + */ + public LoggingThreadFactory(Logger logger) { + this(logger, false); + } - /** - * Constructor. - * - * @param logger The associated logger. - * @param daemon Indicates if threads should be created as daemons. - */ - public LoggingThreadFactory(Logger logger, boolean daemon) { - this.logger = logger; - this.daemon = daemon; - } + /** + * Constructor. + * + * @param logger The associated logger. + * @param daemon Indicates if threads should be created as daemons. + */ + public LoggingThreadFactory(Logger logger, boolean daemon) { + this.logger = logger; + this.daemon = daemon; + } - /** - * Creates a new thread. - * - * @param r The runnable task. - */ - public Thread newThread(Runnable r) { - Thread result = new Thread(r); - result.setName("Restlet-" + result.hashCode()); - result.setUncaughtExceptionHandler(new LoggingExceptionHandler()); - result.setDaemon(this.daemon); - return result; - } + /** + * Creates a new thread. + * + * @param r The runnable task. + */ + public Thread newThread(Runnable r) { + Thread result = new Thread(r); + result.setName("Restlet-" + result.hashCode()); + result.setUncaughtExceptionHandler(new LoggingExceptionHandler()); + result.setDaemon(this.daemon); + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/log/SimplerFormatter.java b/org.restlet/src/main/java/org/restlet/engine/log/SimplerFormatter.java index 0a55ea3820..1259b5b09b 100644 --- a/org.restlet/src/main/java/org/restlet/engine/log/SimplerFormatter.java +++ b/org.restlet/src/main/java/org/restlet/engine/log/SimplerFormatter.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.log; import java.io.PrintWriter; @@ -15,44 +14,44 @@ import java.util.logging.LogRecord; /** - * Special log formatter that displays the level, the logger name and the actual - * message. It also displays the stack trace if available. - * - * This is particularly useful for debugging. - * + * Special log formatter that displays the level, the logger name, and the actual message. It also + * displays the stack trace if available. + * + *

This is particularly useful for debugging. + * * @author Jerome Louvel */ public class SimplerFormatter extends Formatter { - /** - * Format the given LogRecord. - * - * @param record the log record to be formatted. - * @return a formatted log record - */ - public synchronized String format(LogRecord record) { - StringBuilder sb = new StringBuilder(); - - sb.append(record.getLevel().getLocalizedName()); - sb.append(" ["); - sb.append(record.getLoggerName()); - sb.append("] - "); - sb.append(formatMessage(record)); - sb.append('\n'); - - if (record.getThrown() != null) { - try { - sb.append(System.getProperty("line.separator")); - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - record.getThrown().printStackTrace(pw); - pw.close(); - sb.append(sw.toString()); - } catch (Exception ex) { - } - } - - return sb.toString(); - } - + /** + * Format the given LogRecord. + * + * @param logRecord the log record to be formatted. + * @return a formatted log record + */ + public synchronized String format(LogRecord logRecord) { + StringBuilder sb = new StringBuilder(); + + sb.append(logRecord.getLevel().getLocalizedName()); + sb.append(" ["); + sb.append(logRecord.getLoggerName()); + sb.append("] - "); + sb.append(formatMessage(logRecord)); + sb.append('\n'); + + if (logRecord.getThrown() != null) { + try { + sb.append(System.lineSeparator()); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + logRecord.getThrown().printStackTrace(pw); + pw.close(); + sb.append(sw); + } catch (Exception ignored) { + // Ignored + } + } + + return sb.toString(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/log/SimplestFormatter.java b/org.restlet/src/main/java/org/restlet/engine/log/SimplestFormatter.java index ae29c4f336..7d95e046c1 100644 --- a/org.restlet/src/main/java/org/restlet/engine/log/SimplestFormatter.java +++ b/org.restlet/src/main/java/org/restlet/engine/log/SimplestFormatter.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.log; import java.io.PrintWriter; @@ -15,40 +14,40 @@ import java.util.logging.LogRecord; /** - * Special log formatter that displays the actual message only. It also displays - * the stack trace if available. - * - * This is particularly useful for debugging. - * + * Special log formatter that displays the actual message only. It also displays the stack trace if + * available. + * + *

This is particularly useful for debugging. + * * @author Jerome Louvel */ public class SimplestFormatter extends Formatter { - /** - * Format the given LogRecord. - * - * @param record the log record to be formatted. - * @return a formatted log record - */ - public synchronized String format(LogRecord record) { - StringBuilder sb = new StringBuilder(); - - sb.append(formatMessage(record)); - sb.append('\n'); - - if (record.getThrown() != null) { - try { - sb.append(System.getProperty("line.separator")); - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - record.getThrown().printStackTrace(pw); - pw.close(); - sb.append(sw.toString()); - } catch (Exception ex) { - } - } - - return sb.toString(); - } - + /** + * Format the given LogRecord. + * + * @param logRecord the log record to be formatted. + * @return a formatted log record + */ + public synchronized String format(LogRecord logRecord) { + StringBuilder sb = new StringBuilder(); + + sb.append(formatMessage(logRecord)); + sb.append('\n'); + + if (logRecord.getThrown() != null) { + try { + sb.append(System.lineSeparator()); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + logRecord.getThrown().printStackTrace(pw); + pw.close(); + sb.append(sw); + } catch (Exception ignored) { + // Ignored + } + } + + return sb.toString(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/log/package-info.java b/org.restlet/src/main/java/org/restlet/engine/log/package-info.java new file mode 100644 index 0000000000..937aedae56 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/log/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports the log service. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.log; diff --git a/org.restlet/src/main/java/org/restlet/engine/log/package.html b/org.restlet/src/main/java/org/restlet/engine/log/package.html deleted file mode 100644 index ff984d0e51..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/log/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Supports the log service. -

-@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/package-info.java b/org.restlet/src/main/java/org/restlet/engine/package-info.java new file mode 100644 index 0000000000..94283c16c6 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/package-info.java @@ -0,0 +1,8 @@ +/** + * Implementation of Restlet API. + * + * @since Restlet 2.0 + * @see User Guide - + * Engine + */ +package org.restlet.engine; diff --git a/org.restlet/src/main/java/org/restlet/engine/package.html b/org.restlet/src/main/java/org/restlet/engine/package.html deleted file mode 100644 index 9159bece91..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - - Implementation of Restlet API. -

- @since Restlet 2.0 - @see User Guide - Engine - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/resource/AnnotationInfo.java b/org.restlet/src/main/java/org/restlet/engine/resource/AnnotationInfo.java index 6c883cab02..bc80b6f63a 100644 --- a/org.restlet/src/main/java/org/restlet/engine/resource/AnnotationInfo.java +++ b/org.restlet/src/main/java/org/restlet/engine/resource/AnnotationInfo.java @@ -1,238 +1,241 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.resource; -import org.restlet.Context; -import org.restlet.engine.util.SystemUtils; - -import java.lang.reflect.*; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.Objects; import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.engine.util.SystemUtils; /** * Descriptor for Restlet annotations. - * + * * @author Jerome Louvel */ public abstract class AnnotationInfo { - /** - * Returns the actual type for a given generic type name. - * - * @param currentClass The current class to walk up. - * @param genericTypeName The generic type name to resolve. - * @return The actual type. - */ - protected static Class getJavaActualType(Class currentClass, String genericTypeName) { - Class result = null; - - // Lookup in the super class - result = getJavaActualType(currentClass.getGenericSuperclass(), genericTypeName); - - if (result == null) { - // Lookup in the implemented interfaces - Type[] interfaceTypes = currentClass.getGenericInterfaces(); - - for (int i = 0; (result == null) && (i < interfaceTypes.length); i++) { - result = getJavaActualType(interfaceTypes[i], genericTypeName); - } - } - - return result; - } - - /** - * Returns the actual type for a given generic type name. - * - * @param currentType The current type to start with. - * @param genericTypeName The generic type name to resolve. - * @return The actual type. - */ - protected static Class getJavaActualType(Type currentType, String genericTypeName) { - Class result = null; - - if (currentType != null) { - if (currentType instanceof Class) { - // Look in the generic super class or the implemented interfaces - result = getJavaActualType((Class) currentType, genericTypeName); - } else if (currentType instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) currentType; - Class rawType = (Class) parameterizedType.getRawType(); - Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - TypeVariable[] typeParameters = rawType.getTypeParameters(); - - for (int i = 0; (result == null) && (i < actualTypeArguments.length); i++) { - if (genericTypeName.equals(typeParameters[i].getName())) { - result = getTypeClass(actualTypeArguments[i]); - } - } - } - } - - return result; - } - - /** - * Returns the underlying class for a type or null. - * - * @param type The generic type. - * @return The underlying class - */ - protected static Class getTypeClass(Type type) { - Class result = null; - - if (type instanceof Class) { - result = (Class) type; - } else if (type instanceof ParameterizedType) { - result = getTypeClass(((ParameterizedType) type).getRawType()); - } else if (type instanceof GenericArrayType) { - Type componentType = ((GenericArrayType) type).getGenericComponentType(); - Class componentClass = getTypeClass(componentType); - - if (componentClass != null) { - result = Array.newInstance(componentClass, 0).getClass(); - } - } - - return result; - } - - /** The raw annotation value. */ - protected final String annotationValue; - - /** The class that hosts the annotated Java method. */ - protected final Class javaClass; - - /** The annotated Java method. */ - protected final java.lang.reflect.Method javaMethod; - - /** The upper implementation of the annotated Java method. */ - protected final java.lang.reflect.Method javaMethodImpl; - - /** - * Constructor. - * - * @param javaClass The annotated Java class or parent Java class. - * @param javaMethod The annotated Java method. - * @param annotationValue The annotation value. - */ - public AnnotationInfo(Class javaClass, java.lang.reflect.Method javaMethod, String annotationValue) { - super(); - this.javaClass = javaClass; - this.javaMethod = javaMethod; - this.annotationValue = annotationValue; - java.lang.reflect.Method m = null; - - try { - m = javaClass.getMethod(javaMethod.getName(), javaMethod.getParameterTypes()); - } catch (Exception e) { - m = javaMethod; - } - - if (m != null) { - this.javaMethodImpl = m; - } else { - this.javaMethodImpl = javaMethod; - } - } - - /** - * Constructor. - * - * @param javaClass The annotated Java class or parent Java class. - * @param annotationValue The annotation value. - */ - public AnnotationInfo(Class javaClass, String annotationValue) { - this(javaClass, null, annotationValue); - } - - /** - * Indicates if the current variant is equal to the given object. - * - * @param other The other object. - * @return True if the current object is equal to the given object. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof AnnotationInfo)) { - return false; - } - - AnnotationInfo that = (AnnotationInfo) other; - - return Objects.equals(getJavaMethod(), that.getJavaMethod()) - && Objects.equals(getJavaClass(), that.getJavaClass()) - && Objects.equals(getAnnotationValue(), that.getAnnotationValue()); - } - - /** - * Returns the raw annotation value. - * - * @return The raw annotation value. - */ - public String getAnnotationValue() { - return annotationValue; - } - - /** - * Returns the actual type for a given generic type. - * - * @param initialType The initial type, which may be generic. - * @param genericType The generic type information if any. - * @return The actual type. - */ - protected Class getJavaActualType(Class initialType, Type genericType) { - Class result = initialType; - - try { - if (genericType instanceof TypeVariable genericTypeVariable) { - String genericTypeName = genericTypeVariable.getName(); - result = getJavaActualType(getJavaClass(), genericTypeName); - } - } catch (Throwable t) { - Context.getCurrentLogger().log(Level.WARNING, "Cannot get actual type of generic type: " + genericType, t); - } - - return result; - } - - /** - * Returns the resource interface value. - * - * @return The resource interface value. - */ - public Class getJavaClass() { - return javaClass; - } - - @Override - public int hashCode() { - return SystemUtils.hashCode(annotationValue, javaClass, javaMethod); - } - - /** - * Returns the annotated Java method. - * - * @return The annotated Java method. - */ - public java.lang.reflect.Method getJavaMethod() { - return javaMethod; - } - - @Override - public String toString() { - return "AnnotationInfo [javaMethod: " + javaMethod + ", javaClass: " + javaClass + ", value: " + annotationValue - + "]"; - } - -} \ No newline at end of file + /** + * Returns the actual type for a given generic type name. + * + * @param currentClass The current class to walk up. + * @param genericTypeName The generic type name to resolve. + * @return The actual type. + */ + protected static Class getJavaActualType(Class currentClass, String genericTypeName) { + Class result = null; + + // Lookup in the super class + result = getJavaActualType(currentClass.getGenericSuperclass(), genericTypeName); + + if (result == null) { + // Lookup at the implemented interfaces + Type[] interfaceTypes = currentClass.getGenericInterfaces(); + + for (int i = 0; (result == null) && (i < interfaceTypes.length); i++) { + result = getJavaActualType(interfaceTypes[i], genericTypeName); + } + } + + return result; + } + + /** + * Returns the actual type for a given generic type name. + * + * @param currentType The current type to start with. + * @param genericTypeName The generic type name to resolve. + * @return The actual type. + */ + protected static Class getJavaActualType(Type currentType, String genericTypeName) { + Class result = null; + + if (currentType != null) { + if (currentType instanceof Class) { + // Look in the generic super class or the implemented interfaces + result = getJavaActualType((Class) currentType, genericTypeName); + } else if (currentType instanceof final ParameterizedType parameterizedType) { + Class rawType = (Class) parameterizedType.getRawType(); + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + TypeVariable[] typeParameters = rawType.getTypeParameters(); + + for (int i = 0; (result == null) && (i < actualTypeArguments.length); i++) { + if (genericTypeName.equals(typeParameters[i].getName())) { + result = getTypeClass(actualTypeArguments[i]); + } + } + } + } + + return result; + } + + /** + * Returns the underlying class for a type or null. + * + * @param type The generic type. + * @return The underlying class + */ + protected static Class getTypeClass(Type type) { + Class result = null; + + if (type instanceof Class) { + result = (Class) type; + } else if (type instanceof ParameterizedType parameterizedType) { + result = getTypeClass(parameterizedType.getRawType()); + } else if (type instanceof GenericArrayType genericArrayType) { + Type componentType = genericArrayType.getGenericComponentType(); + Class componentClass = getTypeClass(componentType); + + if (componentClass != null) { + result = Array.newInstance(componentClass, 0).getClass(); + } + } + + return result; + } + + /** The raw annotation value. */ + protected final String annotationValue; + + /** The class that hosts the annotated Java method. */ + protected final Class javaClass; + + /** The annotated Java method. */ + protected final java.lang.reflect.Method javaMethod; + + /** The upper implementation of the annotated Java method. */ + protected final java.lang.reflect.Method javaMethodImpl; + + /** + * Constructor. + * + * @param javaClass The annotated Java class or parent Java class. + * @param javaMethod The annotated Java method. + * @param annotationValue The annotation value. + */ + protected AnnotationInfo( + Class javaClass, java.lang.reflect.Method javaMethod, String annotationValue) { + super(); + this.javaClass = javaClass; + this.javaMethod = javaMethod; + this.annotationValue = annotationValue; + java.lang.reflect.Method m = null; + + try { + m = javaClass.getMethod(javaMethod.getName(), javaMethod.getParameterTypes()); + } catch (Exception e) { + m = javaMethod; + } + + if (m != null) { + this.javaMethodImpl = m; + } else { + this.javaMethodImpl = javaMethod; + } + } + + /** + * Constructor. + * + * @param javaClass The annotated Java class or parent Java class. + * @param annotationValue The annotation value. + */ + protected AnnotationInfo(Class javaClass, String annotationValue) { + this(javaClass, null, annotationValue); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof final AnnotationInfo that)) { + return false; + } + + return Objects.equals(getJavaMethod(), that.getJavaMethod()) + && Objects.equals(getJavaClass(), that.getJavaClass()) + && Objects.equals(getAnnotationValue(), that.getAnnotationValue()); + } + + /** + * Returns the raw annotation value. + * + * @return The raw annotation value. + */ + public String getAnnotationValue() { + return annotationValue; + } + + /** + * Returns the actual type for a given generic type. + * + * @param initialType The initial type, which may be generic. + * @param genericType The generic type information, if any. + * @return The actual type. + */ + protected Class getJavaActualType(Class initialType, Type genericType) { + Class result = initialType; + + try { + if (genericType instanceof TypeVariable genericTypeVariable) { + String genericTypeName = genericTypeVariable.getName(); + result = getJavaActualType(getJavaClass(), genericTypeName); + } + } catch (Exception exception) { + Context.getCurrentLogger() + .log( + Level.WARNING, + exception, + () -> "Cannot get actual type of generic type: " + genericType); + } + + return result; + } + + /** + * Returns the resource interface value. + * + * @return The resource interface value. + */ + public Class getJavaClass() { + return javaClass; + } + + @Override + public int hashCode() { + return SystemUtils.hashCode(annotationValue, javaClass, javaMethod); + } + + /** + * Returns the annotated Java method. + * + * @return The annotated Java method. + */ + public java.lang.reflect.Method getJavaMethod() { + return javaMethod; + } + + @Override + public String toString() { + return "AnnotationInfo [javaMethod: " + + javaMethod + + ", javaClass: " + + javaClass + + ", value: " + + annotationValue + + "]"; + } +} diff --git a/org.restlet/src/main/java/org/restlet/engine/resource/AnnotationUtils.java b/org.restlet/src/main/java/org/restlet/engine/resource/AnnotationUtils.java index 5cecc99cd6..69f3df0ac7 100644 --- a/org.restlet/src/main/java/org/restlet/engine/resource/AnnotationUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/resource/AnnotationUtils.java @@ -1,14 +1,18 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.resource; +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; import org.restlet.Context; import org.restlet.data.Form; import org.restlet.data.Method; @@ -17,13 +21,6 @@ import org.restlet.resource.Status; import org.restlet.service.MetadataService; -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CopyOnWriteArrayList; - /** * Utilities to manipulate Restlet annotations. * @@ -31,311 +28,314 @@ */ public class AnnotationUtils { - /** Annotation info cache. */ - private static final ConcurrentMap, List> cache = new ConcurrentHashMap, List>(); - - /** Current instance. */ - private static AnnotationUtils instance = new AnnotationUtils(); - - /** Returns the current instance of AnnotationUtils. */ - public static AnnotationUtils getInstance() { - return instance; - } - - /** - * Protected constructor. - */ - protected AnnotationUtils() { - } - - /** - * Computes the annotation descriptors for the given class or interface. - * - * @param descriptors The annotation descriptors to update or null to create a - * new one. - * @param clazz The class or interface to introspect. - * @param initialClass The class or interface that runs the javaMethod. - * @return The annotation descriptors. - */ - private List addAnnotations(List descriptors, Class clazz, - Class initialClass) { - List result = descriptors; - - if (clazz != null && !ServerResource.class.equals(clazz)) { - // Add the annotation descriptor - if (result == null) { - result = new CopyOnWriteArrayList(); - } - - // Inspect the current class - addThrowableAnnotationDescriptors(result, clazz, initialClass); - - if (clazz.isInterface()) { - for (java.lang.reflect.Method javaMethod : clazz.getMethods()) { - addMethodAnnotationDescriptors(result, clazz, initialClass, javaMethod); - } - } else { - for (java.lang.reflect.Method javaMethod : clazz.getDeclaredMethods()) { - addMethodAnnotationDescriptors(result, clazz, initialClass, javaMethod); - } - } - - // Inspect the implemented interfaces for annotations - Class[] interfaces = clazz.getInterfaces(); - - if (interfaces != null) { - for (Class interfaceClass : interfaces) { - result = addAnnotations(result, interfaceClass, initialClass); - } - } - - // Add the annotations from the super class. - addAnnotations(result, clazz.getSuperclass(), initialClass); - } - - return result; - } - - /** - * Computes the annotation descriptors for the given Java method. - * - * @param descriptors The annotation descriptors to update or null to create a - * new one. - * @param clazz The class or interface that hosts the javaMethod. - * @param initialClass The class or interface that runs the javaMethod. - * @param javaMethod The Java method to inspect. - * @return The annotation descriptors. - */ - private List addMethodAnnotationDescriptors(List descriptors, Class clazz, - Class initialClass, java.lang.reflect.Method javaMethod) { - List result = descriptors; - - for (Annotation annotation : javaMethod.getAnnotations()) { - Annotation methodAnnotation = annotation.annotationType() - .getAnnotation(org.restlet.engine.connector.Method.class); - Method restletMethod = getRestletMethod(annotation, methodAnnotation); - - if (restletMethod != null) { - if (result == null) { - result = new CopyOnWriteArrayList(); - } - try { - java.lang.reflect.Method valueMethod = annotation.getClass().getDeclaredMethod("value"); - String value = (String) valueMethod.invoke(annotation); - - result.add(new MethodAnnotationInfo(initialClass, restletMethod, javaMethod, value)); - } catch (Exception exception) { - Context.getCurrentLogger().info("Cannot get value of Restlet annotation: " + annotation + " due to " - + exception.getMessage()); - } - } - } - - for (Class exceptionClass : javaMethod.getExceptionTypes()) { - for (Annotation annotation : exceptionClass.getAnnotations()) { - org.restlet.resource.Status statusAnnotation = annotation.annotationType() - .getAnnotation(org.restlet.resource.Status.class); - - if (statusAnnotation != null) { - int code = statusAnnotation.value(); - boolean serializable = statusAnnotation.serialize(); - - if (result == null) { - result = new CopyOnWriteArrayList(); - } - - result.add(new ThrowableAnnotationInfo(initialClass, code, serializable)); - } - } - } - - return result; - } - - /** - * Computes the annotation descriptors for the given Java method. - * - * @param descriptors The annotation descriptors to update or null to create a - * new one. - * @param clazz The class or interface that hosts the javaMethod. - * @param initialClass The class or interface that runs the javaMethod. - * @return The annotation descriptors. - */ - private List addThrowableAnnotationDescriptors(List descriptors, Class clazz, - Class initialClass) { - List result = descriptors; - Status status = clazz.getAnnotation(org.restlet.resource.Status.class); - - if (status != null) { - result.add(new ThrowableAnnotationInfo(initialClass, status.value(), status.serialize())); - } - - return result; - } - - /** - * Clears the annotation descriptors cache. - */ - public void clearCache() { - cache.clear(); - } - - /** - * Returns the annotation descriptors for the given resource class. - * - * @param clazz The resource class to introspect. - * @return The list of annotation descriptors. - */ - public synchronized List getAnnotations(Class clazz) { - List result = cache.get(clazz); - - if (result == null) { - // Inspect the class itself for annotations - result = addAnnotations(result, clazz, clazz); - - // Put the list in the cache if no one was previously present - List prev = cache.putIfAbsent(clazz, result); - - if (prev != null) { - // Reuse the previous entry - result = prev; - } - } - - return result; - } - - /** - * Returns the annotation descriptors for the given resource class. - * - * @param javaMethod The Java method. - * @return The list of annotation descriptors. - */ - public List getAnnotations(Class clazz, java.lang.reflect.Method javaMethod) { - return addMethodAnnotationDescriptors(null, clazz, clazz, javaMethod); - } - - /** - * Returns the first annotation descriptor matching the given Java method. - * - * @param annotations The list of annotations. - * @param javaMethod The method to match. - * @return The annotation descriptor. - */ - public MethodAnnotationInfo getMethodAnnotation(List annotations, - java.lang.reflect.Method javaMethod) { - if (annotations != null) { - for (AnnotationInfo annotationInfo : annotations) { - if (annotationInfo instanceof MethodAnnotationInfo - && annotationInfo.getJavaMethod().equals(javaMethod)) { - return (MethodAnnotationInfo) annotationInfo; - } - } - } - - return null; - } - - /** - * Returns the first annotation descriptor matching the given Restlet method. - * - * @param annotations The list of annotations. - * @param restletMethod The method to match. - * @param query The query parameters. - * @param entity The request entity to match or null if no entity is - * provided. - * @param metadataService The metadata service to use. - * @param converterService The converter service to use. - * @return The annotation descriptor. - * @throws IOException - */ - public MethodAnnotationInfo getMethodAnnotation(List annotations, Method restletMethod, Form query, - Representation entity, MetadataService metadataService, - org.restlet.service.ConverterService converterService) throws IOException { - if (annotations != null) { - for (AnnotationInfo annotationInfo : annotations) { - if (annotationInfo instanceof MethodAnnotationInfo) { - if (((MethodAnnotationInfo) annotationInfo).isCompatible(restletMethod, query, entity, - metadataService, converterService)) { - return (MethodAnnotationInfo) annotationInfo; - } - } - } - } - - return null; - } - - /** - * Returns an instance of {@link Method} according to the given annotations. - * - * @param annotation Java annotation. - * @param methodAnnotation Annotation that corresponds to a Restlet method. - * @return An instance of {@link Method} according to the given annotations. - */ - protected Method getRestletMethod(Annotation annotation, Annotation methodAnnotation) { - return (methodAnnotation == null) ? null - : Method.valueOf(((org.restlet.engine.connector.Method) methodAnnotation).value()); - } - - /** - * Returns the status annotation descriptor if present or null. - * - * @param clazz The class with the status attached. - * @return The status annotation descriptor if present or null. - */ - public ThrowableAnnotationInfo getThrowableAnnotationInfo(Class clazz) { - List annotationInfos = getAnnotations(clazz); - - for (AnnotationInfo annotationInfo : annotationInfos) { - if (annotationInfo instanceof ThrowableAnnotationInfo) { - return (ThrowableAnnotationInfo) annotationInfo; - } - } - - return null; - } - - /** - * Returns the {@link Throwable} class matching the given error code if present - * or null. - * - * @param javaMethod The method that holds {@link Throwable}. - * @param errorCode The error code to match. - * @return The {@link Throwable} class matching the given error code if present - * or null. - */ - public ThrowableAnnotationInfo getThrowableAnnotationInfo(java.lang.reflect.Method javaMethod, int errorCode) { - for (Class clazz : javaMethod.getExceptionTypes()) { - ThrowableAnnotationInfo tai = getThrowableAnnotationInfo(clazz); - - if (tai != null && tai.getStatus().getCode() == errorCode) { - return tai; - } - } - - return null; - } - - /** - * Returns the {@link Throwable} class matching the given error code if present - * or null. - * - * @param javaMethod The method that holds {@link Throwable}. - * @param errorCode The error code to match. - * @return The {@link Throwable} class matching the given error code if present - * or null. - */ - public Class getThrowableClass(java.lang.reflect.Method javaMethod, int errorCode) { - for (Class clazz : javaMethod.getExceptionTypes()) { - ThrowableAnnotationInfo tai = getThrowableAnnotationInfo(clazz); - - if (tai != null && tai.getStatus().getCode() == errorCode) { - return clazz; - } - } - - return null; - } - -} \ No newline at end of file + /** Annotation info cache. */ + private static final ConcurrentMap, List> cache = + new ConcurrentHashMap<>(); + + /** Current instance. */ + private static final AnnotationUtils instance = new AnnotationUtils(); + + /** Returns the current instance of AnnotationUtils. */ + public static AnnotationUtils getInstance() { + return instance; + } + + /** Protected constructor. */ + protected AnnotationUtils() {} + + /** + * Computes the annotation descriptors for the given class or interface. + * + * @param descriptors The annotation descriptors to update or null to create a new one. + * @param clazz The class or interface to introspect. + * @param initialClass The class or interface that runs the javaMethod. + * @return The annotation descriptors. + */ + private List addAnnotations( + List descriptors, Class clazz, Class initialClass) { + List result = descriptors; + + if (clazz != null && !ServerResource.class.equals(clazz)) { + // Add the annotation descriptor + if (result == null) { + result = new CopyOnWriteArrayList<>(); + } + + // Inspect the current class + addThrowableAnnotationDescriptors(result, clazz, initialClass); + + if (clazz.isInterface()) { + for (java.lang.reflect.Method javaMethod : clazz.getMethods()) { + addMethodAnnotationDescriptors(result, initialClass, javaMethod); + } + } else { + for (java.lang.reflect.Method javaMethod : clazz.getDeclaredMethods()) { + addMethodAnnotationDescriptors(result, initialClass, javaMethod); + } + } + + // Inspect the implemented interfaces for annotations + Class[] interfaces = clazz.getInterfaces(); + + for (Class interfaceClass : interfaces) { + result = addAnnotations(result, interfaceClass, initialClass); + } + + // Add the annotations from the super class. + addAnnotations(result, clazz.getSuperclass(), initialClass); + } + + return result; + } + + /** + * Computes the annotation descriptors for the given Java method. + * + * @param descriptors The annotation descriptors to update or null to create a new one. + * @param initialClass The class or interface that runs the javaMethod. + * @param javaMethod The Java method to inspect. + * @return The annotation descriptors. + */ + private List addMethodAnnotationDescriptors( + List descriptors, + Class initialClass, + java.lang.reflect.Method javaMethod) { + List result = descriptors; + + for (Annotation annotation : javaMethod.getAnnotations()) { + Annotation methodAnnotation = + annotation + .annotationType() + .getAnnotation(org.restlet.engine.connector.Method.class); + Method restletMethod = getRestletMethod(annotation, methodAnnotation); + + if (restletMethod != null) { + if (result == null) { + result = new CopyOnWriteArrayList<>(); + } + try { + java.lang.reflect.Method valueMethod = + annotation.getClass().getDeclaredMethod("value"); + String value = (String) valueMethod.invoke(annotation); + + result.add( + new MethodAnnotationInfo( + initialClass, restletMethod, javaMethod, value)); + } catch (Exception exception) { + Context.getCurrentLogger() + .info( + "Cannot get value of Restlet annotation: " + + annotation + + " due to " + + exception.getMessage()); + } + } + } + + for (Class exceptionClass : javaMethod.getExceptionTypes()) { + for (Annotation annotation : exceptionClass.getAnnotations()) { + org.restlet.resource.Status statusAnnotation = + annotation + .annotationType() + .getAnnotation(org.restlet.resource.Status.class); + + if (statusAnnotation != null) { + int code = statusAnnotation.value(); + boolean serializable = statusAnnotation.serialize(); + + if (result == null) { + result = new CopyOnWriteArrayList<>(); + } + + result.add(new ThrowableAnnotationInfo(initialClass, code, serializable)); + } + } + } + + return result; + } + + /** + * Computes the annotation descriptors for the given Java method. + * + * @param descriptors The annotation descriptors to update or null to create a new one. + * @param clazz The class or interface that hosts the javaMethod. + * @param initialClass The class or interface that runs the javaMethod. + * @return The annotation descriptors. + */ + private List addThrowableAnnotationDescriptors( + List descriptors, Class clazz, Class initialClass) { + List result = descriptors; + Status status = clazz.getAnnotation(org.restlet.resource.Status.class); + + if (status != null) { + result.add( + new ThrowableAnnotationInfo(initialClass, status.value(), status.serialize())); + } + + return result; + } + + /** Clears the annotation descriptors cache. */ + public void clearCache() { + cache.clear(); + } + + /** + * Returns the annotation descriptors for the given resource class. + * + * @param clazz The resource class to introspect. + * @return The list of annotation descriptors. + */ + public synchronized List getAnnotations(Class clazz) { + List result = cache.get(clazz); + + if (result == null) { + // Inspect the class itself for annotations + result = addAnnotations(result, clazz, clazz); + + // Put the list in the cache if no one was previously present + List prev = cache.putIfAbsent(clazz, result); + + if (prev != null) { + // Reuse the previous entry + result = prev; + } + } + + return result; + } + + /** + * Returns the annotation descriptors for the given resource class. + * + * @param javaMethod The Java method. + * @return The list of annotation descriptors. + */ + public List getAnnotations( + Class clazz, java.lang.reflect.Method javaMethod) { + return addMethodAnnotationDescriptors(null, clazz, javaMethod); + } + + /** + * Returns the first annotation descriptor matching the given Java method. + * + * @param annotations The list of annotations. + * @param javaMethod The method to match. + * @return The annotation descriptor. + */ + public MethodAnnotationInfo getMethodAnnotation( + List annotations, java.lang.reflect.Method javaMethod) { + if (annotations != null) { + for (AnnotationInfo annotationInfo : annotations) { + if (annotationInfo instanceof MethodAnnotationInfo methodAnnotationInfo + && annotationInfo.getJavaMethod().equals(javaMethod)) { + return methodAnnotationInfo; + } + } + } + + return null; + } + + /** + * Returns the first annotation descriptor matching the given Restlet method. + * + * @param annotations The list of annotations. + * @param restletMethod The method to match. + * @param query The query parameters. + * @param entity The request entity to match or null if no entity is provided. + * @param metadataService The metadata service to use. + * @param converterService The converter service to use. + * @return The annotation descriptor. + */ + public MethodAnnotationInfo getMethodAnnotation( + List annotations, + Method restletMethod, + Form query, + Representation entity, + MetadataService metadataService, + org.restlet.service.ConverterService converterService) { + if (annotations != null) { + for (AnnotationInfo annotationInfo : annotations) { + if (annotationInfo instanceof MethodAnnotationInfo methodAnnotationInfo + && methodAnnotationInfo.isCompatible( + restletMethod, query, entity, metadataService, converterService)) { + return methodAnnotationInfo; + } + } + } + + return null; + } + + /** + * Returns an instance of {@link Method} according to the given annotations. + * + * @param annotation Java annotation. + * @param methodAnnotation Annotation that corresponds to a Restlet method. + * @return An instance of {@link Method} according to the given annotations. + */ + protected Method getRestletMethod(Annotation annotation, Annotation methodAnnotation) { + return (methodAnnotation == null) + ? null + : Method.valueOf(((org.restlet.engine.connector.Method) methodAnnotation).value()); + } + + /** + * Returns the status annotation descriptor if present or null. + * + * @param clazz The class with the status attached. + * @return The status annotation descriptor if present or null. + */ + public ThrowableAnnotationInfo getThrowableAnnotationInfo(Class clazz) { + List annotationInfos = getAnnotations(clazz); + + for (AnnotationInfo annotationInfo : annotationInfos) { + if (annotationInfo instanceof ThrowableAnnotationInfo throwableAnnotationInfo) { + return throwableAnnotationInfo; + } + } + + return null; + } + + /** + * Returns the {@link Throwable} class matching the given error code if present or null. + * + * @param javaMethod The method that holds {@link Throwable}. + * @param errorCode The error code to match. + * @return The {@link Throwable} class matching the given error code if present or null. + */ + public ThrowableAnnotationInfo getThrowableAnnotationInfo( + java.lang.reflect.Method javaMethod, int errorCode) { + for (Class clazz : javaMethod.getExceptionTypes()) { + ThrowableAnnotationInfo tai = getThrowableAnnotationInfo(clazz); + + if (tai != null && tai.getStatus().getCode() == errorCode) { + return tai; + } + } + + return null; + } + + /** + * Returns the {@link Throwable} class matching the given error code if present or null. + * + * @param javaMethod The method that holds {@link Throwable}. + * @param errorCode The error code to match. + * @return The {@link Throwable} class matching the given error code if present or null. + */ + public Class getThrowableClass(java.lang.reflect.Method javaMethod, int errorCode) { + for (Class clazz : javaMethod.getExceptionTypes()) { + ThrowableAnnotationInfo tai = getThrowableAnnotationInfo(clazz); + + if (tai != null && tai.getStatus().getCode() == errorCode) { + return clazz; + } + } + + return null; + } +} diff --git a/org.restlet/src/main/java/org/restlet/engine/resource/ClientInvocationHandler.java b/org.restlet/src/main/java/org/restlet/engine/resource/ClientInvocationHandler.java index f067899631..f2a5983686 100644 --- a/org.restlet/src/main/java/org/restlet/engine/resource/ClientInvocationHandler.java +++ b/org.restlet/src/main/java/org/restlet/engine/resource/ClientInvocationHandler.java @@ -1,14 +1,19 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.resource; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -24,259 +29,291 @@ import org.restlet.resource.ResourceException; import org.restlet.resource.Result; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.List; -import java.util.logging.Level; - /** - * Reflection proxy invocation handler created for the - * {@link ClientResource#wrap(Class)} and related methods. - * + * Reflection proxy invocation handler created for the {@link ClientResource#wrap(Class)} and + * related methods. + * * @author Jerome Louvel - * * @param The annotated resource interface. */ public class ClientInvocationHandler implements InvocationHandler { - /** The annotations of the resource interface. */ - private final List annotations; - - /** The associated annotation utils. */ - private AnnotationUtils annotationUtils; - - /** The associated client resource. */ - private final ClientResource clientResource; - - /** - * Constructor. - * - * @param clientResource The associated client resource. - * @param resourceInterface The annotated resource interface. - */ - public ClientInvocationHandler(ClientResource clientResource, Class resourceInterface) { - this(clientResource, resourceInterface, AnnotationUtils.getInstance()); - } - - /** - * Constructor. - * - * @param clientResource The associated client resource. - * @param resourceInterface The annotated resource interface. - * @param annotationUtils The annotationUtils class. - */ - public ClientInvocationHandler(ClientResource clientResource, Class resourceInterface, - AnnotationUtils annotationUtils) { - this.clientResource = clientResource; - this.annotationUtils = annotationUtils; - - // Introspect the interface for Restlet annotations - this.annotations = getAnnotationUtils().getAnnotations(resourceInterface); - } - - /** - * Returns the annotations of the resource interface. - * - * @return The annotations of the resource interface. - */ - public List getAnnotations() { - return annotations; - } - - /** - * Returns the associated annotation utils. - * - * @return The associated annotation utils. - */ - public AnnotationUtils getAnnotationUtils() { - return annotationUtils; - } - - /** - * Returns the associated client resource. - * - * @return The associated client resource. - */ - public ClientResource getClientResource() { - return clientResource; - } - - /** - * Allows for child classes to modify the request. - */ - protected Request getRequest(Method javaMethod, Object[] args) throws Throwable { - return getClientResource().createRequest(); - } - - /** - * Effectively invokes a Java method on the given proxy object. - */ - @SuppressWarnings("rawtypes") - public Object invoke(Object proxy, java.lang.reflect.Method javaMethod, Object[] args) throws Throwable { - Object result = null; - - if (javaMethod.equals(Object.class.getMethod("toString"))) { - // Help debug - result = "ClientProxy for resource: " + clientResource; - } else if (javaMethod.equals(ClientProxy.class.getMethod("getClientResource"))) { - result = clientResource; - } else { - MethodAnnotationInfo annotationInfo = getAnnotationUtils().getMethodAnnotation(getAnnotations(), - javaMethod); - - if (annotationInfo != null) { - Representation requestEntity = null; - - if ((args != null) && args.length > 0) { - // Checks if the user has defined its own callback. - for (int i = 0; i < args.length; i++) { - Object o = args[i]; - - if (o == null) { - requestEntity = null; - } else if (Result.class.isAssignableFrom(o.getClass())) { - // Asynchronous mode where a callback object is to - // be called. - - // Get the kind of result expected. - final Result rCallback = (Result) o; - Type[] genericParameterTypes = javaMethod.getGenericParameterTypes(); - Type genericParameterType = genericParameterTypes[i]; - ParameterizedType parameterizedType = (genericParameterType instanceof java.lang.reflect.ParameterizedType) - ? (java.lang.reflect.ParameterizedType) genericParameterType - : null; - final Class actualType = (parameterizedType != null - && parameterizedType.getActualTypeArguments()[0] instanceof Class) - ? (Class) parameterizedType.getActualTypeArguments()[0] - : null; - - // Define the callback - Uniform callback = new Uniform() { - @SuppressWarnings("unchecked") - public void handle(Request request, Response response) { - if (response.getStatus().isError()) { - rCallback.onFailure(new ResourceException(response.getStatus())); - } else { - if (actualType != null) { - Object result = null; - boolean serializationError = false; - - try { - result = getClientResource().toObject(response.getEntity(), actualType); - } catch (Exception e) { - serializationError = true; - rCallback.onFailure(new ResourceException(e)); - } - - if (!serializationError) { - rCallback.onSuccess(result); - } - } else { - rCallback.onSuccess(null); - } - } - } - }; - - getClientResource().setOnResponse(callback); - } else { - requestEntity = getClientResource().toRepresentation(o); - } - } - } - - // Clone the prototype request - Request request = getRequest(javaMethod, args); - - // The Java method was annotated - request.setMethod(annotationInfo.getRestletMethod()); - - // Add the mandatory query parameters - String query = annotationInfo.getQuery(); - - if (query != null) { - Form queryParams = new Form(annotationInfo.getQuery()); - request.getResourceRef().addQueryParameters(queryParams); - } - - // Set the entity - request.setEntity(requestEntity); - - // Updates the client preferences if they weren't changed - if ((request.getClientInfo().getAcceptedCharacterSets().isEmpty()) - && (request.getClientInfo().getAcceptedEncodings().isEmpty()) - && (request.getClientInfo().getAcceptedLanguages().isEmpty()) - && (request.getClientInfo().getAcceptedMediaTypes().isEmpty())) { - List responseVariants = annotationInfo.getResponseVariants( - getClientResource().getMetadataService(), getClientResource().getConverterService()); - - if (responseVariants != null) { - request.setClientInfo(new ClientInfo(responseVariants)); - } - } - - // Effectively handle the call - Response response = getClientResource().handleOutbound(request); - - // Handle the response, synchronous call - if (getClientResource().getOnResponse() == null) { - if ((response != null) && response.getStatus().isError()) { - ThrowableAnnotationInfo tai = getAnnotationUtils().getThrowableAnnotationInfo(javaMethod, - response.getStatus().getCode()); - - if (tai != null) { - Class throwableClazz = tai.getJavaClass(); - Throwable t = null; - - if (tai.isSerializable() && response.isEntityAvailable()) { - t = (Throwable) getClientResource().toObject(response.getEntity(), throwableClazz); - } else { - try { - t = (Throwable) throwableClazz.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.FINE, - "Unable to instantiate the client-side exception using the default constructor."); - } - - if (response.isEntityAvailable()) { - StatusInfo si = getClientResource().toObject(response.getEntity(), - StatusInfo.class); - - if (si != null) { - response.setStatus( - new Status(si.getCode(), si.getReasonPhrase(), si.getDescription())); - } - } - } - - if (t != null) { - throw t; - } - // TODO cf issues 1004 and 1018. - // this code has been commented as the automatic - // deserialization is problematic. We may rethink a - // way to recover the status info. - // } else if (response.isEntityAvailable()) { - // StatusInfo si = getClientResource().toObject( - // response.getEntity(), StatusInfo.class); - // - // if (si != null) { - // response.setStatus(new Status(si.getCode(), si - // .getReasonPhrase(), si.getDescription())); - // } - } - - getClientResource().doError(response.getStatus()); - } else if (!annotationInfo.getJavaOutputType().equals(void.class)) { - result = getClientResource().toObject((response == null ? null : response.getEntity()), - annotationInfo.getJavaOutputType()); - } - } - } - } - - return result; - } + /** The annotations of the resource interface. */ + private final List annotations; + + /** The associated annotation utils. */ + private final AnnotationUtils annotationUtils; + + /** The associated client resource. */ + private final ClientResource clientResource; + + /** + * Constructor. + * + * @param clientResource The associated client resource. + * @param resourceInterface The annotated resource interface. + */ + public ClientInvocationHandler( + ClientResource clientResource, Class resourceInterface) { + this(clientResource, resourceInterface, AnnotationUtils.getInstance()); + } + + /** + * Constructor. + * + * @param clientResource The associated client resource. + * @param resourceInterface The annotated resource interface. + * @param annotationUtils The annotationUtils class. + */ + public ClientInvocationHandler( + ClientResource clientResource, + Class resourceInterface, + AnnotationUtils annotationUtils) { + this.clientResource = clientResource; + this.annotationUtils = annotationUtils; + + // Introspect the interface for Restlet annotations + this.annotations = getAnnotationUtils().getAnnotations(resourceInterface); + } + + /** + * Returns the annotations of the resource interface. + * + * @return The annotations of the resource interface. + */ + public List getAnnotations() { + return annotations; + } + + /** + * Returns the associated annotation utils. + * + * @return The associated annotation utils. + */ + public AnnotationUtils getAnnotationUtils() { + return annotationUtils; + } + + /** + * Returns the associated client resource. + * + * @return The associated client resource. + */ + public ClientResource getClientResource() { + return clientResource; + } + + /** Allows for child classes to modify the request. */ + protected Request getRequest(Method javaMethod, Object[] args) throws Throwable { + return getClientResource().createRequest(); + } + + /** Effectively invokes a Java method on the given proxy object. */ + @SuppressWarnings("rawtypes") + public Object invoke(Object proxy, java.lang.reflect.Method javaMethod, Object[] args) + throws Throwable { + Object result = null; + + if (javaMethod.equals(Object.class.getMethod("toString"))) { + // Help debug + result = "ClientProxy for resource: " + clientResource; + } else if (javaMethod.equals(ClientProxy.class.getMethod("getClientResource"))) { + result = clientResource; + } else { + MethodAnnotationInfo annotationInfo = + getAnnotationUtils().getMethodAnnotation(getAnnotations(), javaMethod); + + if (annotationInfo != null) { + Representation requestEntity = null; + + if ((args != null) && args.length > 0) { + // Checks if the user has defined its own callback. + for (int i = 0; i < args.length; i++) { + Object o = args[i]; + + if (o == null) { + requestEntity = null; + } else if (Result.class.isAssignableFrom(o.getClass())) { + // Asynchronous mode where a callback object is to + // be called. + + // Get the kind of result expected. + final Result rCallback = (Result) o; + Type[] genericParameterTypes = javaMethod.getGenericParameterTypes(); + Type genericParameterType = genericParameterTypes[i]; + + final Class actualType; + if (genericParameterType + instanceof ParameterizedType parameterizedType) { + if (parameterizedType.getActualTypeArguments()[0] + instanceof Class) { + actualType = + (Class) + parameterizedType.getActualTypeArguments()[0]; + } else { + actualType = null; + } + } else { + actualType = null; + } + + // Define the callback + Uniform callback = + new Uniform() { + @SuppressWarnings("unchecked") + public void handle(Request request, Response response) { + if (response.getStatus().isError()) { + rCallback.onFailure( + new ResourceException( + response.getStatus())); + } else { + if (actualType != null) { + Object result = null; + boolean serializationError = false; + + try { + result = + getClientResource() + .toObject( + response + .getEntity(), + actualType); + } catch (Exception e) { + serializationError = true; + rCallback.onFailure( + new ResourceException(e)); + } + + if (!serializationError) { + rCallback.onSuccess(result); + } + } else { + rCallback.onSuccess(null); + } + } + } + }; + + getClientResource().setOnResponse(callback); + } else { + requestEntity = getClientResource().toRepresentation(o); + } + } + } + + // Clone the prototype request + Request request = getRequest(javaMethod, args); + + // The Java method was annotated + request.setMethod(annotationInfo.getRestletMethod()); + + // Add the mandatory query parameters + String query = annotationInfo.getQuery(); + + if (query != null) { + Form queryParams = new Form(annotationInfo.getQuery()); + request.getResourceRef().addQueryParameters(queryParams); + } + + // Set the entity + request.setEntity(requestEntity); + + // Updates the client preferences if they weren't changed + if ((request.getClientInfo().getAcceptedCharacterSets().isEmpty()) + && (request.getClientInfo().getAcceptedEncodings().isEmpty()) + && (request.getClientInfo().getAcceptedLanguages().isEmpty()) + && (request.getClientInfo().getAcceptedMediaTypes().isEmpty())) { + List responseVariants = + annotationInfo.getResponseVariants( + getClientResource().getMetadataService(), + getClientResource().getConverterService()); + + if (responseVariants != null) { + request.setClientInfo(new ClientInfo(responseVariants)); + } + } + + // Effectively handle the call + Response response = getClientResource().handleOutbound(request); + + // Handle the response, synchronous call + if (getClientResource().getOnResponse() == null) { + if ((response != null) && response.getStatus().isError()) { + ThrowableAnnotationInfo tai = + getAnnotationUtils() + .getThrowableAnnotationInfo( + javaMethod, response.getStatus().getCode()); + + if (tai != null) { + Class throwableClazz = tai.getJavaClass(); + Throwable t = null; + + if (tai.isSerializable() && response.isEntityAvailable()) { + t = + (Throwable) + getClientResource() + .toObject( + response.getEntity(), + throwableClazz); + } else { + try { + t = + (Throwable) + throwableClazz + .getDeclaredConstructor() + .newInstance(); + } catch (Exception e) { + Context.getCurrentLogger() + .log( + Level.FINE, + "Unable to instantiate the client-side exception using the default constructor."); + } + + if (response.isEntityAvailable()) { + StatusInfo si = + getClientResource() + .toObject( + response.getEntity(), StatusInfo.class); + + if (si != null) { + response.setStatus( + new Status( + si.getCode(), + si.getReasonPhrase(), + si.getDescription())); + } + } + } + + if (t != null) { + throw t; + } + // TODO cf issues 1004 and 1018. + // this code has been commented as the automatic + // deserialization is problematic. We may rethink a + // way to recover the status info. + // } else if (response.isEntityAvailable()) { + // StatusInfo si = getClientResource().toObject( + // response.getEntity(), StatusInfo.class); + // + // if (si != null) { + // response.setStatus(new Status(si.getCode(), si + // .getReasonPhrase(), si.getDescription())); + // } + } + + getClientResource().doError(response.getStatus()); + } else if (!annotationInfo.getJavaOutputType().equals(void.class)) { + result = + getClientResource() + .toObject( + (response == null ? null : response.getEntity()), + annotationInfo.getJavaOutputType()); + } + } + } + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/resource/MethodAnnotationInfo.java b/org.restlet/src/main/java/org/restlet/engine/resource/MethodAnnotationInfo.java index 43d91bab05..ce0064ffd8 100644 --- a/org.restlet/src/main/java/org/restlet/engine/resource/MethodAnnotationInfo.java +++ b/org.restlet/src/main/java/org/restlet/engine/resource/MethodAnnotationInfo.java @@ -1,25 +1,32 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.resource; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.StringTokenizer; import org.restlet.Context; -import org.restlet.data.*; +import org.restlet.data.CharacterSet; +import org.restlet.data.Form; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; +import org.restlet.data.Method; +import org.restlet.data.Parameter; import org.restlet.engine.util.StringUtils; import org.restlet.engine.util.SystemUtils; import org.restlet.representation.Representation; import org.restlet.representation.Variant; import org.restlet.service.MetadataService; -import java.io.IOException; -import java.util.*; - /** * Descriptor for method annotations. * @@ -27,371 +34,380 @@ */ public class MethodAnnotationInfo extends AnnotationInfo { - /** The input part of the annotation value. */ - private final String input; - - /** The output part of the annotation value. */ - private final String output; - - /** The optional query part of the annotation value. */ - private final String query; - - /** The matching Restlet method. */ - private final Method restletMethod; - - /** - * Constructor. - * - * @param javaClass The class or interface that hosts the annotated Java - * method. - * @param restletMethod The matching Restlet method. - * @param javaMethod The annotated Java method. - * @param annotationValue The annotation value. - */ - public MethodAnnotationInfo(Class javaClass, Method restletMethod, java.lang.reflect.Method javaMethod, - String annotationValue) { - super(javaClass, javaMethod, annotationValue); - this.restletMethod = restletMethod; - - // Parse the main components of the annotation value - if (!StringUtils.isNullOrEmpty(annotationValue)) { - int queryIndex = annotationValue.indexOf('?'); - - if (queryIndex != -1) { - this.query = annotationValue.substring(queryIndex + 1); - annotationValue = annotationValue.substring(0, queryIndex); - } else { - this.query = null; - } - - int ioSeparatorIndex = annotationValue.indexOf(':'); - - if (ioSeparatorIndex != -1) { - this.input = annotationValue.substring(0, ioSeparatorIndex); - this.output = annotationValue.substring(ioSeparatorIndex + 1); - } else { - this.input = annotationValue; - this.output = annotationValue; - } - - } else { - this.query = null; - this.input = null; - this.output = null; - } - } - - /** - * Indicates if the current object is equal to the given object. - * - * @param other The other object. - * @return True if the current object includes the other. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof MethodAnnotationInfo)) { - return false; - } - - MethodAnnotationInfo that = (MethodAnnotationInfo) other; - - return super.equals(that) && Objects.equals(getRestletMethod(), that.getRestletMethod()); - } - - /** - * Returns the input part of the annotation value. - * - * @return The input part of the annotation value. - */ - public String getInput() { - return input; - } - - /** - * Returns the generic type for the given input parameter. - * - * @param index The input parameter index. - * @return The generic type. - */ - private Class getJavaInputType(int index) { - return getJavaActualType(javaMethodImpl.getParameterTypes()[index], - javaMethodImpl.getGenericParameterTypes()[index]); - } - - /** - * Returns the input types of the Java method. - * - * @return The input types of the Java method. - */ - public Class[] getJavaInputTypes() { - int count = getJavaMethod().getParameterTypes().length; - Class[] classes = new Class[count]; - - for (int i = 0; i < count; i++) { - classes[i] = getJavaInputType(i); - } - - return classes; - } - - /** - * Returns the output type of the Java method. - * - * @return The output type of the Java method. - */ - public Class getJavaOutputType() { - return getJavaActualType(javaMethodImpl.getReturnType(), javaMethodImpl.getGenericReturnType()); - } - - /** - * Returns the output part of the annotation value. - * - * @return The output part of the annotation value. - */ - public String getOutput() { - return output; - } - - /** - * Returns the optional query part of the annotation value. - * - * @return The optional query part of the annotation value. - */ - public String getQuery() { - return query; - } - - /** - * Returns a list of request variants based on the annotation value. - * - * @param metadataService The metadata service to use. - * @return A list of request variants. - * @throws IOException - */ - @SuppressWarnings("unchecked") - public List getRequestVariants(MetadataService metadataService, - org.restlet.service.ConverterService converterService) throws IOException { - List result = null; - Class[] classes = getJavaInputTypes(); - - if (classes != null && classes.length >= 1) { - result = getVariants(metadataService, getInput()); - - if (result == null) { - Class inputClass = classes[0]; - - if (inputClass != null) { - result = (List) converterService.getVariants(inputClass, null); - } - } - } - - return result; - } - - /** - * Returns a list of response variants based on the annotation value. - * - * @param metadataService The metadata service to use. - * @param converterService The converter service to use. - * @return A list of response variants. - * @throws IOException - */ - @SuppressWarnings("unchecked") - public List getResponseVariants(MetadataService metadataService, - org.restlet.service.ConverterService converterService) throws IOException { - List result = null; - - if ((getJavaOutputType() != null) && (getJavaOutputType() != void.class) - && (getJavaOutputType() != Void.class)) { - result = getVariants(metadataService, getOutput()); - - if (result == null) { - result = (List) converterService.getVariants(getJavaOutputType(), null); - } - } - - return result; - } - - /** - * Returns the matching Restlet method. - * - * @return The matching Restlet method. - */ - public Method getRestletMethod() { - return restletMethod; - } - - /** - * Returns the list of representation variants associated to a given annotation - * value, corresponding to either an input or output entity. - * - * @param metadataService The metadata service to use. - * @param annotationValue The entity annotation value. - * @return A list of variants. - */ - private List getVariants(MetadataService metadataService, String annotationValue) { - List result = null; - - if (annotationValue != null) { - - StringTokenizer stValue = new StringTokenizer(annotationValue, "\\|"); - while (stValue.hasMoreTokens()) { - String variantValue = stValue.nextToken().trim(); - - Variant variant = null; - List mediaTypes = null; - List languages = null; - CharacterSet characterSet = null; - - StringTokenizer stExtension = new StringTokenizer(variantValue, "\\+"); - while (stExtension.hasMoreTokens()) { - String extension = stExtension.nextToken().trim(); - if (extension.isEmpty()) { - continue; - } - - List metadataList = metadataService.getAllMetadata(extension); - - if (metadataList != null) { - for (Metadata metadata : metadataList) { - if (metadata instanceof MediaType) { - if (mediaTypes == null) { - mediaTypes = new ArrayList<>(); - } - - mediaTypes.add((MediaType) metadata); - } else if (metadata instanceof Language) { - if (languages == null) { - languages = new ArrayList<>(); - } - - languages.add((Language) metadata); - } else if (metadata instanceof CharacterSet) { - if (characterSet == null) { - characterSet = (CharacterSet) metadata; - } else { - Context.getCurrentLogger().warning( - "A representation variant can have only one character set. Please check your annotation value."); - } - } - } - } - } - - // Now build the representation variants - if (mediaTypes != null) { - for (MediaType mediaType : mediaTypes) { - if ((result == null) || (!result.contains(mediaType))) { - if (result == null) { - result = new ArrayList<>(); - } - - variant = new Variant(mediaType); - - if (languages != null) { - variant.getLanguages().addAll(languages); - } - - if (characterSet != null) { - variant.setCharacterSet(characterSet); - } - - result.add(variant); - } - } - } - } - } - - return result; - } - - @Override - public int hashCode() { - return SystemUtils.hashCode(super.hashCode(), restletMethod); - } - - /** - * Indicates if the annotated method described is compatible with the given - * parameters. - * - * @param restletMethod The Restlet method to match. - * @param requestEntity Optional request entity. - * @param metadataService The metadata service to use. - * @param converterService The converter service to use. - * @return True if the annotated method is compatible. - * @throws IOException - */ - public boolean isCompatible(Method restletMethod, Form queryParams, Representation requestEntity, - MetadataService metadataService, org.restlet.service.ConverterService converterService) throws IOException { - boolean result = true; - - // Verify query parameters - if (getQuery() != null) { - Form requiredParams = new Form(getQuery()); - - for (Iterator iter = requiredParams.iterator(); iter.hasNext() && result;) { - result = queryParams.contains(iter.next()); - } - } - - // Verify HTTP method - if (result) { - result = getRestletMethod().equals(restletMethod); - } - - // Verify request entity - if (result) { - result = isCompatibleRequestEntity(requestEntity, metadataService, converterService); - - } - - return result; - } - - /** - * Indicates if the given request entity is compatible with the annotated method - * described. - * - * @param requestEntity Optional request entity. - * @param metadataService The metadata service to use. - * @param converterService The converter service to use. - * @return True if the given request entity is compatible with the annotated - * method described. - * @throws IOException - */ - public boolean isCompatibleRequestEntity(Representation requestEntity, MetadataService metadataService, - org.restlet.service.ConverterService converterService) throws IOException { - boolean result = true; - - if ((requestEntity != null) && requestEntity.isAvailable()) { - List requestVariants = getRequestVariants(metadataService, converterService); - - if ((requestVariants != null) && !requestVariants.isEmpty()) { - // Check that the compatibility - result = false; - - for (int i = 0; (!result) && (i < requestVariants.size()); i++) { - result = (requestVariants.get(i).isCompatible(requestEntity)); - } - } else { - result = false; - } - } - - return result; - } - - @Override - public String toString() { - return "MethodAnnotationInfo [javaMethod: " + javaMethod + ", javaClass: " + getJavaClass() - + ", restletMethod: " + restletMethod + ", input: " + getInput() + ", value: " + getAnnotationValue() - + ", output: " + getOutput() + ", query: " + getQuery() + "]"; - } - + /** The input part of the annotation value. */ + private final String input; + + /** The output part of the annotation value. */ + private final String output; + + /** The optional query part of the annotation value. */ + private final String query; + + /** The matching Restlet method. */ + private final Method restletMethod; + + /** + * Constructor. + * + * @param javaClass The class or interface that hosts the annotated Java method. + * @param restletMethod The matching Restlet method. + * @param javaMethod The annotated Java method. + * @param annotationValue The annotation value. + */ + public MethodAnnotationInfo( + Class javaClass, + Method restletMethod, + java.lang.reflect.Method javaMethod, + String annotationValue) { + super(javaClass, javaMethod, annotationValue); + this.restletMethod = restletMethod; + + // Parse the main parts of the annotation value + if (!StringUtils.isNullOrEmpty(annotationValue)) { + int queryIndex = annotationValue.indexOf('?'); + + if (queryIndex != -1) { + this.query = annotationValue.substring(queryIndex + 1); + annotationValue = annotationValue.substring(0, queryIndex); + } else { + this.query = null; + } + + int ioSeparatorIndex = annotationValue.indexOf(':'); + + if (ioSeparatorIndex != -1) { + this.input = annotationValue.substring(0, ioSeparatorIndex); + this.output = annotationValue.substring(ioSeparatorIndex + 1); + } else { + this.input = annotationValue; + this.output = annotationValue; + } + + } else { + this.query = null; + this.input = null; + this.output = null; + } + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof final MethodAnnotationInfo that)) { + return false; + } + + return super.equals(that) && Objects.equals(getRestletMethod(), that.getRestletMethod()); + } + + /** + * Returns the input part of the annotation value. + * + * @return The input part of the annotation value. + */ + public String getInput() { + return input; + } + + /** + * Returns the generic type for the given input parameter. + * + * @param index The input parameter index. + * @return The generic type. + */ + private Class getJavaInputType(int index) { + return getJavaActualType( + javaMethodImpl.getParameterTypes()[index], + javaMethodImpl.getGenericParameterTypes()[index]); + } + + /** + * Returns the input types of the Java method. + * + * @return The input types of the Java method. + */ + public Class[] getJavaInputTypes() { + int count = getJavaMethod().getParameterTypes().length; + Class[] classes = new Class[count]; + + for (int i = 0; i < count; i++) { + classes[i] = getJavaInputType(i); + } + + return classes; + } + + /** + * Returns the output type of the Java method. + * + * @return The output type of the Java method. + */ + public Class getJavaOutputType() { + return getJavaActualType( + javaMethodImpl.getReturnType(), javaMethodImpl.getGenericReturnType()); + } + + /** + * Returns the output part of the annotation value. + * + * @return The output part of the annotation value. + */ + public String getOutput() { + return output; + } + + /** + * Returns the optional query part of the annotation value. + * + * @return The optional query part of the annotation value. + */ + public String getQuery() { + return query; + } + + /** + * Returns a list of request variants based on the annotation value. + * + * @param metadataService The metadata service to use. + * @return A list of request variants. + */ + @SuppressWarnings("unchecked") + public List getRequestVariants( + MetadataService metadataService, + org.restlet.service.ConverterService converterService) { + List result = null; + Class[] classes = getJavaInputTypes(); + + if (classes != null && classes.length >= 1) { + result = getVariants(metadataService, getInput()); + + if (result == null) { + Class inputClass = classes[0]; + + if (inputClass != null) { + result = (List) converterService.getVariants(inputClass, null); + } + } + } + + return result; + } + + /** + * Returns a list of response variants based on the annotation value. + * + * @param metadataService The metadata service to use. + * @param converterService The converter service to use. + * @return A list of response variants. + */ + @SuppressWarnings("unchecked") + public List getResponseVariants( + MetadataService metadataService, + org.restlet.service.ConverterService converterService) { + List result = null; + + if ((getJavaOutputType() != null) + && (getJavaOutputType() != void.class) + && (getJavaOutputType() != Void.class)) { + result = getVariants(metadataService, getOutput()); + + if (result == null) { + result = (List) converterService.getVariants(getJavaOutputType(), null); + } + } + + return result; + } + + /** + * Returns the matching Restlet method. + * + * @return The matching Restlet method. + */ + public Method getRestletMethod() { + return restletMethod; + } + + /** + * Returns the list of representation variants associated with a given annotation value, + * corresponding to either an input or output entity. + * + * @param metadataService The metadata service to use. + * @param annotationValue The entity annotation value. + * @return A list of variants. + */ + private List getVariants(MetadataService metadataService, String annotationValue) { + List result = null; + + if (annotationValue != null) { + + StringTokenizer stValue = new StringTokenizer(annotationValue, "\\|"); + while (stValue.hasMoreTokens()) { + String variantValue = stValue.nextToken().trim(); + + Variant variant = null; + List mediaTypes = null; + List languages = null; + CharacterSet characterSet = null; + + StringTokenizer stExtension = new StringTokenizer(variantValue, "\\+"); + while (stExtension.hasMoreTokens()) { + String extension = stExtension.nextToken().trim(); + if (extension.isEmpty()) { + continue; + } + + List metadataList = metadataService.getAllMetadata(extension); + + if (metadataList != null) { + for (Metadata metadata : metadataList) { + if (metadata instanceof MediaType mediaType) { + if (mediaTypes == null) { + mediaTypes = new ArrayList<>(); + } + + mediaTypes.add(mediaType); + } else if (metadata instanceof Language language) { + if (languages == null) { + languages = new ArrayList<>(); + } + + languages.add(language); + } else if (metadata instanceof CharacterSet characterSet1) { + if (characterSet == null) { + characterSet = characterSet1; + } else { + Context.getCurrentLogger() + .warning( + "A representation variant can have only one character set. Please check your annotation value."); + } + } + } + } + } + + // Now build the representation variants + if (mediaTypes != null) { + for (MediaType mediaType : mediaTypes) { + if ((result == null) || (!result.contains(mediaType))) { + if (result == null) { + result = new ArrayList<>(); + } + + variant = new Variant(mediaType); + + if (languages != null) { + variant.getLanguages().addAll(languages); + } + + if (characterSet != null) { + variant.setCharacterSet(characterSet); + } + + result.add(variant); + } + } + } + } + } + + return result; + } + + @Override + public int hashCode() { + return SystemUtils.hashCode(super.hashCode(), restletMethod); + } + + /** + * Indicates if the annotated method described is compatible with the given parameters. + * + * @param restletMethod The Restlet method to match. + * @param requestEntity Optional request entity. + * @param metadataService The metadata service to use. + * @param converterService The converter service to use. + * @return True if the annotated method is compatible. + */ + public boolean isCompatible( + Method restletMethod, + Form queryParams, + Representation requestEntity, + MetadataService metadataService, + org.restlet.service.ConverterService converterService) { + boolean result = true; + + // Verify query parameters + if (getQuery() != null) { + for (Iterator iter = new Form(getQuery()).iterator(); + iter.hasNext() && result; ) { + result = queryParams.contains(iter.next()); + } + } + + // Verify HTTP method + if (result) { + result = getRestletMethod().equals(restletMethod); + } + + // Verify request entity + if (result) { + result = isCompatibleRequestEntity(requestEntity, metadataService, converterService); + } + + return result; + } + + /** + * Indicates if the given request entity is compatible with the annotated method described. + * + * @param requestEntity Optional request entity. + * @param metadataService The metadata service to use. + * @param converterService The converter service to use. + * @return True if the given request entity is compatible with the annotated method described. + */ + public boolean isCompatibleRequestEntity( + Representation requestEntity, + MetadataService metadataService, + org.restlet.service.ConverterService converterService) { + boolean result = true; + + if ((requestEntity != null) && requestEntity.isAvailable()) { + List requestVariants = getRequestVariants(metadataService, converterService); + + if ((requestVariants != null) && !requestVariants.isEmpty()) { + // Check that the compatibility + result = false; + + for (int i = 0; (!result) && (i < requestVariants.size()); i++) { + result = (requestVariants.get(i).isCompatible(requestEntity)); + } + } else { + result = false; + } + } + + return result; + } + + @Override + public String toString() { + return "MethodAnnotationInfo [javaMethod: " + + javaMethod + + ", javaClass: " + + getJavaClass() + + ", restletMethod: " + + restletMethod + + ", input: " + + getInput() + + ", value: " + + getAnnotationValue() + + ", output: " + + getOutput() + + ", query: " + + getQuery() + + "]"; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/resource/ThrowableAnnotationInfo.java b/org.restlet/src/main/java/org/restlet/engine/resource/ThrowableAnnotationInfo.java index 53764b5b0f..871ee9adca 100644 --- a/org.restlet/src/main/java/org/restlet/engine/resource/ThrowableAnnotationInfo.java +++ b/org.restlet/src/main/java/org/restlet/engine/resource/ThrowableAnnotationInfo.java @@ -1,95 +1,84 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.resource; +import java.util.Objects; import org.restlet.data.Status; import org.restlet.engine.util.SystemUtils; -import java.util.Objects; - /** * Descriptor for status annotations. - * + * * @author Jerome Louvel */ public class ThrowableAnnotationInfo extends AnnotationInfo { - /** Indicates if the {@link Status#getThrowable()} should be serialized. */ - private final boolean serializable; - - /** The status parsed from the annotation value. */ - private final Status status; - - /** - * Constructor. - * - * @param throwableClass The class or interface that hosts the annotated Java - * method. - * @param annotationValue The annotation value containing the HTTP error code. - * @param serializable Indicates if the {@link Throwable} should be - * serialized. - */ - public ThrowableAnnotationInfo(Class throwableClass, int annotationValue, boolean serializable) { - super(throwableClass, Integer.toString(annotationValue)); - - // Parse the main components of the annotation value - this.status = Status.valueOf(annotationValue); - this.serializable = serializable; - } - - /** - * Indicates if the current object is equal to the given object. - * - * @param other The other object. - * @return True if the current object includes the other. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof ThrowableAnnotationInfo)) { - return false; - } - - ThrowableAnnotationInfo that = (ThrowableAnnotationInfo) other; - - return super.equals(that) && Objects.equals(getStatus(), that.getStatus()); - } - - /** - * Returns the status parsed from the annotation value. - * - * @return The status parsed from the annotation value. - */ - public Status getStatus() { - return status; - } - - @Override - public int hashCode() { - return SystemUtils.hashCode(super.hashCode(), status); - } - - /** - * Returns the serialize indicator parsed from the annotation value. - * - * @return the serialize indicator parsed from the annotation value. - */ - public boolean isSerializable() { - return serializable; - } - - @Override - public String toString() { - return "ExceptionAnnotationInfo [status=" + status + ", serializable=" + serializable + "]"; - } - + /** Indicates if the {@link Status#getThrowable()} should be serialized. */ + private final boolean serializable; + + /** The status parsed from the annotation value. */ + private final Status status; + + /** + * Constructor. + * + * @param throwableClass The class or interface that hosts the annotated Java method. + * @param annotationValue The annotation value containing the HTTP error code. + * @param serializable Indicates if the {@link Throwable} should be serialized. + */ + public ThrowableAnnotationInfo( + Class throwableClass, int annotationValue, boolean serializable) { + super(throwableClass, Integer.toString(annotationValue)); + + // Parse the main parts of the annotation value + this.status = Status.valueOf(annotationValue); + this.serializable = serializable; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof final ThrowableAnnotationInfo that)) { + return false; + } + + return super.equals(that) && Objects.equals(getStatus(), that.getStatus()); + } + + /** + * Returns the status parsed from the annotation value. + * + * @return The status parsed from the annotation value. + */ + public Status getStatus() { + return status; + } + + @Override + public int hashCode() { + return SystemUtils.hashCode(super.hashCode(), status); + } + + /** + * Returns the serialized indicator parsed from the annotation value. + * + * @return the serialized indicator parsed from the annotation value. + */ + public boolean isSerializable() { + return serializable; + } + + @Override + public String toString() { + return "ExceptionAnnotationInfo [status=" + status + ", serializable=" + serializable + "]"; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/resource/VariantInfo.java b/org.restlet/src/main/java/org/restlet/engine/resource/VariantInfo.java index a332fc9757..0bd6c797ba 100644 --- a/org.restlet/src/main/java/org/restlet/engine/resource/VariantInfo.java +++ b/org.restlet/src/main/java/org/restlet/engine/resource/VariantInfo.java @@ -1,117 +1,107 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.resource; +import java.util.Objects; import org.restlet.data.MediaType; import org.restlet.engine.util.SystemUtils; import org.restlet.representation.Variant; -import java.util.Objects; - /** * Variant that is declared by an annotated Java method. - * + * * @author Jerome Louvel */ public class VariantInfo extends Variant { - /** The optional annotation descriptor. */ - private final MethodAnnotationInfo annotationInfo; - - /** Affinity between this variant and an incoming representation. */ - private float inputScore; - - /** - * Constructor. - * - * @param mediaType The media type. - */ - public VariantInfo(MediaType mediaType) { - this(mediaType, null); - } - - /** - * Constructor. - * - * @param mediaType The media type. - * @param annotationInfo The optional annotation descriptor. - */ - public VariantInfo(MediaType mediaType, MethodAnnotationInfo annotationInfo) { - super(mediaType); - this.annotationInfo = annotationInfo; - inputScore = 1.0f; - } - - /** - * Constructor. - * - * @param variant The variant to enrich. - * @param annotationInfo The optional annotation descriptor. - */ - public VariantInfo(Variant variant, MethodAnnotationInfo annotationInfo) { - this(variant.getMediaType(), annotationInfo); - setCharacterSet(variant.getCharacterSet()); - setEncodings(variant.getEncodings()); - setLanguages(variant.getLanguages()); - } - - /** - * Indicates if the current variant is equal to the given variant. - * - * @param other The other variant. - * @return True if the current variant includes the other. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof VariantInfo)) { - return false; - } - - VariantInfo that = (VariantInfo) other; - - return super.equals(that) && Objects.equals(getAnnotationInfo(), that.getAnnotationInfo()); - } - - /** - * Returns the optional annotation descriptor. - * - * @return The optional annotation descriptor. - */ - public MethodAnnotationInfo getAnnotationInfo() { - return annotationInfo; - } - - /** - * Returns the affinity between this variant and an incoming representation. - * - * @return The affinity between this variant and an incoming representation. - */ - public float getInputScore() { - return inputScore; - } - - @Override - public int hashCode() { - return SystemUtils.hashCode(super.hashCode(), annotationInfo); - } - - /** - * Sets the affinity between this variant and an incoming representation. - * - * @param inputScore The affinity between this variant and an incoming - * representation. - */ - public void setInputScore(float inputScore) { - this.inputScore = inputScore; - } + /** The optional annotation descriptor. */ + private final MethodAnnotationInfo annotationInfo; + + /** Affinity between this variant and an incoming representation. */ + private float inputScore; + + /** + * Constructor. + * + * @param mediaType The media type. + */ + public VariantInfo(MediaType mediaType) { + this(mediaType, null); + } + + /** + * Constructor. + * + * @param mediaType The media type. + * @param annotationInfo The optional annotation descriptor. + */ + public VariantInfo(MediaType mediaType, MethodAnnotationInfo annotationInfo) { + super(mediaType); + this.annotationInfo = annotationInfo; + inputScore = 1.0f; + } + + /** + * Constructor. + * + * @param variant The variant to enrich. + * @param annotationInfo The optional annotation descriptor. + */ + public VariantInfo(Variant variant, MethodAnnotationInfo annotationInfo) { + this(variant.getMediaType(), annotationInfo); + setCharacterSet(variant.getCharacterSet()); + setEncodings(variant.getEncodings()); + setLanguages(variant.getLanguages()); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof final VariantInfo that)) { + return false; + } + + return super.equals(that) && Objects.equals(getAnnotationInfo(), that.getAnnotationInfo()); + } + + /** + * Returns the optional annotation descriptor. + * + * @return The optional annotation descriptor. + */ + public MethodAnnotationInfo getAnnotationInfo() { + return annotationInfo; + } + + /** + * Returns the affinity between this variant and an incoming representation. + * + * @return The affinity between this variant and an incoming representation. + */ + public float getInputScore() { + return inputScore; + } + + @Override + public int hashCode() { + return SystemUtils.hashCode(super.hashCode(), annotationInfo); + } + + /** + * Sets the affinity between this variant and an incoming representation. + * + * @param inputScore The affinity between this variant and an incoming representation. + */ + public void setInputScore(float inputScore) { + this.inputScore = inputScore; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/resource/package-info.java b/org.restlet/src/main/java/org/restlet/engine/resource/package-info.java new file mode 100644 index 0000000000..2c2ba780ce --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/resource/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports resources. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.resource; diff --git a/org.restlet/src/main/java/org/restlet/engine/resource/package.html b/org.restlet/src/main/java/org/restlet/engine/resource/package.html deleted file mode 100644 index e2886f941c..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/resource/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Supports resources. -

-@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/security/AuthenticatorHelper.java b/org.restlet/src/main/java/org/restlet/engine/security/AuthenticatorHelper.java index 38b30073ed..ebebdb71db 100644 --- a/org.restlet/src/main/java/org/restlet/engine/security/AuthenticatorHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/security/AuthenticatorHelper.java @@ -1,177 +1,183 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.security; +import java.util.logging.Logger; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; -import org.restlet.data.*; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.Header; +import org.restlet.data.Reference; import org.restlet.engine.Helper; import org.restlet.engine.header.ChallengeWriter; import org.restlet.engine.header.HeaderConstants; import org.restlet.util.Series; -import java.io.IOException; -import java.util.logging.Logger; - /** * Base class for authentication helpers. - * + * * @author Jerome Louvel */ public abstract class AuthenticatorHelper extends Helper { - /** The supported challenge scheme. */ - private volatile ChallengeScheme challengeScheme; - - /** Indicates if client side authentication is supported. */ - private volatile boolean clientSide; - - /** Indicates if server side authentication is supported. */ - private volatile boolean serverSide; - - /** - * Constructor. - * - * @param challengeScheme The supported challenge scheme. - * @param clientSide Indicates if client side authentication is supported. - * @param serverSide Indicates if server side authentication is supported. - */ - public AuthenticatorHelper(ChallengeScheme challengeScheme, boolean clientSide, boolean serverSide) { - this.challengeScheme = challengeScheme; - this.clientSide = clientSide; - this.serverSide = serverSide; - } - - /** - * Formats a challenge request as raw credentials. - * - * @param cw The header writer to update. - * @param challenge The challenge request to format. - * @param response The parent response. - * @param httpHeaders The current request HTTP headers. - */ - public void formatRequest(ChallengeWriter cw, ChallengeRequest challenge, Response response, - Series

httpHeaders) throws IOException { - } - - /** - * Formats a challenge response as raw credentials. - * - * @param cw The header writer to update. - * @param challenge The challenge response to format. - * @param request The parent request. - * @param httpHeaders The current request HTTP headers. - */ - public void formatResponse(ChallengeWriter cw, ChallengeResponse challenge, Request request, - Series
httpHeaders) { - } - - /** - * Returns the supported challenge scheme. - * - * @return The supported challenge scheme. - */ - public ChallengeScheme getChallengeScheme() { - return this.challengeScheme; - } - - /** - * Returns the context's logger. - * - * @return The context's logger. - */ - public Logger getLogger() { - return Context.getCurrentLogger(); - } - - /** - * Indicates if client side authentication is supported. - * - * @return True if client side authentication is supported. - */ - public boolean isClientSide() { - return this.clientSide; - } - - /** - * Indicates if server side authentication is supported. - * - * @return True if server side authentication is supported. - */ - public boolean isServerSide() { - return this.serverSide; - } - - /** - * Parses an authenticate header into a challenge request. The header is - * {@link HeaderConstants#HEADER_WWW_AUTHENTICATE}. - * - * @param challenge The challenge request to update. - * @param response The parent response. - * @param httpHeaders The current response HTTP headers. - */ - public void parseRequest(ChallengeRequest challenge, Response response, Series
httpHeaders) { - } - - /** - * Parses an authorization header into a challenge response. The header is - * {@link HeaderConstants#HEADER_AUTHORIZATION}. - * - * @param challenge The challenge response to update. - * @param request The parent request. - * @param httpHeaders The current request HTTP headers. - */ - public void parseResponse(ChallengeResponse challenge, Request request, Series
httpHeaders) { - } - - /** - * Sets the supported challenge scheme. - * - * @param challengeScheme The supported challenge scheme. - */ - public void setChallengeScheme(ChallengeScheme challengeScheme) { - this.challengeScheme = challengeScheme; - } - - /** - * Indicates if client side authentication is supported. - * - * @param clientSide True if client side authentication is supported. - */ - public void setClientSide(boolean clientSide) { - this.clientSide = clientSide; - } - - /** - * Indicates if server side authentication is supported. - * - * @param serverSide True if server side authentication is supported. - */ - public void setServerSide(boolean serverSide) { - this.serverSide = serverSide; - } - - /** - * Optionally updates the request with a challenge response before sending it. - * This is sometimes useful for authentication schemes that aren't based on the - * Authorization header but instead on URI query parameters or other headers. By - * default it returns the resource URI reference unchanged. - * - * @param resourceRef The resource URI reference to update. - * @param challengeResponse The challenge response provided. - * @param request The request to update. - * @return The original URI reference if unchanged or a new one if updated. - */ - public Reference updateReference(Reference resourceRef, ChallengeResponse challengeResponse, Request request) { - return resourceRef; - } - + /** The supported challenge scheme. */ + private volatile ChallengeScheme challengeScheme; + + /** Indicates if client side authentication is supported. */ + private volatile boolean clientSide; + + /** Indicates if server side authentication is supported. */ + private volatile boolean serverSide; + + /** + * Constructor. + * + * @param challengeScheme The supported challenge scheme. + * @param clientSide Indicates if client side authentication is supported. + * @param serverSide Indicates if server side authentication is supported. + */ + protected AuthenticatorHelper( + ChallengeScheme challengeScheme, boolean clientSide, boolean serverSide) { + this.challengeScheme = challengeScheme; + this.clientSide = clientSide; + this.serverSide = serverSide; + } + + /** + * Formats a challenge request as raw credentials. + * + * @param cw The header writer to update. + * @param challenge The challenge request to format. + * @param response The parent response. + * @param httpHeaders The current request HTTP headers. + */ + public void formatRequest( + ChallengeWriter cw, + ChallengeRequest challenge, + Response response, + Series
httpHeaders) {} + + /** + * Formats a challenge response as raw credentials. + * + * @param cw The header writer to update. + * @param challenge The challenge response to format. + * @param request The parent request. + * @param httpHeaders The current request HTTP headers. + */ + public void formatResponse( + ChallengeWriter cw, + ChallengeResponse challenge, + Request request, + Series
httpHeaders) {} + + /** + * Returns the supported challenge scheme. + * + * @return The supported challenge scheme. + */ + public ChallengeScheme getChallengeScheme() { + return this.challengeScheme; + } + + /** + * Returns the context's logger. + * + * @return The context's logger. + */ + public Logger getLogger() { + return Context.getCurrentLogger(); + } + + /** + * Indicates if client side authentication is supported. + * + * @return True if client side authentication is supported. + */ + public boolean isClientSide() { + return this.clientSide; + } + + /** + * Indicates if server side authentication is supported. + * + * @return True if server side authentication is supported. + */ + public boolean isServerSide() { + return this.serverSide; + } + + /** + * Parses an authenticate header into a challenge request. The header is {@link + * HeaderConstants#HEADER_WWW_AUTHENTICATE}. + * + * @param challenge The challenge request to update. + * @param response The parent response. + * @param httpHeaders The current response HTTP headers. + */ + public void parseRequest( + ChallengeRequest challenge, Response response, Series
httpHeaders) {} + + /** + * Parses an authorization header into a challenge response. The header is {@link + * HeaderConstants#HEADER_AUTHORIZATION}. + * + * @param challenge The challenge response to update. + * @param request The parent request. + * @param httpHeaders The current request HTTP headers. + */ + public void parseResponse( + ChallengeResponse challenge, Request request, Series
httpHeaders) {} + + /** + * Sets the supported challenge scheme. + * + * @param challengeScheme The supported challenge scheme. + */ + public void setChallengeScheme(ChallengeScheme challengeScheme) { + this.challengeScheme = challengeScheme; + } + + /** + * Indicates if client side authentication is supported. + * + * @param clientSide True if client side authentication is supported. + */ + public void setClientSide(boolean clientSide) { + this.clientSide = clientSide; + } + + /** + * Indicates if server side authentication is supported. + * + * @param serverSide True if server side authentication is supported. + */ + public void setServerSide(boolean serverSide) { + this.serverSide = serverSide; + } + + /** + * Optionally updates the request with a challenge response before sending it. This is sometimes + * useful for authentication schemes that aren't based on the Authorization header but instead + * on URI query parameters or other headers. By default, it returns the resource URI reference + * unchanged. + * + * @param resourceRef The resource URI reference to update. + * @param challengeResponse The challenge response provided. + * @param request The request to update. + * @return The original URI reference if unchanged or a new one if updated. + */ + public Reference updateReference( + Reference resourceRef, ChallengeResponse challengeResponse, Request request) { + return resourceRef; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/security/AuthenticatorUtils.java b/org.restlet/src/main/java/org/restlet/engine/security/AuthenticatorUtils.java index df08c74e88..ab7db1abe7 100644 --- a/org.restlet/src/main/java/org/restlet/engine/security/AuthenticatorUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/security/AuthenticatorUtils.java @@ -1,18 +1,27 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.security; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; -import org.restlet.data.*; +import org.restlet.data.AuthenticationInfo; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.Header; +import org.restlet.data.Parameter; +import org.restlet.data.Reference; import org.restlet.engine.Engine; import org.restlet.engine.header.ChallengeRequestReader; import org.restlet.engine.header.ChallengeWriter; @@ -20,383 +29,392 @@ import org.restlet.engine.header.HeaderReader; import org.restlet.util.Series; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; - /** * Authentication utilities. - * + * * @author Jerome Louvel * @author Ray Waldin (ray@waldin.net) */ public class AuthenticatorUtils { - /** - * Indicates if any of the objects is null. - * - * @param objects The objects to test. - * @return True if any of the objects is null. - */ - public static boolean anyNull(Object... objects) { - for (final Object o : objects) { - if (o == null) { - return true; - } - } - return false; - } - - /** - * Formats authentication information as an HTTP header value. The header is - * {@link HeaderConstants#HEADER_AUTHENTICATION_INFO}. - * - * @param info The authentication information to format. - * @return The {@link HeaderConstants#HEADER_AUTHENTICATION_INFO} header value. - */ - public static String formatAuthenticationInfo(AuthenticationInfo info) { - ChallengeWriter cw = new ChallengeWriter(); - boolean firstParameter = true; - - if (info != null) { - if (info.getNextServerNonce() != null && !info.getNextServerNonce().isEmpty()) { - cw.setFirstChallengeParameter(firstParameter); - cw.appendQuotedChallengeParameter("nextnonce", info.getNextServerNonce()); - firstParameter = false; - } - - if (info.getQuality() != null && !info.getQuality().isEmpty()) { - cw.setFirstChallengeParameter(firstParameter); - cw.appendChallengeParameter("qop", info.getQuality()); - firstParameter = false; - - if (info.getNonceCount() > 0) { - cw.appendChallengeParameter("nc", formatNonceCount(info.getNonceCount())); - } - } - - if (info.getResponseDigest() != null && !info.getResponseDigest().isEmpty()) { - cw.setFirstChallengeParameter(firstParameter); - cw.appendQuotedChallengeParameter("rspauth", info.getResponseDigest()); - firstParameter = false; - } - - if (info.getClientNonce() != null && !info.getClientNonce().isEmpty()) { - cw.setFirstChallengeParameter(firstParameter); - cw.appendChallengeParameter("cnonce", info.getClientNonce()); - firstParameter = false; - } - } - - return cw.toString(); - } - - /** - * Formats a given nonce count as an HTTP header value. The header is - * {@link HeaderConstants#HEADER_AUTHENTICATION_INFO}. - * - * @param nonceCount The given nonce count. - * @return The formatted value of the given nonce count. - */ - public static String formatNonceCount(int nonceCount) { - StringBuilder result = new StringBuilder(Integer.toHexString(nonceCount)); - while (result.length() < 8) { - result.insert(0, '0'); - } - - return result.toString(); - } - - /** - * Formats a challenge request as an HTTP header value. The header is - * {@link HeaderConstants#HEADER_WWW_AUTHENTICATE} . The default implementation - * relies on - * {@link AuthenticatorHelper#formatRequest(ChallengeWriter, ChallengeRequest, Response, Series)} - * to append all parameters from {@link ChallengeRequest#getParameters()}. - * - * @param challenge The challenge request to format. - * @param response The parent response. - * @param httpHeaders The current response HTTP headers. - * @return The {@link HeaderConstants#HEADER_WWW_AUTHENTICATE} header value. - */ - public static String formatRequest(ChallengeRequest challenge, Response response, Series

httpHeaders) { - String result = null; - - if (challenge == null) { - Context.getCurrentLogger().warning("No challenge response to format."); - } else if (challenge.getScheme() == null) { - Context.getCurrentLogger().warning("A challenge response must have a scheme defined."); - } else if (challenge.getScheme().getTechnicalName() == null) { - Context.getCurrentLogger().warning("A challenge scheme must have a technical name defined."); - } else { - ChallengeWriter cw = new ChallengeWriter(); - cw.append(challenge.getScheme().getTechnicalName()).appendSpace(); - int cwInitialLength = cw.getBuffer().length(); - - if (challenge.getRawValue() != null) { - cw.append(challenge.getRawValue()); - } else { - AuthenticatorHelper helper = Engine.getInstance().findHelper(challenge.getScheme(), false, true); - - if (helper != null) { - try { - helper.formatRequest(cw, challenge, response, httpHeaders); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, - "Unable to format the challenge request: " + challenge, e); - } - } else { - result = "?"; - Context.getCurrentLogger().warning( - "Challenge scheme " + challenge.getScheme() + " not supported by the Restlet engine."); - } - } - - result = (cw.getBuffer().length() > cwInitialLength) ? cw.toString() : null; - } - - return result; - } - - /** - * Formats a challenge response as an HTTP header value. The header is - * {@link HeaderConstants#HEADER_AUTHORIZATION}. The default implementation - * relies on - * {@link AuthenticatorHelper#formatResponse(ChallengeWriter, ChallengeResponse, Request, Series)} - * unless some custom credentials are provided via - * - * @param challenge The challenge response to format. - * @param request The parent request. - * @param httpHeaders The current request HTTP headers. - * @return The {@link HeaderConstants#HEADER_AUTHORIZATION} header value. - * @link ChallengeResponse#getCredentials()}. - */ - public static String formatResponse(ChallengeResponse challenge, Request request, Series
httpHeaders) { - String result = null; - - if (challenge == null) { - Context.getCurrentLogger().warning("No challenge response to format."); - } else if (challenge.getScheme() == null) { - Context.getCurrentLogger().warning("A challenge response must have a scheme defined."); - } else if (challenge.getScheme().getTechnicalName() == null) { - Context.getCurrentLogger().warning("A challenge scheme must have a technical name defined."); - } else { - ChallengeWriter cw = new ChallengeWriter(); - cw.append(challenge.getScheme().getTechnicalName()).appendSpace(); - int cwInitialLength = cw.getBuffer().length(); - - if (challenge.getRawValue() != null) { - cw.append(challenge.getRawValue()); - } else { - AuthenticatorHelper helper = Engine.getInstance().findHelper(challenge.getScheme(), true, false); - - if (helper != null) { - try { - helper.formatResponse(cw, challenge, request, httpHeaders); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, - "Unable to format the challenge response: " + challenge, e); - } - } else { - Context.getCurrentLogger().warning( - "Challenge scheme " + challenge.getScheme() + " not supported by the Restlet engine."); - } - } - - result = (cw.getBuffer().length() > cwInitialLength) ? cw.toString() : null; - } - - return result; - } - - /** - * Parses the "Authentication-Info" header. - * - * @param header The header value to parse. - * @return The equivalent {@link AuthenticationInfo} instance. - */ - public static AuthenticationInfo parseAuthenticationInfo(String header) { - AuthenticationInfo result = null; - HeaderReader hr = new HeaderReader(header); - - try { - String nextNonce = null; - String qop = null; - String responseAuth = null; - String cnonce = null; - int nonceCount = 0; - Parameter param = hr.readParameter(); - - while (param != null) { - try { - if ("nextnonce".equals(param.getName())) { - nextNonce = param.getValue(); - } else if ("qop".equals(param.getName())) { - qop = param.getValue(); - } else if ("rspauth".equals(param.getName())) { - responseAuth = param.getValue(); - } else if ("cnonce".equals(param.getName())) { - cnonce = param.getValue(); - } else if ("nc".equals(param.getName())) { - nonceCount = Integer.parseInt(param.getValue(), 16); - } - - if (hr.skipValueSeparator()) { - param = hr.readParameter(); - } else { - param = null; - } - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, - "Unable to parse the authentication info header parameter", e); - } - } - - result = new AuthenticationInfo(nextNonce, nonceCount, cnonce, qop, responseAuth); - } catch (IOException e) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to parse the authentication info header: " + header, - e); - } - - return result; - } - - /** - * Parses an WWW-Authenticate header into a list of challenge request. The header is - * {@link HeaderConstants#HEADER_WWW_AUTHENTICATE}. - * - * @param header The HTTP header value to parse. - * @param httpHeaders The current response HTTP headers. - * @return The list of parsed challenge request. - */ - public static List parseRequest(Response response, String header, Series
httpHeaders) { - List result = new ArrayList(); - - if (header != null) { - result = new ChallengeRequestReader(header).readValues(); - for (ChallengeRequest cr : result) { - // Give a chance to the authenticator helper to do further - // parsing - AuthenticatorHelper helper = Engine.getInstance().findHelper(cr.getScheme(), true, false); - - if (helper != null) { - helper.parseRequest(cr, response, httpHeaders); - } else { - Context.getCurrentLogger() - .warning("Couldn't find any helper support the " + cr.getScheme() + " challenge scheme."); - } - } - } - - return result; - } - - /** - * Parses an authorization header into a challenge response. The header is - * {@link HeaderConstants#HEADER_AUTHORIZATION}. - * - * @param request The parent request. - * @param header The authorization header. - * @param httpHeaders The current request HTTP headers. - * @return The parsed challenge response. - */ - public static ChallengeResponse parseResponse(Request request, String header, Series
httpHeaders) { - ChallengeResponse result = null; - - if (header != null) { - int space = header.indexOf(' '); - - if (space != -1) { - String scheme = header.substring(0, space); - String rawValue = header.substring(space + 1); - - result = new ChallengeResponse(new ChallengeScheme("HTTP_" + scheme, scheme)); - result.setRawValue(rawValue); - } - } - - if (result != null) { - // Give a chance to the authenticator helper to do further parsing - AuthenticatorHelper helper = Engine.getInstance().findHelper(result.getScheme(), true, false); - - if (helper != null) { - helper.parseResponse(result, request, httpHeaders); - } else { - Context.getCurrentLogger() - .warning("Couldn't find any helper support the " + result.getScheme() + " challenge scheme."); - } - } - - return result; - - } - - /** - * Updates a {@link ChallengeResponse} object according to given request and - * response. - * - * @param challengeResponse The challengeResponse to update. - * @param request The request. - * @param response The response. - */ - public static void update(ChallengeResponse challengeResponse, Request request, Response response) { - ChallengeRequest challengeRequest = null; - - for (ChallengeRequest c : response.getChallengeRequests()) { - if (challengeResponse.getScheme().equals(c.getScheme())) { - challengeRequest = c; - break; - } - } - - String realm = null; - String nonce = null; - - if (challengeRequest != null) { - realm = challengeRequest.getRealm(); - nonce = challengeRequest.getServerNonce(); - challengeResponse.setOpaque(challengeRequest.getOpaque()); - } - - challengeResponse.setRealm(realm); - challengeResponse.setServerNonce(nonce); - - challengeResponse.setDigestRef(new Reference(request.getResourceRef().getPath())); - } - - /** - * Optionally updates the request with a challenge response before sending it. - * This is sometimes useful for authentication schemes that aren't based on the - * Authorization header but instead on URI query parameters or other headers. By - * default, it returns the resource URI reference unchanged. - * - * @param resourceRef The resource URI reference to update. - * @param challengeResponse The challenge response provided. - * @param request The request to update. - * @return The original URI reference if unchanged or a new one if updated. - */ - public static Reference updateReference(Reference resourceRef, ChallengeResponse challengeResponse, - Request request) { - if (challengeResponse != null && challengeResponse.getRawValue() == null) { - AuthenticatorHelper helper = Engine.getInstance().findHelper(challengeResponse.getScheme(), true, false); - - if (helper != null) { - resourceRef = helper.updateReference(resourceRef, challengeResponse, request); - } else { - Context.getCurrentLogger().warning( - "Challenge scheme " + challengeResponse.getScheme() + " not supported by the Restlet engine."); - } - } - - return resourceRef; - } - - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e., it isn't instantiable and extensible. - */ - private AuthenticatorUtils() { - } - + /** + * Indicates if any of the objects is null. + * + * @param objects The objects to test. + * @return True if any of the objects is null. + */ + public static boolean anyNull(Object... objects) { + for (final Object o : objects) { + if (o == null) { + return true; + } + } + return false; + } + + /** + * Formats authentication information as an HTTP header value. The header is {@link + * HeaderConstants#HEADER_AUTHENTICATION_INFO}. + * + * @param info The authentication information to format. + * @return The {@link HeaderConstants#HEADER_AUTHENTICATION_INFO} header value. + */ + public static String formatAuthenticationInfo(AuthenticationInfo info) { + ChallengeWriter cw = new ChallengeWriter(); + boolean firstParameter = true; + + if (info == null) { + return cw.toString(); + } + + if (info.getNextServerNonce() != null && !info.getNextServerNonce().isEmpty()) { + cw.setFirstChallengeParameter(firstParameter); + cw.appendQuotedChallengeParameter("nextnonce", info.getNextServerNonce()); + firstParameter = false; + } + + if (info.getQuality() != null && !info.getQuality().isEmpty()) { + cw.setFirstChallengeParameter(firstParameter); + cw.appendChallengeParameter("qop", info.getQuality()); + firstParameter = false; + + if (info.getNonceCount() > 0) { + cw.appendChallengeParameter("nc", formatNonceCount(info.getNonceCount())); + } + } + + if (info.getResponseDigest() != null && !info.getResponseDigest().isEmpty()) { + cw.setFirstChallengeParameter(firstParameter); + cw.appendQuotedChallengeParameter("rspauth", info.getResponseDigest()); + firstParameter = false; + } + + if (info.getClientNonce() != null && !info.getClientNonce().isEmpty()) { + cw.setFirstChallengeParameter(firstParameter); + cw.appendChallengeParameter("cnonce", info.getClientNonce()); + } + + return cw.toString(); + } + + /** + * Formats a given nonce count as an HTTP header value. The header is {@link + * HeaderConstants#HEADER_AUTHENTICATION_INFO}. + * + * @param nonceCount The given nonce count. + * @return The formatted value of the given nonce count. + */ + public static String formatNonceCount(int nonceCount) { + StringBuilder result = new StringBuilder(Integer.toHexString(nonceCount)); + while (result.length() < 8) { + result.insert(0, '0'); + } + + return result.toString(); + } + + /** + * Formats a challenge request as an HTTP header value. The header is {@link + * HeaderConstants#HEADER_WWW_AUTHENTICATE} . The default implementation relies on {@link + * AuthenticatorHelper#formatRequest(ChallengeWriter, ChallengeRequest, Response, Series)} to + * append all parameters from {@link ChallengeRequest#getParameters()}. + * + * @param challenge The challenge request to format. + * @param response The parent response. + * @param httpHeaders The current response HTTP headers. + * @return The {@link HeaderConstants#HEADER_WWW_AUTHENTICATE} header value. + */ + public static String formatRequest( + ChallengeRequest challenge, Response response, Series
httpHeaders) { + String result = null; + + if (challenge == null) { + Context.getCurrentLogger().warning("No challenge response to format."); + } else if (challenge.getScheme() == null) { + Context.getCurrentLogger().warning("A challenge response must have a scheme defined."); + } else if (challenge.getScheme().getTechnicalName() == null) { + Context.getCurrentLogger() + .warning("A challenge scheme must have a technical name defined."); + } else { + ChallengeWriter cw = new ChallengeWriter(); + cw.append(challenge.getScheme().getTechnicalName()).appendSpace(); + int cwInitialLength = cw.getBuffer().length(); + + if (challenge.getRawValue() != null) { + cw.append(challenge.getRawValue()); + } else { + AuthenticatorHelper helper = findHelper(challenge.getScheme(), false, true); + + if (helper != null) { + helper.formatRequest(cw, challenge, response, httpHeaders); + } + } + + result = (cw.getBuffer().length() > cwInitialLength) ? cw.toString() : null; + } + + return result; + } + + /** + * Formats a challenge response as an HTTP header value. The header is {@link + * HeaderConstants#HEADER_AUTHORIZATION}. The default implementation relies on {@link + * AuthenticatorHelper#formatResponse(ChallengeWriter, ChallengeResponse, Request, Series)} + * unless some custom credentials are provided via + * + * @param challenge The challenge response to format. + * @param request The parent request. + * @param httpHeaders The current request HTTP headers. + * @return The {@link HeaderConstants#HEADER_AUTHORIZATION} header value. + * @link ChallengeResponse#getCredentials()}. + */ + public static String formatResponse( + ChallengeResponse challenge, Request request, Series
httpHeaders) { + String result = null; + + if (challenge == null) { + Context.getCurrentLogger().warning("No challenge response to format."); + } else if (challenge.getScheme() == null) { + Context.getCurrentLogger().warning("A challenge response must have a scheme defined."); + } else if (challenge.getScheme().getTechnicalName() == null) { + Context.getCurrentLogger() + .warning("A challenge scheme must have a technical name defined."); + } else { + ChallengeWriter cw = new ChallengeWriter(); + cw.append(challenge.getScheme().getTechnicalName()).appendSpace(); + int cwInitialLength = cw.getBuffer().length(); + + if (challenge.getRawValue() != null) { + cw.append(challenge.getRawValue()); + } else { + AuthenticatorHelper helper = findHelper(challenge.getScheme(), true, false); + + if (helper != null) { + helper.formatResponse(cw, challenge, request, httpHeaders); + } + } + + result = (cw.getBuffer().length() > cwInitialLength) ? cw.toString() : null; + } + + return result; + } + + /** + * Parses the "Authentication-Info" header. + * + * @param header The header value to parse. + * @return The equivalent {@link AuthenticationInfo} instance. + */ + public static AuthenticationInfo parseAuthenticationInfo(final String header) { + HeaderReader hr = new HeaderReader<>(header); + + Parameter param; + try { + param = hr.readParameter(); + } catch (IOException e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + e, + () -> "Unable to parse the authentication info header: " + header); + return null; + } + + String nextNonce = null; + String qop = null; + String responseAuth = null; + String cnonce = null; + String nonceCountAsString = null; + + try { + while (param != null) { + if ("nextnonce".equals(param.getName())) { + nextNonce = param.getValue(); + } else if ("qop".equals(param.getName())) { + qop = param.getValue(); + } else if ("rspauth".equals(param.getName())) { + responseAuth = param.getValue(); + } else if ("cnonce".equals(param.getName())) { + cnonce = param.getValue(); + } else if ("nc".equals(param.getName())) { + nonceCountAsString = param.getValue(); + } + + param = + hr.skipValueSeparator() + ? hr.readParameter() // next parameter + : null; // end of header + } + } catch (IOException e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Unable to parse the authentication info header parameter", + e); + } + + int nonceCount = getNonceCount(nonceCountAsString); + + return new AuthenticationInfo(nextNonce, nonceCount, cnonce, qop, responseAuth); + } + + private static int getNonceCount(final String nonceCountAsString) { + if (nonceCountAsString == null) { + return 0; + } + try { + return Integer.parseInt(nonceCountAsString, 16); + } catch (NumberFormatException e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + e, + () -> "Unable to parse the nonce count value: " + nonceCountAsString); + } + return 0; + } + + /** + * Parses an WWW-Authenticate header into a list of challenge request. The header is {@link + * HeaderConstants#HEADER_WWW_AUTHENTICATE}. + * + * @param header The HTTP header value to parse. + * @param httpHeaders The current response HTTP headers. + * @return The list of parsed challenge request. + */ + public static List parseRequest( + Response response, String header, Series
httpHeaders) { + List result = new ArrayList<>(); + + if (header != null) { + result = new ChallengeRequestReader(header).readValues(); + for (ChallengeRequest cr : result) { + // Give a chance to the authenticator helper to do further parsing + AuthenticatorHelper helper = findHelper(cr.getScheme(), true, false); + + if (helper != null) { + helper.parseRequest(cr, response, httpHeaders); + } + } + } + + return result; + } + + /** + * Parses an authorization header into a challenge response. The header is {@link + * HeaderConstants#HEADER_AUTHORIZATION}. + * + * @param request The parent request. + * @param header The authorization header. + * @param httpHeaders The current request HTTP headers. + * @return The parsed challenge response. + */ + public static ChallengeResponse parseResponse( + Request request, String header, Series
httpHeaders) { + ChallengeResponse result = null; + + if (header != null) { + int space = header.indexOf(' '); + + if (space != -1) { + String scheme = header.substring(0, space); + String rawValue = header.substring(space + 1); + + result = new ChallengeResponse(new ChallengeScheme("HTTP_" + scheme, scheme)); + result.setRawValue(rawValue); + } + } + + if (result != null) { + // Give a chance to the authenticator helper to do further parsing + AuthenticatorHelper helper = findHelper(result.getScheme(), true, false); + + if (helper != null) { + helper.parseResponse(result, request, httpHeaders); + } + } + + return result; + } + + /** + * Updates a {@link ChallengeResponse} object according to given request and response. + * + * @param challengeResponse The challengeResponse to update. + * @param request The request. + * @param response The response. + */ + public static void update( + ChallengeResponse challengeResponse, Request request, Response response) { + ChallengeRequest challengeRequest = null; + + for (ChallengeRequest c : response.getChallengeRequests()) { + if (challengeResponse.getScheme().equals(c.getScheme())) { + challengeRequest = c; + break; + } + } + + String realm = null; + String nonce = null; + + if (challengeRequest != null) { + realm = challengeRequest.getRealm(); + nonce = challengeRequest.getServerNonce(); + challengeResponse.setOpaque(challengeRequest.getOpaque()); + } + + challengeResponse.setRealm(realm); + challengeResponse.setServerNonce(nonce); + + challengeResponse.setDigestRef(new Reference(request.getResourceRef().getPath())); + } + + /** + * Optionally updates the request with a challenge response before sending it. This is sometimes + * useful for authentication schemes that aren't based on the Authorization header but instead + * on URI query parameters or other headers. By default, it returns the resource URI reference + * unchanged. + * + * @param resourceRef The resource URI reference to update. + * @param challengeResponse The challenge response provided. + * @param request The request to update. + * @return The original URI reference if unchanged or a new one if updated. + */ + public static Reference updateReference( + Reference resourceRef, ChallengeResponse challengeResponse, Request request) { + if (challengeResponse != null && challengeResponse.getRawValue() == null) { + AuthenticatorHelper helper = findHelper(challengeResponse.getScheme(), true, false); + + if (helper != null) { + resourceRef = helper.updateReference(resourceRef, challengeResponse, request); + } + } + + return resourceRef; + } + + private static AuthenticatorHelper findHelper( + final ChallengeScheme challengeScheme, + final boolean clientSide, + final boolean serverSide) { + final AuthenticatorHelper helper = + Engine.getInstance().findHelper(challengeScheme, clientSide, serverSide); + if (helper == null) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Challenge scheme {0} not supported by the Restlet engine.", + challengeScheme); + } + return helper; + } + + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private AuthenticatorUtils() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/security/HttpBasicHelper.java b/org.restlet/src/main/java/org/restlet/engine/security/HttpBasicHelper.java index 43ce5cfa94..1081514606 100644 --- a/org.restlet/src/main/java/org/restlet/engine/security/HttpBasicHelper.java +++ b/org.restlet/src/main/java/org/restlet/engine/security/HttpBasicHelper.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.security; import java.io.CharArrayWriter; @@ -14,7 +13,6 @@ import java.io.UnsupportedEncodingException; import java.util.Base64; import java.util.logging.Level; - import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -30,147 +28,172 @@ /** * Implements the HTTP BASIC authentication. - * + * * @author Jerome Louvel */ public class HttpBasicHelper extends AuthenticatorHelper { - /** - * Constructor. - */ - public HttpBasicHelper() { - super(ChallengeScheme.HTTP_BASIC, true, true); - } - - @Override - public void formatRequest(ChallengeWriter cw, ChallengeRequest challenge, Response response, - Series

httpHeaders) throws IOException { - String realm = challenge.getRealm(); - String charset = challenge.getParameters().getFirstValue("charset"); - - if (realm != null) { - cw.appendQuotedChallengeParameter("realm", realm); - } else { - getLogger() - .warning("The realm directive is required for all authentication schemes that issue a challenge."); - } - - if (charset != null) { - if ("UTF-8".equalsIgnoreCase(charset)) { - cw.appendQuotedChallengeParameter("charset", "UTF-8"); - } else { - getLogger().warning("The \"charset\" parameter must be \"UTF-8\" per RFC 7617."); - } - } - } - - @Override - public void formatResponse(ChallengeWriter cw, ChallengeResponse challenge, Request request, - Series
httpHeaders) { - try { - if (challenge == null) { - throw new RuntimeException("No challenge provided, unable to encode credentials"); - } else { - String charset = challenge.getParameters().getFirstValue("charset"); - - if (charset != null) { - if ("UTF-8".equalsIgnoreCase(charset)) { - charset = "UTF-8"; - } else { - getLogger().warning( - "The \"charset\" parameter must be \"UTF-8\" per RFC 7617. Using \"ISO-8859-1\" instead."); - charset = "ISO-8859-1"; - } - } else { - charset = "ISO-8859-1"; - } - - CharArrayWriter credentials = new CharArrayWriter(); - credentials.write(challenge.getIdentifier()); - credentials.write(":"); - credentials.write(challenge.getSecret()); - cw.append(Base64.getEncoder().encodeToString(IoUtils.toByteArray(credentials.toCharArray(), charset))); - } - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Unsupported encoding, unable to encode credentials"); - } catch (IOException e) { - throw new RuntimeException("Unexpected exception, unable to encode credentials", e); - } - } - - @Override - public void parseRequest(ChallengeRequest challenge, Response response, Series
httpHeaders) { - if (challenge.getRawValue() != null) { - HeaderReader hr = new HeaderReader(challenge.getRawValue()); - - try { - Parameter param = hr.readParameter(); - - while (param != null) { - try { - if ("realm".equals(param.getName())) { - challenge.setRealm(param.getValue()); - } else { - challenge.getParameters().add(param); - } - - if (hr.skipValueSeparator()) { - param = hr.readParameter(); - } else { - param = null; - } - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, - "Unable to parse the challenge request header parameter", e); - } - } - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to parse the challenge request header parameter", - e); - } - } - } - - @Override - public void parseResponse(ChallengeResponse challenge, Request request, Series
httpHeaders) { - if (challenge.getRawValue() == null) { - getLogger().info("Cannot decode credentials: " + challenge.getRawValue()); - return; - } - - try { - String charset = challenge.getParameters().getFirstValue("charset"); - - if (charset != null) { - if ("UTF-8".equalsIgnoreCase(charset)) { - charset = "UTF-8"; - } else { - getLogger().warning( - "The \"charset\" parameter must be \"UTF-8\" per RFC 7617. Using \"ISO-8859-1\" instead."); - charset = "ISO-8859-1"; - } - } else { - charset = "ISO-8859-1"; - } - - byte[] credentialsEncoded = Base64.getDecoder().decode(challenge.getRawValue()); - - String credentials = new String(credentialsEncoded, charset); - int separator = credentials.indexOf(':'); - - if (separator == -1) { - // Log the blocking - getLogger().info("Invalid credentials given by client with IP: " - + ((request != null) ? request.getClientInfo().getAddress() : "?")); - } else { - challenge.setIdentifier(credentials.substring(0, separator)); - challenge.setSecret(credentials.substring(separator + 1)); - } - } catch (UnsupportedEncodingException e) { - getLogger().log(Level.INFO, "Unsupported HTTP Basic encoding error", e); - } catch (IllegalArgumentException e) { - getLogger().log(Level.INFO, "Unable to decode the HTTP Basic credential", e); - } - } - + private static final String CHARSET = "charset"; + private static final String REALM = "realm"; + private static final String UTF_8 = "UTF-8"; + private static final String ISO_8859_1 = "ISO-8859-1"; + + /** Constructor. */ + public HttpBasicHelper() { + super(ChallengeScheme.HTTP_BASIC, true, true); + } + + @Override + public void formatRequest( + ChallengeWriter cw, + ChallengeRequest challenge, + Response response, + Series
httpHeaders) { + String realm = challenge.getRealm(); + String charset = challenge.getParameters().getFirstValue(CHARSET); + + if (realm != null) { + cw.appendQuotedChallengeParameter(REALM, realm); + } else { + getLogger() + .warning( + "The realm directive is required for all authentication schemes that issue a challenge."); + } + + if (charset != null) { + if (UTF_8.equalsIgnoreCase(charset)) { + cw.appendQuotedChallengeParameter(CHARSET, UTF_8); + } else { + getLogger().warning("The \"charset\" parameter must be \"UTF-8\" per RFC 7617."); + } + } + } + + @Override + public void formatResponse( + ChallengeWriter cw, + ChallengeResponse challenge, + Request request, + Series
httpHeaders) { + try { + if (challenge == null) { + throw new RuntimeException("No challenge provided, unable to encode credentials"); + } else { + String charset = getCharset(challenge); + + CharArrayWriter credentials = new CharArrayWriter(); + credentials.write(challenge.getIdentifier()); + credentials.write(":"); + credentials.write(challenge.getSecret()); + cw.append( + Base64.getEncoder() + .encodeToString( + IoUtils.toByteArray(credentials.toCharArray(), charset))); + } + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Unsupported encoding, unable to encode credentials"); + } catch (IOException e) { + throw new RuntimeException("Unexpected exception, unable to encode credentials", e); + } + } + + @Override + public void parseRequest( + ChallengeRequest challenge, Response response, Series
httpHeaders) { + if (challenge.getRawValue() == null) { + return; + } + + HeaderReader hr = new HeaderReader<>(challenge.getRawValue()); + + Parameter param = null; + try { + param = hr.readParameter(); + } catch (IOException e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Unable to parse the challenge request header parameter", + e); + } + + while (param != null) { + try { + if (REALM.equals(param.getName())) { + challenge.setRealm(param.getValue()); + } else { + challenge.getParameters().add(param); + } + + if (hr.skipValueSeparator()) { + param = hr.readParameter(); + } else { + param = null; // end of header + } + } catch (IOException e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Unable to parse the challenge request header parameter", + e); + } + } + } + + @Override + public void parseResponse( + ChallengeResponse challenge, Request request, Series
httpHeaders) { + if (challenge.getRawValue() == null) { + getLogger().info("Cannot decode credentials: " + challenge.getRawValue()); + return; + } + + try { + String charset = getCharset(challenge); + + byte[] credentialsEncoded = Base64.getDecoder().decode(challenge.getRawValue()); + + String credentials = new String(credentialsEncoded, charset); + int separator = credentials.indexOf(':'); + + if (separator == -1) { + // Log the blocking + getLogger() + .log( + Level.INFO, + "Invalid credentials given by client with IP: {0}", + request != null ? request.getClientInfo().getAddress() : "?"); + } else { + challenge.setIdentifier(credentials.substring(0, separator)); + challenge.setSecret(credentials.substring(separator + 1)); + } + } catch (UnsupportedEncodingException e) { + getLogger().log(Level.INFO, "Unsupported HTTP Basic encoding error", e); + } catch (IllegalArgumentException e) { + getLogger().log(Level.INFO, "Unable to decode the HTTP Basic credential", e); + } + } + + /** + * Returns the charset from the given ChallengeResponse. If the charset is not specified, or if + * it is not UTF-8, then ISO-8859-1 is returned as per RFC 7617. + * + * @param challenge The challenge response. + * @return The charset. + */ + private String getCharset(final ChallengeResponse challenge) { + String charset = challenge.getParameters().getFirstValue(CHARSET); + + if (charset == null) { + charset = ISO_8859_1; + } else if (UTF_8.equalsIgnoreCase(charset)) { + charset = UTF_8; + } else { + getLogger() + .warning( + "The \"charset\" parameter must be \"UTF-8\" per RFC 7617. Using \"ISO-8859-1\" instead."); + charset = ISO_8859_1; + } + return charset; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/security/RestletSslContextFactoryClient.java b/org.restlet/src/main/java/org/restlet/engine/security/RestletSslContextFactoryClient.java index 83bbba53cd..ab9e612d78 100644 --- a/org.restlet/src/main/java/org/restlet/engine/security/RestletSslContextFactoryClient.java +++ b/org.restlet/src/main/java/org/restlet/engine/security/RestletSslContextFactoryClient.java @@ -1,41 +1,36 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.security; import java.io.IOException; import java.net.InetAddress; - import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; - import org.eclipse.jetty.util.ssl.SslContextFactory; /** * Jetty SSL context factory based on a Restlet SSL context one. - * + * * @author Jerome Louvel */ public class RestletSslContextFactoryClient extends SslContextFactory.Client { /** * Constructor. - * - * @param restletSslContextFactory - * The Restlet SSL context factory to leverage. + * + * @param restletSslContextFactory The Restlet SSL context factory to leverage. * @throws Exception */ public RestletSslContextFactoryClient( - org.restlet.engine.ssl.SslContextFactory restletSslContextFactory) - throws Exception { + org.restlet.engine.ssl.SslContextFactory restletSslContextFactory) throws Exception { setSslContext(restletSslContextFactory.createSslContext()); } @@ -53,9 +48,10 @@ public SSLEngine newSSLEngine(String host, int port) { public SSLServerSocket newSslServerSocket(String host, int port, int backlog) throws IOException { SSLServerSocketFactory factory = getSslContext().getServerSocketFactory(); - return (SSLServerSocket) ((host == null) - ? factory.createServerSocket(port, backlog) : - factory.createServerSocket(port, backlog, InetAddress.getByName(host))); + return (SSLServerSocket) + ((host == null) + ? factory.createServerSocket(port, backlog) + : factory.createServerSocket(port, backlog, InetAddress.getByName(host))); } @Override diff --git a/org.restlet/src/main/java/org/restlet/engine/security/RestletSslContextFactoryServer.java b/org.restlet/src/main/java/org/restlet/engine/security/RestletSslContextFactoryServer.java index ac4155a644..1b835fbe32 100644 --- a/org.restlet/src/main/java/org/restlet/engine/security/RestletSslContextFactoryServer.java +++ b/org.restlet/src/main/java/org/restlet/engine/security/RestletSslContextFactoryServer.java @@ -1,42 +1,37 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.security; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; - import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; - import org.eclipse.jetty.util.ssl.SslContextFactory; /** * Jetty SSL context factory based on a Restlet SSL context one. - * + * * @author Jerome Louvel */ public class RestletSslContextFactoryServer extends SslContextFactory.Server { /** * Constructor. - * - * @param restletSslContextFactory - * The Restlet SSL context factory to leverage. + * + * @param restletSslContextFactory The Restlet SSL context factory to leverage. * @throws Exception */ public RestletSslContextFactoryServer( - org.restlet.engine.ssl.SslContextFactory restletSslContextFactory) - throws Exception { + org.restlet.engine.ssl.SslContextFactory restletSslContextFactory) throws Exception { setSslContext(restletSslContextFactory.createSslContext()); } @@ -54,9 +49,10 @@ public SSLEngine newSSLEngine(String host, int port) { public SSLServerSocket newSslServerSocket(String host, int port, int backlog) throws IOException { final SSLServerSocketFactory factory = getSslContext().getServerSocketFactory(); - final ServerSocket serverSocket = (host == null) - ? factory.createServerSocket(port, backlog) - : factory.createServerSocket(port, backlog, InetAddress.getByName(host)); + final ServerSocket serverSocket = + (host == null) + ? factory.createServerSocket(port, backlog) + : factory.createServerSocket(port, backlog, InetAddress.getByName(host)); return (SSLServerSocket) serverSocket; } diff --git a/org.restlet/src/main/java/org/restlet/engine/security/RoleMapping.java b/org.restlet/src/main/java/org/restlet/engine/security/RoleMapping.java index d7e9d28c03..112dab7884 100644 --- a/org.restlet/src/main/java/org/restlet/engine/security/RoleMapping.java +++ b/org.restlet/src/main/java/org/restlet/engine/security/RoleMapping.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.security; import org.restlet.security.Group; @@ -15,53 +14,50 @@ /** * Mapping from an organization or a user or a group to a role. - * + * * @author Jerome Louvel */ public class RoleMapping { - /** - * The source of the mapping. It must be an instance of one of these classes: - * {@link User} or {@link Group}. - */ - private volatile Object source; - - /** The target role of the mapping. */ - private volatile Role target; - - /** - * Default constructor. - */ - public RoleMapping() { - this(null, null); - } - - /** - * Constructor. - * - * @param source - * @param target - */ - public RoleMapping(Object source, Role target) { - super(); - this.source = source; - this.target = target; - } - - public Object getSource() { - return source; - } - - public Role getTarget() { - return target; - } - - public void setSource(Object source) { - this.source = source; - } - - public void setTarget(Role target) { - this.target = target; - } - + /** + * The source of the mapping. It must be an instance of one of these classes: {@link User} or + * {@link Group}. + */ + private volatile Object source; + + /** The target role of the mapping. */ + private volatile Role target; + + /** Default constructor. */ + public RoleMapping() { + this(null, null); + } + + /** + * Constructor. + * + * @param source + * @param target + */ + public RoleMapping(Object source, Role target) { + super(); + this.source = source; + this.target = target; + } + + public Object getSource() { + return source; + } + + public Role getTarget() { + return target; + } + + public void setSource(Object source) { + this.source = source; + } + + public void setTarget(Role target) { + this.target = target; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/security/package-info.java b/org.restlet/src/main/java/org/restlet/engine/security/package-info.java new file mode 100644 index 0000000000..20ea8df573 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/security/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports security. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.security; diff --git a/org.restlet/src/main/java/org/restlet/engine/security/package.html b/org.restlet/src/main/java/org/restlet/engine/security/package.html deleted file mode 100644 index edf452d0f3..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/security/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Supports security. -

-@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/ssl/DefaultSslContext.java b/org.restlet/src/main/java/org/restlet/engine/ssl/DefaultSslContext.java index 7163606422..ed2d3ca41c 100644 --- a/org.restlet/src/main/java/org/restlet/engine/ssl/DefaultSslContext.java +++ b/org.restlet/src/main/java/org/restlet/engine/ssl/DefaultSslContext.java @@ -1,46 +1,45 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.ssl; import javax.net.ssl.SSLContext; /** * Default SSL context that delegates calls to {@link WrapperSslContextSpi} - * + * * @author Jerome Louvel */ public class DefaultSslContext extends SSLContext { - /** - * Creates a SSL context SPI capable or setting additional properties on the - * created SSL engines and socket factories. - * - * @param contextFactory The parent SSL context factory. - * @param wrappedContext The wrapped SSL context. - * @return The created SSL context SPI. - */ - private static WrapperSslContextSpi createContextSpi(DefaultSslContextFactory contextFactory, - SSLContext wrappedContext) { - return new WrapperSslContextSpi(contextFactory, wrappedContext); - } - - /** - * Constructor. - * - * @param contextFactory The parent SSL context factory. - * @param wrappedContext The wrapped SSL context. - * - */ - public DefaultSslContext(DefaultSslContextFactory contextFactory, SSLContext wrappedContext) { - super(createContextSpi(contextFactory, wrappedContext), wrappedContext.getProvider(), - wrappedContext.getProtocol()); - } + /** + * Creates a SSL context SPI capable or setting additional properties on the created SSL engines + * and socket factories. + * + * @param contextFactory The parent SSL context factory. + * @param wrappedContext The wrapped SSL context. + * @return The created SSL context SPI. + */ + private static WrapperSslContextSpi createContextSpi( + DefaultSslContextFactory contextFactory, SSLContext wrappedContext) { + return new WrapperSslContextSpi(contextFactory, wrappedContext); + } + /** + * Constructor. + * + * @param contextFactory The parent SSL context factory. + * @param wrappedContext The wrapped SSL context. + */ + public DefaultSslContext(DefaultSslContextFactory contextFactory, SSLContext wrappedContext) { + super( + createContextSpi(contextFactory, wrappedContext), + wrappedContext.getProvider(), + wrappedContext.getProtocol()); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/ssl/DefaultSslContextFactory.java b/org.restlet/src/main/java/org/restlet/engine/ssl/DefaultSslContextFactory.java index ba760acbc3..02f1b6fff6 100644 --- a/org.restlet/src/main/java/org/restlet/engine/ssl/DefaultSslContextFactory.java +++ b/org.restlet/src/main/java/org/restlet/engine/ssl/DefaultSslContextFactory.java @@ -1,33 +1,32 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.ssl; -import org.restlet.data.Parameter; -import org.restlet.util.Series; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSocketFactory; import java.io.FileInputStream; -import java.security.*; +import java.security.KeyStore; +import java.security.SecureRandom; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSocketFactory; +import org.restlet.data.Parameter; +import org.restlet.util.Series; /** - * This {@link SslContextFactory} makes it possible to configure most basic - * options when building an SSLContext. See the {@link #init(Series)} method for - * the list of parameters supported by this factory when configuring your HTTP - * client or server connector. Here is the list of SSL related parameters that - * are also supported: + * This {@link SslContextFactory} makes it possible to configure most basic options when building an + * SSLContext. See the {@link #init(Series)} method for the list of parameters supported by this + * factory when configuring your HTTP client or server connector. Here is the list of SSL related + * parameters that are also supported: + * * * * @@ -146,395 +145,433 @@ * into account if the "needClientAuthentication" parameter is 'false'. * *
list of supported parameters
- *

- * In short, two instances of KeyStore are used when configuring an SSLContext: - * the key store (which contains the public and private keys and certificates to - * be used locally) and the trust store (which generally holds the CA - * certificates to be trusted when connecting to a remote host). Both keystore - * and trust store are KeyStores. When not explicitly set using the setters of - * this class, the values will default to the default system properties, - * following the behavior described in the JSSE reference guide. - *

- *

- * There is more information in the JSSE Reference Guide. - *

- * + * + *

In short, two instances of KeyStore are used when configuring an SSLContext: the key store + * (which contains the public and private keys and certificates to be used locally) and the trust + * store (which generally holds the CA certificates to be trusted when connecting to a remote host). + * Both keystore and trust store are KeyStores. When not explicitly set using the setters of this + * class, the values will default to the default system properties, following the behavior described + * in the JSSE reference guide. + * + *

There is more information in the JSSE + * Reference Guide. + * * @author Bruno Harbulot * @see javax.net.ssl.SSLContext * @see java.security.KeyStore * @see JSSE - * Reference - Standard names + * "https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#AppA">JSSE + * Reference - Standard names */ public class DefaultSslContextFactory extends SslContextFactory { - /** The whitespace-separated list of disabled cipher suites. */ - private volatile String[] disabledCipherSuites = null; - - /** The whitespace-separated list of disabled SSL protocols. */ - private volatile String[] disabledProtocols = null; - - /** The whitespace-separated list of enabled cipher suites. */ - private volatile String[] enabledCipherSuites = null; - - /** The whitespace-separated list of enabled SSL protocols. */ - private volatile String[] enabledProtocols = null; - - /** The name of the KeyManager algorithm. */ - private volatile String keyManagerAlgorithm = System.getProperty("ssl.KeyManagerFactory.algorithm", "SunX509"); - - /** The password for the key in the keystore (as a String). */ - private volatile char[] keyStoreKeyPassword = (System.getProperty("javax.net.ssl.keyPassword", - System.getProperty("javax.net.ssl.keyStorePassword")) != null) ? System - .getProperty("javax.net.ssl.keyPassword", System.getProperty("javax.net.ssl.keyStorePassword")) - .toCharArray() : null; - - /** The password for the keystore (as a String). */ - private volatile char[] keyStorePassword = (System.getProperty("javax.net.ssl.keyStorePassword") != null) - ? System.getProperty("javax.net.ssl.keyStorePassword").toCharArray() - : null; - - /** The path to the KeyStore file. */ - private volatile String keyStorePath = System - .getProperty("javax.net.ssl.keyStore", - (System.getProperty("user.home") != null) ? ((System.getProperty("user.home").endsWith("/")) - ? System.getProperty("user.home") + ".keystore" - : System.getProperty("user.home") + "/.keystore") : null); - - /** The name of the keystore provider. */ - private volatile String keyStoreProvider = System.getProperty("javax.net.ssl.keyStoreProvider"); - - /** The keyStore type of the keystore. */ - private volatile String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", "JKS"); - - /** Indicates if we require client certificate authentication. */ - private volatile boolean needClientAuthentication = false; - - /** The standard name of the protocol to use when creating the SSLContext. */ - private volatile String protocol = "TLS"; - - /** The name of the SecureRandom algorithm. */ - private volatile String secureRandomAlgorithm = null; - - /** The name of the TrustManager algorithm. */ - private volatile String trustManagerAlgorithm = System.getProperty("ssl.TrustManagerFactory.algorithm", "SunX509"); - - /** The password for the trust store keystore. */ - private volatile char[] trustStorePassword = (System.getProperty("javax.net.ssl.trustStorePassword") != null) - ? System.getProperty("javax.net.ssl.trustStorePassword").toCharArray() - : null; - - /** The path to the trust store (keystore) file. */ - private volatile String trustStorePath = System.getProperty("javax.net.ssl.trustStore"); - - /** The name of the trust store (keystore) provider. */ - private volatile String trustStoreProvider = System.getProperty("javax.net.ssl.trustStoreProvider"); - - /** The KeyStore type of the trust store. */ - private volatile String trustStoreType = System.getProperty("javax.net.ssl.trustStoreType"); - - /** Indicates if we would like client certificate authentication. */ - private volatile boolean wantClientAuthentication = false; - - /** - * This class is likely to contain sensitive information; cloning is therefore - * not allowed. - */ - @Override - protected final DefaultSslContextFactory clone() throws CloneNotSupportedException { - throw new CloneNotSupportedException(); - } - - /** - * Creates a configured and initialized SSLContext from the values set via the - * various setters of this class. If keyStorePath, - * keyStoreProvider, keyStoreType are all - * null, the SSLContext will be initialized with a - * null array of KeyManagers. Similarly, if - * trustStorePath, trustStoreProvider, - * trustStoreType are all null, a null - * array of TrustManagers will be used. - * - * @see SSLContext#init(javax.net.ssl.KeyManager[], - * javax.net.ssl.TrustManager[], SecureRandom) - */ - @Override - public javax.net.ssl.SSLContext createSslContext() throws Exception { - final javax.net.ssl.SSLContext result; - - final javax.net.ssl.KeyManagerFactory kmf; - if ((this.keyStorePath != null) || (this.keyStoreProvider != null) || (this.keyStoreType != null)) { - final KeyStore keyStore = loadKeyStore(this.keyStorePath, this.keyStoreProvider, this.keyStoreType, this.keyStorePassword); - - // Creates the key-manager factory. - kmf = javax.net.ssl.KeyManagerFactory.getInstance(this.keyManagerAlgorithm); - kmf.init(keyStore, this.keyStoreKeyPassword); - } else { - kmf = null; - } - - final javax.net.ssl.TrustManagerFactory tmf; - if ((this.trustStorePath != null) || (this.trustStoreProvider != null) || (this.trustStoreType != null)) { - final KeyStore trustStore = loadKeyStore(this.trustStorePath, this.trustStoreProvider, this.trustStoreType, this.trustStorePassword); - - // Creates the trust-manager factory. - tmf = javax.net.ssl.TrustManagerFactory.getInstance(this.trustManagerAlgorithm); - tmf.init(trustStore); - } else { - tmf = null; - } - - final SecureRandom sr; - if (this.secureRandomAlgorithm != null) { - sr = SecureRandom.getInstance(this.secureRandomAlgorithm); - } else { - sr = null; - } - - // Creates the SSL context - final javax.net.ssl.SSLContext sslContext = javax.net.ssl.SSLContext.getInstance(this.protocol); - sslContext.init(kmf != null ? kmf.getKeyManagers() : null, tmf != null ? tmf.getTrustManagers() : null, sr); - - // Wraps the SSL context to be able to set cipher suites and other - // properties after SSL engine creation, for example - result = createWrapper(sslContext); - return result; - } - - /** - * Creates a new {@link SSLContext} wrapper. Necessary to properly initialize - * the {@link SSLEngine} or {@link SSLSocketFactory} or - * {@link javax.net.ssl.SSLServerSocketFactory} created. - * - * @param sslContext The SSL context to wrap. - * @return The SSL context wrapper. - */ - protected javax.net.ssl.SSLContext createWrapper(javax.net.ssl.SSLContext sslContext) { - return new DefaultSslContext(this, sslContext); - } - - /** - * Returns the whitespace-separated list of disabled cipher suites. - * - * @return The whitespace-separated list of disabled cipher suites. - */ - public String[] getDisabledCipherSuites() { - return disabledCipherSuites; - } - - /** - * Returns the whitespace-separated list of disabled SSL protocols. - * - * @return The whitespace-separated list of disabled SSL protocols. - */ - public String[] getDisabledProtocols() { - return disabledProtocols; - } - - /** - * Returns the whitespace-separated list of enabled cipher suites. - * - * @return The whitespace-separated list of enabled cipher suites. - */ - public String[] getEnabledCipherSuites() { - return enabledCipherSuites; - } - - /** - * Returns the whitespace-separated list of enabled SSL protocols. - * - * @return The whitespace-separated list of enabled SSL protocols. - */ - public String[] getEnabledProtocols() { - return enabledProtocols; - } - - /** - * Returns the name of the KeyManager algorithm. - * - * @return The name of the KeyManager algorithm. - */ - public String getKeyManagerAlgorithm() { - return keyManagerAlgorithm; - } - - /** - * Returns the password for the key in the keystore (as a String). - * - * @return The password for the key in the keystore (as a String). - */ - public char[] getKeyStoreKeyPassword() { - return keyStoreKeyPassword; - } - - /** - * Returns the password for the keystore (as a String). - * - * @return The password for the keystore (as a String). - */ - public char[] getKeyStorePassword() { - return keyStorePassword; - } - - /** - * Returns the path to the KeyStore file. - * - * @return The path to the KeyStore file. - */ - public String getKeyStorePath() { - return keyStorePath; - } - - /** - * Returns the name of the keystore provider. - * - * @return The name of the keystore provider. - */ - public String getKeyStoreProvider() { - return keyStoreProvider; - } - - /** - * Returns the keyStore type of the keystore. - * - * @return The keyStore type of the keystore. - */ - public String getKeyStoreType() { - return keyStoreType; - } - - /** - * Returns the secure socket protocol name, "TLS" by default. - * - * @return The secure socket protocol. - */ - public String getProtocol() { - return this.protocol; - } - - /** - * Returns the name of the SecureRandom algorithm. - * - * @return The name of the SecureRandom algorithm. - */ - public String getSecureRandomAlgorithm() { - return secureRandomAlgorithm; - } - - /** - * Returns the selected cipher suites. The selection is the subset of supported - * suites that are both in the enabled suites and out of the disabled suites. - * - * @param supportedCipherSuites The initial cipher suites to restrict. - * @return The selected cipher suites. - */ - public String[] getSelectedCipherSuites(String[] supportedCipherSuites) { - Set resultSet = new HashSet(); - - if (supportedCipherSuites != null) { - for (String supportedCipherSuite : supportedCipherSuites) { - if (((getEnabledCipherSuites() == null) - || Arrays.asList(getEnabledCipherSuites()).contains(supportedCipherSuite)) - && ((getDisabledCipherSuites() == null) - || !Arrays.asList(getDisabledCipherSuites()).contains(supportedCipherSuite))) { - resultSet.add(supportedCipherSuite); - } - } - } - - String[] result = new String[resultSet.size()]; - return resultSet.toArray(result); - } - - /** - * Returns the selected SSL protocols. The selection is the subset of supported - * protocols whose name starts with the name of {@link #getEnabledProtocols()} name. - * - * @param supportedProtocols The selected SSL protocols. - * @return The selected SSL protocols. - */ - public String[] getSelectedSslProtocols(String[] supportedProtocols) { - Set resultSet = new HashSet(); - - if (supportedProtocols != null) { - for (String supportedProtocol : supportedProtocols) { - if (((getEnabledProtocols() == null) - || Arrays.asList(getEnabledProtocols()).contains(supportedProtocol)) - && ((getDisabledProtocols() == null) - || !Arrays.asList(getDisabledProtocols()).contains(supportedProtocol))) { - resultSet.add(supportedProtocol); - } - } - } - - String[] result = new String[resultSet.size()]; - return resultSet.toArray(result); - } - - /** - * Returns the name of the TrustManager algorithm. - * - * @return The name of the TrustManager algorithm. - */ - public String getTrustManagerAlgorithm() { - return trustManagerAlgorithm; - } - - /** - * Returns the password for the trust store keystore. - * - * @return The password for the trust store keystore. - */ - public char[] getTrustStorePassword() { - return trustStorePassword; - } - - /** - * Returns the path to the trust store (keystore) file. - * - * @return The path to the trust store (keystore) file. - */ - public String getTrustStorePath() { - return trustStorePath; - } - - /** - * Returns the name of the trust store (keystore) provider. - * - * @return The name of the trust store (keystore) provider. - */ - public String getTrustStoreProvider() { - return trustStoreProvider; - } - - /** - * Returns the KeyStore type of the trust store. - * - * @return The KeyStore type of the trust store. - */ - public String getTrustStoreType() { - return trustStoreType; - } - - /** - * Sets the following options according to parameters that may have been set up - * directly in the HttpsClientHelper or HttpsServerHelper parameters. See class - * Javadocs for the list of parameters supported. - * - * @param helperParameters Typically, the parameters that would have been - * obtained from HttpsServerHelper.getParameters() - */ - @Override - public void init(Series helperParameters) { - // Parses and set the disabled cipher suites - String[] disabledCipherSuitesArray = helperParameters.getValuesArray("disabledCipherSuites"); - Set disabledCipherSuites = new HashSet<>(); - - for (String disabledCipherSuiteSeries : disabledCipherSuitesArray) { + private static final String JAVAX_NET_SSL_KEY_PASSWORD = "javax.net.ssl.keyPassword"; + private static final String JAVAX_NET_SSL_KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; + private static final String USER_HOME = "user.home"; + private static final String JAVAX_NET_SSL_TRUST_STORE_PASSWORD = + "javax.net.ssl.trustStorePassword"; + + /** The whitespace-separated list of disabled cipher suites. */ + private volatile String[] disabledCipherSuites = null; + + /** The whitespace-separated list of disabled SSL protocols. */ + private volatile String[] disabledProtocols = null; + + /** The whitespace-separated list of enabled cipher suites. */ + private volatile String[] enabledCipherSuites = null; + + /** The whitespace-separated list of enabled SSL protocols. */ + private volatile String[] enabledProtocols = null; + + private static final String SUN_X509 = "SunX509"; + + /** The name of the KeyManager algorithm. */ + private volatile String keyManagerAlgorithm = + System.getProperty("ssl.KeyManagerFactory.algorithm", SUN_X509); + + /** The password for the key in the keystore (as a String). */ + private volatile char[] keyStoreKeyPassword = + (System.getProperty( + JAVAX_NET_SSL_KEY_PASSWORD, + System.getProperty(JAVAX_NET_SSL_KEY_STORE_PASSWORD)) + != null) + ? System.getProperty( + JAVAX_NET_SSL_KEY_PASSWORD, + System.getProperty(JAVAX_NET_SSL_KEY_STORE_PASSWORD)) + .toCharArray() + : null; + + /** The password for the keystore (as a String). */ + private volatile char[] keyStorePassword = + (System.getProperty(JAVAX_NET_SSL_KEY_STORE_PASSWORD) != null) + ? System.getProperty(JAVAX_NET_SSL_KEY_STORE_PASSWORD).toCharArray() + : null; + + /** The path to the KeyStore file. */ + private volatile String keyStorePath = + System.getProperty( + "javax.net.ssl.keyStore", + (System.getProperty(USER_HOME) != null) + ? ((System.getProperty(USER_HOME).endsWith("/")) + ? System.getProperty(USER_HOME) + ".keystore" + : System.getProperty(USER_HOME) + "/.keystore") + : null); + + /** The name of the keystore provider. */ + private volatile String keyStoreProvider = System.getProperty("javax.net.ssl.keyStoreProvider"); + + /** The keyStore type of the keystore. */ + private volatile String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", "JKS"); + + /** Indicates if we require client certificate authentication. */ + private volatile boolean needClientAuthentication = false; + + /** The standard name of the protocol to use when creating the SSLContext. */ + private volatile String protocol = "TLS"; + + /** The name of the SecureRandom algorithm. */ + private volatile String secureRandomAlgorithm = null; + + /** The name of the TrustManager algorithm. */ + private volatile String trustManagerAlgorithm = + System.getProperty("ssl.TrustManagerFactory.algorithm", SUN_X509); + + /** The password for the trust store keystore. */ + private volatile char[] trustStorePassword = + (System.getProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD) != null) + ? System.getProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD).toCharArray() + : null; + + /** The path to the trust store (keystore) file. */ + private volatile String trustStorePath = System.getProperty("javax.net.ssl.trustStore"); + + /** The name of the trust store (keystore) provider. */ + private volatile String trustStoreProvider = + System.getProperty("javax.net.ssl.trustStoreProvider"); + + /** The KeyStore type of the trust store. */ + private volatile String trustStoreType = System.getProperty("javax.net.ssl.trustStoreType"); + + /** Indicates if we would like client certificate authentication. */ + private volatile boolean wantClientAuthentication = false; + + /** This class is likely to contain sensitive information; cloning is therefore not allowed. */ + @Override + protected final DefaultSslContextFactory clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException(); + } + + /** + * Creates a configured and initialized SSLContext from the values set via the + * various setters of this class. If keyStorePath, + * keyStoreProvider, keyStoreType are all + * null, the SSLContext will be initialized with a + * null array of KeyManagers. Similarly, if + * trustStorePath, trustStoreProvider, + * trustStoreType are all null, a null + * array of TrustManagers will be used. + * + * @see SSLContext#init(javax.net.ssl.KeyManager[], + * javax.net.ssl.TrustManager[], SecureRandom) + */ + @Override + public javax.net.ssl.SSLContext createSslContext() throws Exception { + final javax.net.ssl.SSLContext result; + + final javax.net.ssl.KeyManagerFactory kmf; + if ((this.keyStorePath != null) + || (this.keyStoreProvider != null) + || (this.keyStoreType != null)) { + final KeyStore keyStore = + loadKeyStore( + this.keyStorePath, + this.keyStoreProvider, + this.keyStoreType, + this.keyStorePassword); + + // Creates the key-manager factory. + kmf = javax.net.ssl.KeyManagerFactory.getInstance(this.keyManagerAlgorithm); + kmf.init(keyStore, this.keyStoreKeyPassword); + } else { + kmf = null; + } + + final javax.net.ssl.TrustManagerFactory tmf; + if ((this.trustStorePath != null) + || (this.trustStoreProvider != null) + || (this.trustStoreType != null)) { + final KeyStore trustStore = + loadKeyStore( + this.trustStorePath, + this.trustStoreProvider, + this.trustStoreType, + this.trustStorePassword); + + // Creates the trust-manager factory. + tmf = javax.net.ssl.TrustManagerFactory.getInstance(this.trustManagerAlgorithm); + tmf.init(trustStore); + } else { + tmf = null; + } + + final SecureRandom sr; + if (this.secureRandomAlgorithm != null) { + sr = SecureRandom.getInstance(this.secureRandomAlgorithm); + } else { + sr = null; + } + + // Creates the SSL context + final javax.net.ssl.SSLContext sslContext = + javax.net.ssl.SSLContext.getInstance(this.protocol); + sslContext.init( + kmf != null ? kmf.getKeyManagers() : null, + tmf != null ? tmf.getTrustManagers() : null, + sr); + + // Wraps the SSL context to be able to set cipher suites and other + // properties after SSL engine creation, for example + result = createWrapper(sslContext); + return result; + } + + /** + * Creates a new {@link SSLContext} wrapper. Necessary to properly initialize the {@link + * SSLEngine} or {@link SSLSocketFactory} or {@link javax.net.ssl.SSLServerSocketFactory} + * created. + * + * @param sslContext The SSL context to wrap. + * @return The SSL context wrapper. + */ + protected javax.net.ssl.SSLContext createWrapper(javax.net.ssl.SSLContext sslContext) { + return new DefaultSslContext(this, sslContext); + } + + /** + * Returns the whitespace-separated list of disabled cipher suites. + * + * @return The whitespace-separated list of disabled cipher suites. + */ + public String[] getDisabledCipherSuites() { + return disabledCipherSuites; + } + + /** + * Returns the whitespace-separated list of disabled SSL protocols. + * + * @return The whitespace-separated list of disabled SSL protocols. + */ + public String[] getDisabledProtocols() { + return disabledProtocols; + } + + /** + * Returns the whitespace-separated list of enabled cipher suites. + * + * @return The whitespace-separated list of enabled cipher suites. + */ + public String[] getEnabledCipherSuites() { + return enabledCipherSuites; + } + + /** + * Returns the whitespace-separated list of enabled SSL protocols. + * + * @return The whitespace-separated list of enabled SSL protocols. + */ + public String[] getEnabledProtocols() { + return enabledProtocols; + } + + /** + * Returns the name of the KeyManager algorithm. + * + * @return The name of the KeyManager algorithm. + */ + public String getKeyManagerAlgorithm() { + return keyManagerAlgorithm; + } + + /** + * Returns the password for the key in the keystore (as a String). + * + * @return The password for the key in the keystore (as a String). + */ + public char[] getKeyStoreKeyPassword() { + return keyStoreKeyPassword; + } + + /** + * Returns the password for the keystore (as a String). + * + * @return The password for the keystore (as a String). + */ + public char[] getKeyStorePassword() { + return keyStorePassword; + } + + /** + * Returns the path to the KeyStore file. + * + * @return The path to the KeyStore file. + */ + public String getKeyStorePath() { + return keyStorePath; + } + + /** + * Returns the name of the keystore provider. + * + * @return The name of the keystore provider. + */ + public String getKeyStoreProvider() { + return keyStoreProvider; + } + + /** + * Returns the keyStore type of the keystore. + * + * @return The keyStore type of the keystore. + */ + public String getKeyStoreType() { + return keyStoreType; + } + + /** + * Returns the secure socket protocol name, "TLS" by default. + * + * @return The secure socket protocol. + */ + public String getProtocol() { + return this.protocol; + } + + /** + * Returns the name of the SecureRandom algorithm. + * + * @return The name of the SecureRandom algorithm. + */ + public String getSecureRandomAlgorithm() { + return secureRandomAlgorithm; + } + + /** + * Returns the selected cipher suites. The selection is the subset of supported suites that are + * both in the enabled suites and out of the disabled suites. + * + * @param supportedCipherSuites The initial cipher suites to restrict. + * @return The selected cipher suites. + */ + public String[] getSelectedCipherSuites(String[] supportedCipherSuites) { + Set resultSet = new HashSet<>(); + + if (supportedCipherSuites != null) { + for (String supportedCipherSuite : supportedCipherSuites) { + if (((getEnabledCipherSuites() == null) + || Arrays.asList(getEnabledCipherSuites()) + .contains(supportedCipherSuite)) + && ((getDisabledCipherSuites() == null) + || !Arrays.asList(getDisabledCipherSuites()) + .contains(supportedCipherSuite))) { + resultSet.add(supportedCipherSuite); + } + } + } + + String[] result = new String[resultSet.size()]; + return resultSet.toArray(result); + } + + /** + * Returns the selected SSL protocols. The selection is the subset of supported protocols whose + * name starts with the name of {@link #getEnabledProtocols()} name. + * + * @param supportedProtocols The selected SSL protocols. + * @return The selected SSL protocols. + */ + public String[] getSelectedSslProtocols(String[] supportedProtocols) { + Set resultSet = new HashSet<>(); + + if (supportedProtocols != null) { + for (String supportedProtocol : supportedProtocols) { + if (((getEnabledProtocols() == null) + || Arrays.asList(getEnabledProtocols()).contains(supportedProtocol)) + && ((getDisabledProtocols() == null) + || !Arrays.asList(getDisabledProtocols()) + .contains(supportedProtocol))) { + resultSet.add(supportedProtocol); + } + } + } + + String[] result = new String[resultSet.size()]; + return resultSet.toArray(result); + } + + /** + * Returns the name of the TrustManager algorithm. + * + * @return The name of the TrustManager algorithm. + */ + public String getTrustManagerAlgorithm() { + return trustManagerAlgorithm; + } + + /** + * Returns the password for the trust store keystore. + * + * @return The password for the trust store keystore. + */ + public char[] getTrustStorePassword() { + return trustStorePassword; + } + + /** + * Returns the path to the trust store (keystore) file. + * + * @return The path to the trust store (keystore) file. + */ + public String getTrustStorePath() { + return trustStorePath; + } + + /** + * Returns the name of the trust store (keystore) provider. + * + * @return The name of the trust store (keystore) provider. + */ + public String getTrustStoreProvider() { + return trustStoreProvider; + } + + /** + * Returns the KeyStore type of the trust store. + * + * @return The KeyStore type of the trust store. + */ + public String getTrustStoreType() { + return trustStoreType; + } + + /** + * Sets the following options according to parameters that may have been set up directly in the + * HttpsClientHelper or HttpsServerHelper parameters. See class Javadocs for the list of + * parameters supported. + * + * @param helperParameters Typically, the parameters that would have been obtained from + * HttpsServerHelper.getParameters() + */ + @Override + public void init(Series helperParameters) { + // Parses and set the disabled cipher suites + String[] disabledCipherSuitesArray = + helperParameters.getValuesArray("disabledCipherSuites"); + Set disabledCipherSuites = new HashSet<>(); + + for (String disabledCipherSuiteSeries : disabledCipherSuitesArray) { Collections.addAll(disabledCipherSuites, disabledCipherSuiteSeries.split(" ")); - } + } if (disabledCipherSuites.isEmpty()) { setDisabledCipherSuites(null); @@ -545,12 +582,12 @@ public void init(Series helperParameters) { } // Parses and set the disabled protocols - String[] disabledProtocolsArray = helperParameters.getValuesArray("disabledProtocols"); - Set disabledProtocols = new HashSet<>(); + String[] disabledProtocolsArray = helperParameters.getValuesArray("disabledProtocols"); + Set disabledProtocols = new HashSet<>(); - for (String disabledProtocolsSeries : disabledProtocolsArray) { + for (String disabledProtocolsSeries : disabledProtocolsArray) { Collections.addAll(disabledProtocols, disabledProtocolsSeries.split(" ")); - } + } if (disabledProtocols.isEmpty()) { setDisabledProtocols(null); @@ -561,12 +598,12 @@ public void init(Series helperParameters) { } // Parses and set the enabled cipher suites - String[] enabledCipherSuitesArray = helperParameters.getValuesArray("enabledCipherSuites"); - Set enabledCipherSuites = new HashSet<>(); + String[] enabledCipherSuitesArray = helperParameters.getValuesArray("enabledCipherSuites"); + Set enabledCipherSuites = new HashSet<>(); - for (String enabledCipherSuiteSeries : enabledCipherSuitesArray) { + for (String enabledCipherSuiteSeries : enabledCipherSuitesArray) { Collections.addAll(enabledCipherSuites, enabledCipherSuiteSeries.split(" ")); - } + } if (enabledCipherSuites.isEmpty()) { setEnabledCipherSuites(null); @@ -577,12 +614,12 @@ public void init(Series helperParameters) { } // Parses and set the enabled protocols - String[] enabledProtocolsArray = helperParameters.getValuesArray("enabledProtocols"); - Set enabledProtocols = new HashSet<>(); + String[] enabledProtocolsArray = helperParameters.getValuesArray("enabledProtocols"); + Set enabledProtocols = new HashSet<>(); - for (String enabledProtocolSeries : enabledProtocolsArray) { + for (String enabledProtocolSeries : enabledProtocolsArray) { Collections.addAll(enabledProtocols, enabledProtocolSeries.split(" ")); - } + } if (enabledProtocols.isEmpty()) { setEnabledProtocols(null); @@ -592,304 +629,319 @@ public void init(Series helperParameters) { setEnabledProtocols(enabledProtocolsArray); } - setKeyManagerAlgorithm(helperParameters.getFirstValue("keyManagerAlgorithm", true, - System.getProperty("ssl.KeyManagerFactory.algorithm", "SunX509"))); - setKeyStorePassword(helperParameters.getFirstValue("keyStorePassword", true, - System.getProperty("javax.net.ssl.keyStorePassword", ""))); - setKeyStoreKeyPassword( - helperParameters.getFirstValue("keyPassword", true, System.getProperty("javax.net.ssl.keyPassword"))); - - if (this.keyStoreKeyPassword == null) { - this.keyStoreKeyPassword = this.keyStorePassword; - } - - setKeyStorePath( - helperParameters.getFirstValue("keyStorePath", true, System.getProperty("javax.net.ssl.keyStore"))); - setKeyStoreType( - helperParameters.getFirstValue("keyStoreType", true, System.getProperty("javax.net.ssl.keyStoreType"))); - setNeedClientAuthentication( - Boolean.parseBoolean(helperParameters.getFirstValue("needClientAuthentication", true, "false"))); - setProtocol(helperParameters.getFirstValue("protocol", true, "TLS")); - setSecureRandomAlgorithm(helperParameters.getFirstValue("secureRandomAlgorithm", true)); - setTrustManagerAlgorithm(helperParameters.getFirstValue("trustManagerAlgorithm", true, - System.getProperty("ssl.TrustManagerFactory.algorithm", "SunX509"))); - setTrustStorePassword(helperParameters.getFirstValue("trustStorePassword", true, - System.getProperty("javax.net.ssl.trustStorePassword"))); - setTrustStorePath( - helperParameters.getFirstValue("trustStorePath", true, System.getProperty("javax.net.ssl.trustStore"))); - setTrustStoreType(helperParameters.getFirstValue("trustStoreType", true, - System.getProperty("javax.net.ssl.trustStoreType"))); - setWantClientAuthentication( - Boolean.parseBoolean(helperParameters.getFirstValue("wantClientAuthentication", true, "false"))); - } - - /** - * Indicates if we require client certificate authentication. - * - * @return True if we require client certificate authentication. - */ - public boolean isNeedClientAuthentication() { - return needClientAuthentication; - } - - /** - * Indicates if we would like client certificate authentication. - * - * @return True if we would like client certificate authentication. - */ - public boolean isWantClientAuthentication() { - return wantClientAuthentication; - } - - /** - * Loads a keystore according to its file path, type and password. - * @param path The file path of the keystore. - * @param provider The name of the keystore provider. - * @param type The keystore type of the keystore. - * @param password the optional password of the keystore. - * @return a keystore. - * @throws Exception - */ - protected KeyStore loadKeyStore(String path, String provider, String type, char[] password) throws Exception { - final String nonNullKeyStoreType = (type != null) ? type : KeyStore.getDefaultType(); - final KeyStore keyStore = (provider != null) - ? KeyStore.getInstance(nonNullKeyStoreType, provider) - : KeyStore.getInstance(nonNullKeyStoreType); - - try (FileInputStream keyStoreInputStream = ((path != null) && (!"NONE".equals(path))) - ? new FileInputStream(path) - : null) { - keyStore.load(keyStoreInputStream, password); - } - - return keyStore; - } - - /** - * Sets the whitespace-separated list of disabled cipher suites. - * - * @param disabledCipherSuites The whitespace-separated list of disabled cipher - * suites. - */ - public void setDisabledCipherSuites(String[] disabledCipherSuites) { - this.disabledCipherSuites = disabledCipherSuites; - } - - /** - * Sets the whitespace-separated list of disabled SSL protocols. - * - * @param disabledProtocols The whitespace-separated list of disabled SSL - * protocols. - */ - public void setDisabledProtocols(String[] disabledProtocols) { - this.disabledProtocols = disabledProtocols; - } - - /** - * Sets the whitespace-separated list of enabled cipher suites. - * - * @param enabledCipherSuites The whitespace-separated list of enabled cipher - * suites. - */ - public void setEnabledCipherSuites(String[] enabledCipherSuites) { - this.enabledCipherSuites = enabledCipherSuites; - } - - /** - * Sets the standard name of the protocols to use when creating the SSL sockets - * or engines. - * - * @param enabledProtocols The standard name of the protocols to use when - * creating the SSL sockets or engines. - */ - public void setEnabledProtocols(String[] enabledProtocols) { - this.enabledProtocols = enabledProtocols; - } - - /** - * Sets the KeyManager algorithm. The default value is that of the - * ssl.KeyManagerFactory.algorithm system property, or "SunX509" - * if the system property has not been set up. - * - * @param keyManagerAlgorithm The KeyManager algorithm. - */ - public void setKeyManagerAlgorithm(String keyManagerAlgorithm) { - this.keyManagerAlgorithm = keyManagerAlgorithm; - } - - /** - * Sets the password of the key in the keystore. The default value is that of - * the javax.net.ssl.keyPassword system property, falling back to - * javax.net.ssl.keyStorePassword. This system property name is not - * standard. - * - * @param keyStoreKeyPassword The password of the key in the keystore. - */ - public void setKeyStoreKeyPassword(char[] keyStoreKeyPassword) { - this.keyStoreKeyPassword = keyStoreKeyPassword; - } - - /** - * Sets the password of the key in the keystore. The default value is that of - * the javax.net.ssl.keyPassword system property, falling back to - * javax.net.ssl.keyStorePassword. This system property name is not - * standard. - * - * @param keyStoreKeyPassword The password of the key in the keystore. - */ - public void setKeyStoreKeyPassword(String keyStoreKeyPassword) { - this.keyStoreKeyPassword = (keyStoreKeyPassword != null) ? keyStoreKeyPassword.toCharArray() : null; - } - - /** - * Sets the keystore password. The default value is that of the - * javax.net.ssl.keyStorePassword system property. - * - * @param keyStorePassword Sets the keystore password. - */ - public void setKeyStorePassword(char[] keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - - /** - * Sets the keystore password. The default value is that of the - * javax.net.ssl.keyStorePassword system property. - * - * @param keyStorePassword Sets the keystore password. - */ - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = (keyStorePassword != null) ? keyStorePassword.toCharArray() : null; - } - - /** - * Sets the path to the keystore file. The default value is that of the - * javax.net.ssl.keyStore system property. - * - * @param keyStorePath The path to the keystore file. - */ - public void setKeyStorePath(String keyStorePath) { - this.keyStorePath = keyStorePath; - } - - /** - * Sets the name of the keystore provider. The default value is that of the - * javax.net.ssl.keyStoreProvider system property. - * - * @param keyStoreProvider The name of the keystore provider. - */ - public void setKeyStoreProvider(String keyStoreProvider) { - this.keyStoreProvider = keyStoreProvider; - } - - /** - * Sets the KeyStore type of the keystore. The default value is that of the - * javax.net.ssl.keyStoreType system property. - * - * @param keyStoreType The KeyStore type of the keystore. - */ - public void setKeyStoreType(String keyStoreType) { - this.keyStoreType = keyStoreType; - } - - /** - * Indicates if we require client certificate authentication. The default value - * is false. - * - * @param needClientAuthentication True if we require client certificate - * authentication. - */ - public void setNeedClientAuthentication(boolean needClientAuthentication) { - this.needClientAuthentication = needClientAuthentication; - } - - /** - * Sets the secure socket protocol name, "TLS" by default. - * - * @param protocol Name of the secure socket protocol to use. - */ - public void setProtocol(String protocol) { - this.protocol = protocol; - } - - /** - * Sets the SecureRandom algorithm. The default value is null, in which - * case the default SecureRandom would be used. - * - * @param secureRandomAlgorithm The SecureRandom algorithm. - */ - public void setSecureRandomAlgorithm(String secureRandomAlgorithm) { - this.secureRandomAlgorithm = secureRandomAlgorithm; - } - - /** - * Sets the TrustManager algorithm. The default value is that of the - * ssl.TrustManagerFactory.algorithm system property, or "SunX509" - * if the system property has not been set up. - * - * @param trustManagerAlgorithm The TrustManager algorithm. - */ - public void setTrustManagerAlgorithm(String trustManagerAlgorithm) { - this.trustManagerAlgorithm = trustManagerAlgorithm; - } - - /** - * Sets the password of the trust store KeyStore. The default value is that of - * the javax.net.ssl.trustStorePassword system property. - * - * @param trustStorePassword The password of the trust store KeyStore. - */ - public void setTrustStorePassword(char[] trustStorePassword) { - this.trustStorePassword = trustStorePassword; - } - - /** - * Sets the password of the trust store KeyStore. The default value is that of - * the javax.net.ssl.trustStorePassword system property. - * - * @param trustStorePassword The password of the trust store KeyStore. - */ - public void setTrustStorePassword(String trustStorePassword) { - this.trustStorePassword = (trustStorePassword != null) ? trustStorePassword.toCharArray() : null; - } - - /** - * Sets the path to the trust store KeyStore. The default value is that of the - * javax.net.ssl.trustStore system property. - * - * @param trustStorePath The trustStorePath to set - */ - public void setTrustStorePath(String trustStorePath) { - this.trustStorePath = trustStorePath; - } - - /** - * Sets the name of the trust store provider. The default value is that of the - * javax.net.ssl.trustStoreProvider system property. - * - * @param trustStoreProvider The name of the trust store provider. - */ - public void setTrustStoreProvider(String trustStoreProvider) { - this.trustStoreProvider = trustStoreProvider; - } - - /** - * Sets the KeyStore type of the trust store. The default value is that of the - * javax.net.ssl.trustStoreType system property. - * - * @param trustStoreType The KeyStore type of the trust store. - */ - public void setTrustStoreType(String trustStoreType) { - this.trustStoreType = trustStoreType; - } - - /** - * Indicates if we would like client certificate authentication. The default - * value is false. - * - * @param wantClientAuthentication True if we would like client certificate - * authentication. - */ - public void setWantClientAuthentication(boolean wantClientAuthentication) { - this.wantClientAuthentication = wantClientAuthentication; - } + setKeyManagerAlgorithm( + helperParameters.getFirstValue( + "keyManagerAlgorithm", + true, + System.getProperty("ssl.KeyManagerFactory.algorithm", SUN_X509))); + setKeyStorePassword( + helperParameters.getFirstValue( + "keyStorePassword", + true, + System.getProperty(JAVAX_NET_SSL_KEY_STORE_PASSWORD, ""))); + setKeyStoreKeyPassword( + helperParameters.getFirstValue( + "keyPassword", true, System.getProperty(JAVAX_NET_SSL_KEY_PASSWORD))); + + if (this.keyStoreKeyPassword == null) { + this.keyStoreKeyPassword = this.keyStorePassword; + } + + setKeyStorePath( + helperParameters.getFirstValue( + "keyStorePath", true, System.getProperty("javax.net.ssl.keyStore"))); + setKeyStoreType( + helperParameters.getFirstValue( + "keyStoreType", true, System.getProperty("javax.net.ssl.keyStoreType"))); + setNeedClientAuthentication( + Boolean.parseBoolean( + helperParameters.getFirstValue("needClientAuthentication", true, "false"))); + setProtocol(helperParameters.getFirstValue("protocol", true, "TLS")); + setSecureRandomAlgorithm(helperParameters.getFirstValue("secureRandomAlgorithm", true)); + setTrustManagerAlgorithm( + helperParameters.getFirstValue( + "trustManagerAlgorithm", + true, + System.getProperty("ssl.TrustManagerFactory.algorithm", SUN_X509))); + setTrustStorePassword( + helperParameters.getFirstValue( + "trustStorePassword", + true, + System.getProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD))); + setTrustStorePath( + helperParameters.getFirstValue( + "trustStorePath", true, System.getProperty("javax.net.ssl.trustStore"))); + setTrustStoreType( + helperParameters.getFirstValue( + "trustStoreType", + true, + System.getProperty("javax.net.ssl.trustStoreType"))); + setWantClientAuthentication( + Boolean.parseBoolean( + helperParameters.getFirstValue("wantClientAuthentication", true, "false"))); + } + + /** + * Indicates if we require client certificate authentication. + * + * @return True if we require client certificate authentication. + */ + public boolean isNeedClientAuthentication() { + return needClientAuthentication; + } + + /** + * Indicates if we would like client certificate authentication. + * + * @return True if we would like client certificate authentication. + */ + public boolean isWantClientAuthentication() { + return wantClientAuthentication; + } + + /** + * Loads a keystore according to its file path, type and password. + * + * @param path The file path of the keystore. + * @param provider The name of the keystore provider. + * @param type The keystore type of the keystore. + * @param password the optional password of the keystore. + * @return a keystore. + * @throws Exception + */ + protected KeyStore loadKeyStore(String path, String provider, String type, char[] password) + throws Exception { + final String nonNullKeyStoreType = (type != null) ? type : KeyStore.getDefaultType(); + final KeyStore keyStore = + (provider != null) + ? KeyStore.getInstance(nonNullKeyStoreType, provider) + : KeyStore.getInstance(nonNullKeyStoreType); + + try (FileInputStream keyStoreInputStream = + ((path != null) && (!"NONE".equals(path))) ? new FileInputStream(path) : null) { + keyStore.load(keyStoreInputStream, password); + } + + return keyStore; + } + + /** + * Sets the whitespace-separated list of disabled cipher suites. + * + * @param disabledCipherSuites The whitespace-separated list of disabled cipher suites. + */ + public void setDisabledCipherSuites(String[] disabledCipherSuites) { + this.disabledCipherSuites = disabledCipherSuites; + } + + /** + * Sets the whitespace-separated list of disabled SSL protocols. + * + * @param disabledProtocols The whitespace-separated list of disabled SSL protocols. + */ + public void setDisabledProtocols(String[] disabledProtocols) { + this.disabledProtocols = disabledProtocols; + } + + /** + * Sets the whitespace-separated list of enabled cipher suites. + * + * @param enabledCipherSuites The whitespace-separated list of enabled cipher suites. + */ + public void setEnabledCipherSuites(String[] enabledCipherSuites) { + this.enabledCipherSuites = enabledCipherSuites; + } + + /** + * Sets the standard name of the protocols to use when creating the SSL sockets or engines. + * + * @param enabledProtocols The standard name of the protocols to use when creating the SSL + * sockets or engines. + */ + public void setEnabledProtocols(String[] enabledProtocols) { + this.enabledProtocols = enabledProtocols; + } + + /** + * Sets the KeyManager algorithm. The default value is that of the + * ssl.KeyManagerFactory.algorithm system property, or "SunX509" if the system + * property has not been set up. + * + * @param keyManagerAlgorithm The KeyManager algorithm. + */ + public void setKeyManagerAlgorithm(String keyManagerAlgorithm) { + this.keyManagerAlgorithm = keyManagerAlgorithm; + } + + /** + * Sets the password of the key in the keystore. The default value is that of the + * javax.net.ssl.keyPassword system property, falling back to + * javax.net.ssl.keyStorePassword. This system property name is not standard. + * + * @param keyStoreKeyPassword The password of the key in the keystore. + */ + public void setKeyStoreKeyPassword(char[] keyStoreKeyPassword) { + this.keyStoreKeyPassword = keyStoreKeyPassword; + } + + /** + * Sets the password of the key in the keystore. The default value is that of the + * javax.net.ssl.keyPassword system property, falling back to + * javax.net.ssl.keyStorePassword. This system property name is not standard. + * + * @param keyStoreKeyPassword The password of the key in the keystore. + */ + public void setKeyStoreKeyPassword(String keyStoreKeyPassword) { + this.keyStoreKeyPassword = + (keyStoreKeyPassword != null) ? keyStoreKeyPassword.toCharArray() : null; + } + + /** + * Sets the keystore password. The default value is that of the + * javax.net.ssl.keyStorePassword system property. + * + * @param keyStorePassword Sets the keystore password. + */ + public void setKeyStorePassword(char[] keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + /** + * Sets the keystore password. The default value is that of the + * javax.net.ssl.keyStorePassword system property. + * + * @param keyStorePassword Sets the keystore password. + */ + public void setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = (keyStorePassword != null) ? keyStorePassword.toCharArray() : null; + } + + /** + * Sets the path to the keystore file. The default value is that of the + * javax.net.ssl.keyStore system property. + * + * @param keyStorePath The path to the keystore file. + */ + public void setKeyStorePath(String keyStorePath) { + this.keyStorePath = keyStorePath; + } + + /** + * Sets the name of the keystore provider. The default value is that of the + * javax.net.ssl.keyStoreProvider system property. + * + * @param keyStoreProvider The name of the keystore provider. + */ + public void setKeyStoreProvider(String keyStoreProvider) { + this.keyStoreProvider = keyStoreProvider; + } + + /** + * Sets the KeyStore type of the keystore. The default value is that of the + * javax.net.ssl.keyStoreType system property. + * + * @param keyStoreType The KeyStore type of the keystore. + */ + public void setKeyStoreType(String keyStoreType) { + this.keyStoreType = keyStoreType; + } + + /** + * Indicates if we require client certificate authentication. The default value is false. + * + * @param needClientAuthentication True if we require client certificate authentication. + */ + public void setNeedClientAuthentication(boolean needClientAuthentication) { + this.needClientAuthentication = needClientAuthentication; + } + + /** + * Sets the secure socket protocol name, "TLS" by default. + * + * @param protocol Name of the secure socket protocol to use. + */ + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + /** + * Sets the SecureRandom algorithm. The default value is null, in which case the default + * SecureRandom would be used. + * + * @param secureRandomAlgorithm The SecureRandom algorithm. + */ + public void setSecureRandomAlgorithm(String secureRandomAlgorithm) { + this.secureRandomAlgorithm = secureRandomAlgorithm; + } + + /** + * Sets the TrustManager algorithm. The default value is that of the + * ssl.TrustManagerFactory.algorithm system property, or "SunX509" if the system + * property has not been set up. + * + * @param trustManagerAlgorithm The TrustManager algorithm. + */ + public void setTrustManagerAlgorithm(String trustManagerAlgorithm) { + this.trustManagerAlgorithm = trustManagerAlgorithm; + } + + /** + * Sets the password of the trust store KeyStore. The default value is that of the + * javax.net.ssl.trustStorePassword system property. + * + * @param trustStorePassword The password of the trust store KeyStore. + */ + public void setTrustStorePassword(char[] trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + + /** + * Sets the password of the trust store KeyStore. The default value is that of the + * javax.net.ssl.trustStorePassword system property. + * + * @param trustStorePassword The password of the trust store KeyStore. + */ + public void setTrustStorePassword(String trustStorePassword) { + this.trustStorePassword = + (trustStorePassword != null) ? trustStorePassword.toCharArray() : null; + } + + /** + * Sets the path to the trust store KeyStore. The default value is that of the + * javax.net.ssl.trustStore system property. + * + * @param trustStorePath The trustStorePath to set + */ + public void setTrustStorePath(String trustStorePath) { + this.trustStorePath = trustStorePath; + } + + /** + * Sets the name of the trust store provider. The default value is that of the + * javax.net.ssl.trustStoreProvider system property. + * + * @param trustStoreProvider The name of the trust store provider. + */ + public void setTrustStoreProvider(String trustStoreProvider) { + this.trustStoreProvider = trustStoreProvider; + } + + /** + * Sets the KeyStore type of the trust store. The default value is that of the + * javax.net.ssl.trustStoreType system property. + * + * @param trustStoreType The KeyStore type of the trust store. + */ + public void setTrustStoreType(String trustStoreType) { + this.trustStoreType = trustStoreType; + } + + /** + * Indicates if we would like client certificate authentication. The default value is false. + * + * @param wantClientAuthentication True if we would like client certificate authentication. + */ + public void setWantClientAuthentication(boolean wantClientAuthentication) { + this.wantClientAuthentication = wantClientAuthentication; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/ssl/SslContextFactory.java b/org.restlet/src/main/java/org/restlet/engine/ssl/SslContextFactory.java index 01406d317e..d47aca6757 100644 --- a/org.restlet/src/main/java/org/restlet/engine/ssl/SslContextFactory.java +++ b/org.restlet/src/main/java/org/restlet/engine/ssl/SslContextFactory.java @@ -1,48 +1,46 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.ssl; +import javax.net.ssl.SSLContext; import org.restlet.data.Parameter; import org.restlet.util.Series; -import javax.net.ssl.SSLContext; - /** - * This is an abstract factory that produces configured and initialized - * instances of SSLContext. Concrete implementations of SslContextFactory must - * implement {@link #createSslContext()}, which should typically consist of: - * + * This is an abstract factory that produces configured and initialized instances of SSLContext. + * Concrete implementations of SslContextFactory must implement {@link #createSslContext()}, which + * should typically consist of: + * *

  *    SSLContext sslContext = SSLContext.getInstance(...);
  *    ...
  *    sslContext.init(..., ..., ...);
  *    return sslContext;
  * 
- * + * * @author Bruno Harbulot * @see SSLContext */ public abstract class SslContextFactory { - /** - * Creates a configured and initialized SSLContext. - * - * @return A configured and initialized SSLContext. - * @throws Exception - */ - public abstract SSLContext createSslContext() throws Exception; + /** + * Creates a configured and initialized SSLContext. + * + * @return A configured and initialized SSLContext. + * @throws Exception + */ + public abstract SSLContext createSslContext() throws Exception; - /** - * Initialize the factory with the given connector parameters. - * - * @param parameters The connector parameters. - */ - public abstract void init(Series parameters); + /** + * Initialize the factory with the given connector parameters. + * + * @param parameters The connector parameters. + */ + public abstract void init(Series parameters); } diff --git a/org.restlet/src/main/java/org/restlet/engine/ssl/SslUtils.java b/org.restlet/src/main/java/org/restlet/engine/ssl/SslUtils.java index 14474389e3..eea44c58af 100644 --- a/org.restlet/src/main/java/org/restlet/engine/ssl/SslUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/ssl/SslUtils.java @@ -1,57 +1,57 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.ssl; -import org.restlet.Context; -import org.restlet.engine.RestletHelper; - import java.lang.reflect.InvocationTargetException; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.data.Parameter; +import org.restlet.engine.RestletHelper; +import org.restlet.util.Series; /** * Various HTTPS utilities. - * + * * @author Jerome Louvel */ public class SslUtils { - /** Cache of SSL key sizes for various cipher suites. */ - private final static ConcurrentMap keySizesCache = new ConcurrentHashMap(); - - /** - * Extract the SSL key size of a given cipher suite. - * - * @param sslCipherSuite The SSL cipher suite. - * @return The SSL key size. - */ - public static Integer extractKeySize(String sslCipherSuite) { - Integer keySize = keySizesCache.get(sslCipherSuite); - - if (keySize == null) { - final int encAlgorithmIndex = sslCipherSuite.indexOf("WITH_"); - if (encAlgorithmIndex >= 0) { - final String encAlgorithm = sslCipherSuite.substring(encAlgorithmIndex + 5); - - /* - * (Encryption algorithms and key sizes, quoted from RFC 2246) - * - * Key Expanded Effective IV Block Cipher Type Material Key Material Key Bits - * Size Size - * - * NULL Stream 0 0 0 0 N/A IDEA_CBC Block 16 16 128 8 8 RC2_CBC_40 Block 5 16 40 - * 8 8 RC4_40 Stream 5 16 40 0 N/A RC4_128 Stream 16 16 128 0 N/A DES40_CBC - * Block 5 8 40 8 8 DES_CBC Block 8 8 56 8 8 3DES_EDE_CBC Block 24 24 168 8 8 - */ + /** Cache of SSL key sizes for various cipher suites. */ + private static final ConcurrentMap keySizesCache = new ConcurrentHashMap<>(); + + /** + * Extract the SSL key size of a given cipher suite. + * + * @param sslCipherSuite The SSL cipher suite. + * @return The SSL key size. + */ + public static Integer extractKeySize(String sslCipherSuite) { + Integer keySize = keySizesCache.get(sslCipherSuite); + + if (keySize == null) { + final int encAlgorithmIndex = sslCipherSuite.indexOf("WITH_"); + if (encAlgorithmIndex >= 0) { + final String encAlgorithm = sslCipherSuite.substring(encAlgorithmIndex + 5); + + /* + * (Encryption algorithms and key sizes, quoted from RFC 2246) + * + * Key Expanded Effective IV Block Cipher Type Material Key Material Key Bits + * Size Size + * + * NULL Stream 0 0 0 0 N/A IDEA_CBC Block 16 16 128 8 8 RC2_CBC_40 Block 5 16 40 + * 8 8 RC4_40 Stream 5 16 40 0 N/A RC4_128 Stream 16 16 128 0 N/A DES40_CBC + * Block 5 8 40 8 8 DES_CBC Block 8 8 56 8 8 3DES_EDE_CBC Block 24 24 168 8 8 + */ if (encAlgorithm.startsWith("NULL_")) { keySize = 0; } else if (encAlgorithm.startsWith("IDEA_CBC_")) { @@ -76,7 +76,7 @@ public static Integer extractKeySize(String sslCipherSuite) { keySize = Integer.valueOf(st.nextToken()); break; } catch (NumberFormatException e) { -// Tokens that are not integers are ignored. + // Tokens that are not integers are ignored. } } } @@ -85,69 +85,118 @@ public static Integer extractKeySize(String sslCipherSuite) { keySizesCache.put(sslCipherSuite, keySize); } } - } - - return keySize; - } - - /** - * Returns the SSL context factory. It first looks for a "sslContextFactory" - * attribute (instance), then for a "sslContextFactory" parameter (class name to - * instantiate). - * - * @param helper The helper to use. - * - * @return The SSL context factory. - */ - public static SslContextFactory getSslContextFactory(RestletHelper helper) { - - SslContextFactory result = (SslContextFactory) ((helper.getContext() == null) - ? null - : helper.getContext().getAttributes().get("sslContextFactory")); - - if (result == null) { - String[] sslContextFactoryNames = helper.getHelpedParameters().getValuesArray("sslContextFactory"); - - if (sslContextFactoryNames != null) { - for (String sslContextFactoryName : sslContextFactoryNames) { - if ((result == null) && (sslContextFactoryName != null)) { - try { - Class sslContextFactoryClass = Class - .forName(sslContextFactoryName).asSubclass(SslContextFactory.class); - result = sslContextFactoryClass.getDeclaredConstructor().newInstance(); - result.init(helper.getHelpedParameters()); - } catch (ClassNotFoundException e) { - Context.getCurrentLogger().log(Level.WARNING, - "Unable to find SslContextFactory class: " + sslContextFactoryName, e); - } catch (ClassCastException e) { - Context.getCurrentLogger().log(Level.WARNING, - "Class " + sslContextFactoryName + " does not implement SslContextFactory", e); - } catch (InstantiationException | NoSuchMethodException e) { - Context.getCurrentLogger().log(Level.WARNING, "Could not instantiate class " - + sslContextFactoryName + " with default constructor", e); - } catch (IllegalAccessException e) { - Context.getCurrentLogger().log(Level.WARNING, - "Illegal access when instantiating class " + sslContextFactoryName, e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } - } - } - } - - if (result == null) { - result = new DefaultSslContextFactory(); - result.init(helper.getHelpedParameters()); - } - - return result; - } - - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e. it isn't instantiable and extensible. - */ - private SslUtils() { - } + } + + return keySize; + } + + /** + * Returns the SSL context factory. It first looks for a "sslContextFactory" attribute + * (instance), then for a "sslContextFactory" parameter (class name to instantiate). + * + * @param helper The helper to use. + * @return The SSL context factory. + */ + public static SslContextFactory getSslContextFactory(RestletHelper helper) { + + SslContextFactory result = getSslContextFactoryFromContext(helper.getContext()); + + if (result == null) { + final Series helpedParameters = helper.getHelpedParameters(); + result = getSslContextFactoryFromHelpedParameters(helpedParameters); + } + + if (result == null) { + result = getDefaultSslContextFactory(helper); + } + + return result; + } + + private static SslContextFactory getDefaultSslContextFactory(final RestletHelper helper) { + SslContextFactory result; + result = new DefaultSslContextFactory(); + result.init(helper.getHelpedParameters()); + return result; + } + + private static SslContextFactory getSslContextFactoryFromHelpedParameters( + final Series helpedParameters) { + + String[] sslContextFactoryNames = helpedParameters.getValuesArray("sslContextFactory"); + if (sslContextFactoryNames == null) { + return null; + } + + SslContextFactory result = null; + for (String sslContextFactoryName : sslContextFactoryNames) { + if (result == null && sslContextFactoryName != null) { + result = getSslContextFactoryByClassName(helpedParameters, sslContextFactoryName); + } + } + return result; + } + + private static SslContextFactory getSslContextFactoryByClassName( + final Series helpedParameters, final String sslContextFactoryName) { + try { + Class sslContextFactoryClass = + Class.forName(sslContextFactoryName).asSubclass(SslContextFactory.class); + SslContextFactory result = + sslContextFactoryClass.getDeclaredConstructor().newInstance(); + result.init(helpedParameters); + return result; + } catch (ClassNotFoundException e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + e, + () -> + "Unable to find SslContextFactory class: " + + sslContextFactoryName); + } catch (ClassCastException e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + e, + () -> + "Class " + + sslContextFactoryName + + " does not implement SslContextFactory"); + } catch (InstantiationException | NoSuchMethodException e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + e, + () -> + "Could not instantiate class " + + sslContextFactoryName + + " with default constructor"); + } catch (IllegalAccessException e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + e, + () -> + "Illegal access when instantiating class " + + sslContextFactoryName); + } catch (InvocationTargetException e) { + throw new RuntimeException( + "Cannot instantiate SslContextFactory class: " + sslContextFactoryName, e); + } + + return null; + } + + private static SslContextFactory getSslContextFactoryFromContext(final Context context) { + return context == null + ? null + : (SslContextFactory) context.getAttributes().get("sslContextFactory"); + } + + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private SslUtils() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslContextSpi.java b/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslContextSpi.java index 074eabbff9..5619d54846 100644 --- a/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslContextSpi.java +++ b/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslContextSpi.java @@ -1,124 +1,133 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.ssl; -import javax.net.ssl.*; import java.security.KeyManagementException; import java.security.SecureRandom; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLContextSpi; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; /** - * Default SSL context SPI capable or setting additional properties on the - * created SSL engines and socket factories. - * + * Default SSL context SPI capable or setting additional properties on the created SSL engines and + * socket factories. + * * @author Jerome Louvel */ public class WrapperSslContextSpi extends SSLContextSpi { - /** The parent SSL context factory. */ - private final DefaultSslContextFactory contextFactory; - - /** The wrapped SSL context. */ - private final SSLContext wrappedContext; - - /** - * Constructor. - * - * @param contextFactory The parent SSL context factory. - * @param wrappedContext The wrapped SSL context. - */ - public WrapperSslContextSpi(DefaultSslContextFactory contextFactory, SSLContext wrappedContext) { - this.contextFactory = contextFactory; - this.wrappedContext = wrappedContext; - } - - @Override - protected SSLEngine engineCreateSSLEngine() { - SSLEngine result = getWrappedContext().createSSLEngine(); - initEngine(result); - return result; - } - - @Override - protected SSLEngine engineCreateSSLEngine(String peerHost, int peerPort) { - SSLEngine result = getWrappedContext().createSSLEngine(peerHost, peerPort); - initEngine(result); - return result; - } - - @Override - protected SSLSessionContext engineGetClientSessionContext() { - return getWrappedContext().getClientSessionContext(); - } - - @Override - protected SSLSessionContext engineGetServerSessionContext() { - return getWrappedContext().getServerSessionContext(); - } - - @Override - protected SSLServerSocketFactory engineGetServerSocketFactory() { - return new WrapperSslServerSocketFactory(getContextFactory(), getWrappedContext().getServerSocketFactory()); - } - - @Override - protected SSLSocketFactory engineGetSocketFactory() { - return new WrapperSslSocketFactory(getContextFactory(), getWrappedContext().getSocketFactory()); - } - - @Override - protected void engineInit(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws KeyManagementException { - getWrappedContext().init(km, tm, random); - } - - /** - * Returns the parent SSL context factory. - * - * @return The parent SSL context factory. - */ - protected DefaultSslContextFactory getContextFactory() { - return contextFactory; - } - - /** - * Returns the wrapped SSL context. - * - * @return The wrapped SSL context. - */ - protected SSLContext getWrappedContext() { - return wrappedContext; - } - - /** - * Initializes the SSL engine with additional parameters from the SSL context - * factory. - * - * @param sslEngine The SSL engine to initialize. - */ - protected void initEngine(SSLEngine sslEngine) { - if (getContextFactory().isNeedClientAuthentication()) { - sslEngine.setNeedClientAuth(true); - } else if (getContextFactory().isWantClientAuthentication()) { - sslEngine.setWantClientAuth(true); - } - - if ((getContextFactory().getEnabledCipherSuites() != null) - || (getContextFactory().getDisabledCipherSuites() != null)) { - sslEngine.setEnabledCipherSuites( - getContextFactory().getSelectedCipherSuites(sslEngine.getSupportedCipherSuites())); - } - - if ((getContextFactory().getEnabledProtocols() != null) - || (getContextFactory().getDisabledProtocols() != null)) { - sslEngine.setEnabledProtocols( - getContextFactory().getSelectedSslProtocols(sslEngine.getSupportedProtocols())); - } - } - + /** The parent SSL context factory. */ + private final DefaultSslContextFactory contextFactory; + + /** The wrapped SSL context. */ + private final SSLContext wrappedContext; + + /** + * Constructor. + * + * @param contextFactory The parent SSL context factory. + * @param wrappedContext The wrapped SSL context. + */ + public WrapperSslContextSpi( + DefaultSslContextFactory contextFactory, SSLContext wrappedContext) { + this.contextFactory = contextFactory; + this.wrappedContext = wrappedContext; + } + + @Override + protected SSLEngine engineCreateSSLEngine() { + SSLEngine result = getWrappedContext().createSSLEngine(); + initEngine(result); + return result; + } + + @Override + protected SSLEngine engineCreateSSLEngine(String peerHost, int peerPort) { + SSLEngine result = getWrappedContext().createSSLEngine(peerHost, peerPort); + initEngine(result); + return result; + } + + @Override + protected SSLSessionContext engineGetClientSessionContext() { + return getWrappedContext().getClientSessionContext(); + } + + @Override + protected SSLSessionContext engineGetServerSessionContext() { + return getWrappedContext().getServerSessionContext(); + } + + @Override + protected SSLServerSocketFactory engineGetServerSocketFactory() { + return new WrapperSslServerSocketFactory( + getContextFactory(), getWrappedContext().getServerSocketFactory()); + } + + @Override + protected SSLSocketFactory engineGetSocketFactory() { + return new WrapperSslSocketFactory( + getContextFactory(), getWrappedContext().getSocketFactory()); + } + + @Override + protected void engineInit(KeyManager[] km, TrustManager[] tm, SecureRandom random) + throws KeyManagementException { + getWrappedContext().init(km, tm, random); + } + + /** + * Returns the parent SSL context factory. + * + * @return The parent SSL context factory. + */ + protected DefaultSslContextFactory getContextFactory() { + return contextFactory; + } + + /** + * Returns the wrapped SSL context. + * + * @return The wrapped SSL context. + */ + protected SSLContext getWrappedContext() { + return wrappedContext; + } + + /** + * Initializes the SSL engine with additional parameters from the SSL context factory. + * + * @param sslEngine The SSL engine to initialize. + */ + protected void initEngine(SSLEngine sslEngine) { + if (getContextFactory().isNeedClientAuthentication()) { + sslEngine.setNeedClientAuth(true); + } else if (getContextFactory().isWantClientAuthentication()) { + sslEngine.setWantClientAuth(true); + } + + if ((getContextFactory().getEnabledCipherSuites() != null) + || (getContextFactory().getDisabledCipherSuites() != null)) { + sslEngine.setEnabledCipherSuites( + getContextFactory() + .getSelectedCipherSuites(sslEngine.getSupportedCipherSuites())); + } + + if ((getContextFactory().getEnabledProtocols() != null) + || (getContextFactory().getDisabledProtocols() != null)) { + sslEngine.setEnabledProtocols( + getContextFactory().getSelectedSslProtocols(sslEngine.getSupportedProtocols())); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslServerSocketFactory.java b/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslServerSocketFactory.java index 10846de58b..7c84788352 100644 --- a/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslServerSocketFactory.java +++ b/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslServerSocketFactory.java @@ -1,126 +1,130 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.ssl; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLServerSocketFactory; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; /** - * SSL server socket factory that wraps the default one to do extra - * initialization. Configures the cipher suites and the SSL certificate request. - * + * SSL server socket factory that wraps the default one to do extra initialization. Configures the + * cipher suites and the SSL certificate request. + * * @author Jerome Louvel */ public class WrapperSslServerSocketFactory extends SSLServerSocketFactory { - /** The parent SSL context factory. */ - private final DefaultSslContextFactory contextFactory; - - /** The wrapped SSL server socket factory. */ - private final SSLServerSocketFactory wrappedSocketFactory; - - /** - * Constructor. - * - * @param contextFactory The parent SSL context factory. - * @param wrappedSocketFactory The wrapped SSL server socket factory. - */ - public WrapperSslServerSocketFactory(DefaultSslContextFactory contextFactory, - SSLServerSocketFactory wrappedSocketFactory) { - this.wrappedSocketFactory = wrappedSocketFactory; - this.contextFactory = contextFactory; - } - - @Override - public ServerSocket createServerSocket() throws IOException { - SSLServerSocket result = (SSLServerSocket) getWrappedSocketFactory().createServerSocket(); - return initSslServerSocket(result); - } - - @Override - public ServerSocket createServerSocket(int port) throws IOException { - SSLServerSocket result = (SSLServerSocket) getWrappedSocketFactory().createServerSocket(port); - return initSslServerSocket(result); - } - - @Override - public ServerSocket createServerSocket(int port, int backLog) throws IOException { - SSLServerSocket result = (SSLServerSocket) getWrappedSocketFactory().createServerSocket(port, backLog); - return initSslServerSocket(result); - } - - @Override - public ServerSocket createServerSocket(int port, int backLog, InetAddress ifAddress) throws IOException { - SSLServerSocket result = (SSLServerSocket) getWrappedSocketFactory().createServerSocket(port, backLog, - ifAddress); - return initSslServerSocket(result); - } - - /** - * Returns the parent SSL context factory. - * - * @return The parent SSL context factory. - */ - public DefaultSslContextFactory getContextFactory() { - return contextFactory; - } - - @Override - public String[] getDefaultCipherSuites() { - return getWrappedSocketFactory().getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return getWrappedSocketFactory().getSupportedCipherSuites(); - } - - /** - * Returns the wrapped SSL server socket factory. - * - * @return The wrapped SSL server socket factory. - */ - public SSLServerSocketFactory getWrappedSocketFactory() { - return wrappedSocketFactory; - } - - /** - * Initializes the SSL server socket. Configures the certificate request (need - * or want) and the enabled cipher suites. - * - * @param sslServerSocket The server socket to initialize. - * @return The initialized server socket. - */ - protected SSLServerSocket initSslServerSocket(SSLServerSocket sslServerSocket) { - if (getContextFactory().isNeedClientAuthentication()) { - sslServerSocket.setNeedClientAuth(true); - } else if (getContextFactory().isWantClientAuthentication()) { - sslServerSocket.setWantClientAuth(true); - } - - if ((getContextFactory().getEnabledCipherSuites() != null) - || (getContextFactory().getDisabledCipherSuites() != null)) { - sslServerSocket.setEnabledCipherSuites( - getContextFactory().getSelectedCipherSuites(sslServerSocket.getSupportedCipherSuites())); - } - - if ((getContextFactory().getEnabledProtocols() != null) - || (getContextFactory().getDisabledProtocols() != null)) { - sslServerSocket.setEnabledProtocols( - getContextFactory().getSelectedSslProtocols(sslServerSocket.getSupportedProtocols())); - } - - return sslServerSocket; - } - + /** The parent SSL context factory. */ + private final DefaultSslContextFactory contextFactory; + + /** The wrapped SSL server socket factory. */ + private final SSLServerSocketFactory wrappedSocketFactory; + + /** + * Constructor. + * + * @param contextFactory The parent SSL context factory. + * @param wrappedSocketFactory The wrapped SSL server socket factory. + */ + public WrapperSslServerSocketFactory( + DefaultSslContextFactory contextFactory, SSLServerSocketFactory wrappedSocketFactory) { + this.wrappedSocketFactory = wrappedSocketFactory; + this.contextFactory = contextFactory; + } + + @Override + public ServerSocket createServerSocket() throws IOException { + SSLServerSocket result = (SSLServerSocket) getWrappedSocketFactory().createServerSocket(); + return initSslServerSocket(result); + } + + @Override + public ServerSocket createServerSocket(int port) throws IOException { + SSLServerSocket result = + (SSLServerSocket) getWrappedSocketFactory().createServerSocket(port); + return initSslServerSocket(result); + } + + @Override + public ServerSocket createServerSocket(int port, int backLog) throws IOException { + SSLServerSocket result = + (SSLServerSocket) getWrappedSocketFactory().createServerSocket(port, backLog); + return initSslServerSocket(result); + } + + @Override + public ServerSocket createServerSocket(int port, int backLog, InetAddress ifAddress) + throws IOException { + SSLServerSocket result = + (SSLServerSocket) + getWrappedSocketFactory().createServerSocket(port, backLog, ifAddress); + return initSslServerSocket(result); + } + + /** + * Returns the parent SSL context factory. + * + * @return The parent SSL context factory. + */ + public DefaultSslContextFactory getContextFactory() { + return contextFactory; + } + + @Override + public String[] getDefaultCipherSuites() { + return getWrappedSocketFactory().getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return getWrappedSocketFactory().getSupportedCipherSuites(); + } + + /** + * Returns the wrapped SSL server socket factory. + * + * @return The wrapped SSL server socket factory. + */ + public SSLServerSocketFactory getWrappedSocketFactory() { + return wrappedSocketFactory; + } + + /** + * Initializes the SSL server socket. Configures the certificate request (need or want) and the + * enabled cipher suites. + * + * @param sslServerSocket The server socket to initialize. + * @return The initialized server socket. + */ + protected SSLServerSocket initSslServerSocket(SSLServerSocket sslServerSocket) { + if (getContextFactory().isNeedClientAuthentication()) { + sslServerSocket.setNeedClientAuth(true); + } else if (getContextFactory().isWantClientAuthentication()) { + sslServerSocket.setWantClientAuth(true); + } + + if ((getContextFactory().getEnabledCipherSuites() != null) + || (getContextFactory().getDisabledCipherSuites() != null)) { + sslServerSocket.setEnabledCipherSuites( + getContextFactory() + .getSelectedCipherSuites(sslServerSocket.getSupportedCipherSuites())); + } + + if ((getContextFactory().getEnabledProtocols() != null) + || (getContextFactory().getDisabledProtocols() != null)) { + sslServerSocket.setEnabledProtocols( + getContextFactory() + .getSelectedSslProtocols(sslServerSocket.getSupportedProtocols())); + } + + return sslServerSocket; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslSocketFactory.java b/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslSocketFactory.java index 8e457268d2..2511a3b9e6 100644 --- a/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslSocketFactory.java +++ b/org.restlet/src/main/java/org/restlet/engine/ssl/WrapperSslSocketFactory.java @@ -1,138 +1,144 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.ssl; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; -import java.net.UnknownHostException; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; /** - * SSL socket factory that wraps the default one to do extra initialization. - * Configures the cipher suites and the SSL certificate request. - * + * SSL socket factory that wraps the default one to do extra initialization. Configures the cipher + * suites and the SSL certificate request. + * * @author Jerome Louvel */ public class WrapperSslSocketFactory extends SSLSocketFactory { - /** The parent SSL context factory. */ - private final DefaultSslContextFactory contextFactory; - - /** The wrapped SSL server socket factory. */ - private final SSLSocketFactory wrappedSocketFactory; - - /** - * Constructor. - * - * @param contextFactory The parent SSL context factory. - * @param wrappedSocketFactory The wrapped SSL server socket factory. - */ - public WrapperSslSocketFactory(DefaultSslContextFactory contextFactory, SSLSocketFactory wrappedSocketFactory) { - this.wrappedSocketFactory = wrappedSocketFactory; - this.contextFactory = contextFactory; - } - - @Override - public Socket createSocket() throws IOException { - SSLSocket result = (SSLSocket) getWrappedSocketFactory().createSocket(); - return initSslSocket(result); - } - - @Override - public Socket createSocket(InetAddress host, int port) throws IOException { - SSLSocket result = (SSLSocket) getWrappedSocketFactory().createSocket(host, port); - return initSslSocket(result); - } - - @Override - public Socket createSocket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException { - SSLSocket result = (SSLSocket) getWrappedSocketFactory().createSocket(host, port, localAddress, localPort); - return initSslSocket(result); - } - - @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - SSLSocket result = (SSLSocket) getWrappedSocketFactory().createSocket(s, host, port, autoClose); - return initSslSocket(result); - } - - @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { - SSLSocket result = (SSLSocket) getWrappedSocketFactory().createSocket(host, port); - return initSslSocket(result); - } - - @Override - public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) - throws IOException, UnknownHostException { - SSLSocket result = (SSLSocket) getWrappedSocketFactory().createSocket(host, port, localAddress, localPort); - return initSslSocket(result); - } - - /** - * Returns the parent SSL context factory. - * - * @return The parent SSL context factory. - */ - public DefaultSslContextFactory getContextFactory() { - return contextFactory; - } - - @Override - public String[] getDefaultCipherSuites() { - return getWrappedSocketFactory().getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return getWrappedSocketFactory().getSupportedCipherSuites(); - } - - /** - * Returns the wrapped SSL socket factory. - * - * @return The wrapped SSL socket factory. - */ - public SSLSocketFactory getWrappedSocketFactory() { - return wrappedSocketFactory; - } - - /** - * Initializes the SSL socket. Configures the certificate request (need or want) - * and the enabled cipher suites. - * - * @param sslSocket The socket to initialize. - * @return The initialized socket. - */ - protected SSLSocket initSslSocket(SSLSocket sslSocket) { - if (getContextFactory().isNeedClientAuthentication()) { - sslSocket.setNeedClientAuth(true); - } else if (getContextFactory().isWantClientAuthentication()) { - sslSocket.setWantClientAuth(true); - } - - if ((getContextFactory().getEnabledCipherSuites() != null) - || (getContextFactory().getDisabledCipherSuites() != null)) { - sslSocket.setEnabledCipherSuites( - getContextFactory().getSelectedCipherSuites(sslSocket.getSupportedCipherSuites())); - } - - if ((getContextFactory().getEnabledProtocols() != null) - || (getContextFactory().getDisabledProtocols() != null)) { - sslSocket.setEnabledProtocols( - getContextFactory().getSelectedSslProtocols(sslSocket.getSupportedProtocols())); - } - - return sslSocket; - } - + /** The parent SSL context factory. */ + private final DefaultSslContextFactory contextFactory; + + /** The wrapped SSL server socket factory. */ + private final SSLSocketFactory wrappedSocketFactory; + + /** + * Constructor. + * + * @param contextFactory The parent SSL context factory. + * @param wrappedSocketFactory The wrapped SSL server socket factory. + */ + public WrapperSslSocketFactory( + DefaultSslContextFactory contextFactory, SSLSocketFactory wrappedSocketFactory) { + this.wrappedSocketFactory = wrappedSocketFactory; + this.contextFactory = contextFactory; + } + + @Override + public Socket createSocket() throws IOException { + SSLSocket result = (SSLSocket) getWrappedSocketFactory().createSocket(); + return initSslSocket(result); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + SSLSocket result = (SSLSocket) getWrappedSocketFactory().createSocket(host, port); + return initSslSocket(result); + } + + @Override + public Socket createSocket(InetAddress host, int port, InetAddress localAddress, int localPort) + throws IOException { + SSLSocket result = + (SSLSocket) + getWrappedSocketFactory().createSocket(host, port, localAddress, localPort); + return initSslSocket(result); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) + throws IOException { + SSLSocket result = + (SSLSocket) getWrappedSocketFactory().createSocket(s, host, port, autoClose); + return initSslSocket(result); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + SSLSocket result = (SSLSocket) getWrappedSocketFactory().createSocket(host, port); + return initSslSocket(result); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) + throws IOException { + SSLSocket result = + (SSLSocket) + getWrappedSocketFactory().createSocket(host, port, localAddress, localPort); + return initSslSocket(result); + } + + /** + * Returns the parent SSL context factory. + * + * @return The parent SSL context factory. + */ + public DefaultSslContextFactory getContextFactory() { + return contextFactory; + } + + @Override + public String[] getDefaultCipherSuites() { + return getWrappedSocketFactory().getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return getWrappedSocketFactory().getSupportedCipherSuites(); + } + + /** + * Returns the wrapped SSL socket factory. + * + * @return The wrapped SSL socket factory. + */ + public SSLSocketFactory getWrappedSocketFactory() { + return wrappedSocketFactory; + } + + /** + * Initializes the SSL socket. Configures the certificate request (need or want) and the enabled + * cipher suites. + * + * @param sslSocket The socket to initialize. + * @return The initialized socket. + */ + protected SSLSocket initSslSocket(SSLSocket sslSocket) { + if (getContextFactory().isNeedClientAuthentication()) { + sslSocket.setNeedClientAuth(true); + } else if (getContextFactory().isWantClientAuthentication()) { + sslSocket.setWantClientAuth(true); + } + + if ((getContextFactory().getEnabledCipherSuites() != null) + || (getContextFactory().getDisabledCipherSuites() != null)) { + sslSocket.setEnabledCipherSuites( + getContextFactory() + .getSelectedCipherSuites(sslSocket.getSupportedCipherSuites())); + } + + if ((getContextFactory().getEnabledProtocols() != null) + || (getContextFactory().getDisabledProtocols() != null)) { + sslSocket.setEnabledProtocols( + getContextFactory().getSelectedSslProtocols(sslSocket.getSupportedProtocols())); + } + + return sslSocket; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/ssl/package-info.java b/org.restlet/src/main/java/org/restlet/engine/ssl/package-info.java new file mode 100644 index 0000000000..dc684c1024 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/ssl/package-info.java @@ -0,0 +1,6 @@ +/** + * Support SSL and TLS. + * + * @since Restlet 2.2 + */ +package org.restlet.engine.ssl; diff --git a/org.restlet/src/main/java/org/restlet/engine/ssl/package.html b/org.restlet/src/main/java/org/restlet/engine/ssl/package.html deleted file mode 100644 index 44799a23d1..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/ssl/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Support SSL and TLS. -

-@since Restlet 2.2 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/engine/util/AlphaNumericComparator.java b/org.restlet/src/main/java/org/restlet/engine/util/AlphaNumericComparator.java index fa12353c24..b3d36e5719 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/AlphaNumericComparator.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/AlphaNumericComparator.java @@ -1,123 +1,113 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; /** * Optimized public-domain implementation of a Java alphanumeric sort. - *

* - * This implementation uses a single comparison pass over the characters in a - * CharSequence and returns as soon as a differing character is found, unless - * the difference occurs in a series of numeric characters, in which case that - * series is followed to its end. Numeric series of equal length are compared - * numerically, that is, according to the most significant (leftmost) differing + *

This implementation uses a single comparison pass over the characters in a CharSequence and + * returns as soon as a differing character is found, unless the difference occurs in a series of + * numeric characters, in which case that series is followed to its end. Numeric series of equal + * length are compared numerically, that is, according to the most significant (leftmost) differing * digit. Series of unequal length are compared by their length. - *

* - * This implementation appears to be 2-5 times faster than alphanumeric - * comparators based on substring analysis, with a lighter memory - * footprint. - *

+ *

This implementation appears to be 2-5 times faster than alphanumeric comparators based on + * substring analysis, with a lighter memory footprint. * - * This alphanumeric comparator has approximately 20%-50% the performance of the - * lexical String.compareTo() operation. Character sequences without numeric - * data are compared more quickly. - *

+ *

This alphanumeric comparator has approximately 20%-50% the performance of the lexical + * String.compareTo() operation. Character sequences without numeric data are compared more quickly. * - * Dedicated to the public domain by the original author: - * Public Domain List + *

Dedicated to the public domain by the original author: Public Domain List * - * @author Rob Heittman, Solertium - * Corporation + * @author Rob Heittman, Solertium Corporation */ public class AlphaNumericComparator extends AlphabeticalComparator { - private static final long serialVersionUID = 1L; - - @Override - public int compare(final String uri0, final String uri1) { - int ptr = 0; - int msd = 0; - int diff = 0; - char a, b; - - final int llength = uri0.length(); - final int rlength = uri1.length(); - final int min = Math.min(rlength, llength); - - boolean rAtEnd, rHasNoMoreDigits; - - while (ptr < min) { - a = uri0.charAt(ptr); - b = uri1.charAt(ptr); - diff = a - b; - - if ((a >= '9') || (b >= '9') || (a <= '0') || (b <= '0')) { - if (diff != 0) { - return diff; - } - - msd = 0; - } else { - if (msd == 0) { - msd = diff; - } - - rAtEnd = rlength - ptr < 2; - - if (llength - ptr < 2) { - if (rAtEnd) { - return msd; - } - - if (!isNotDigit(a) && !isNotDigit(b)) - return diff; - - return -1; - } - - if (rAtEnd) { - if (!isNotDigit(a) && !isNotDigit(b)) - return diff; - - return -1; - } - - rHasNoMoreDigits = isNotDigit(uri1.charAt(ptr + 1)); - - if (isNotDigit(uri0.charAt(ptr + 1))) { - if (rHasNoMoreDigits && (msd != 0)) { - return msd; - } - - if (!rHasNoMoreDigits) { - return -1; - } - } else { - if (rHasNoMoreDigits) { - return 1; - } - } - } - ptr++; - } - return llength - rlength; - } - - /** - * Indicates if the character is a digit. - * - * @param x The character to test. - * @return True if the character is a digit. - */ - protected boolean isNotDigit(final char x) { - return (x > '9') || (x < '0'); - } - + private static final long serialVersionUID = 1L; + + @Override + public int compare(final String uri0, final String uri1) { + int ptr = 0; + int msd = 0; + int diff = 0; + char a; + char b; + + final int lLength = uri0.length(); + final int rLength = uri1.length(); + final int min = Math.min(rLength, lLength); + + boolean rAtEnd; + boolean rHasNoMoreDigits; + + while (ptr < min) { + a = uri0.charAt(ptr); + b = uri1.charAt(ptr); + diff = a - b; + + if ((a >= '9') || (b >= '9') || (a <= '0') || (b <= '0')) { + if (diff != 0) { + return diff; + } + + msd = 0; + } else { + if (msd == 0) { + msd = diff; + } + + rAtEnd = rLength - ptr < 2; + + if (lLength - ptr < 2) { + if (rAtEnd) { + return msd; + } + + if (!isNotDigit(a) && !isNotDigit(b)) return diff; + + return -1; + } + + if (rAtEnd) { + if (!isNotDigit(a) && !isNotDigit(b)) return diff; + + return -1; + } + + rHasNoMoreDigits = isNotDigit(uri1.charAt(ptr + 1)); + + if (isNotDigit(uri0.charAt(ptr + 1))) { + if (rHasNoMoreDigits && (msd != 0)) { + return msd; + } + + if (!rHasNoMoreDigits) { + return -1; + } + } else { + if (rHasNoMoreDigits) { + return 1; + } + } + } + ptr++; + } + return lLength - rLength; + } + + /** + * Indicates if the character is a digit. + * + * @param x The character to test. + * @return True if the character is a digit. + */ + protected boolean isNotDigit(final char x) { + return (x > '9') || (x < '0'); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/AlphabeticalComparator.java b/org.restlet/src/main/java/org/restlet/engine/util/AlphabeticalComparator.java index 760e574637..05106a10c3 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/AlphabeticalComparator.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/AlphabeticalComparator.java @@ -1,60 +1,58 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; -import org.restlet.data.Reference; - import java.io.Serializable; import java.util.Comparator; +import org.restlet.data.Reference; /** * Allows sorting the list of references set by the resource. - * + * * @author Jerome Louvel */ public class AlphabeticalComparator implements Comparator, Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - /** - * Compares two references. - * - * @param ref0 The first reference. - * @param ref1 The second reference. - * @return The comparison result. - * @see Comparator - */ - public int compare(Reference ref0, Reference ref1) { - final boolean bRep0Null = (ref0.getIdentifier() == null); - final boolean bRep1Null = (ref1.getIdentifier() == null); + /** + * Compares two references. + * + * @param ref0 The first reference. + * @param ref1 The second reference. + * @return The comparison result. + * @see Comparator + */ + public int compare(Reference ref0, Reference ref1) { + final boolean bRep0Null = (ref0.getIdentifier() == null); + final boolean bRep1Null = (ref1.getIdentifier() == null); - if (bRep0Null && bRep1Null) { - return 0; - } - if (bRep0Null) { - return -1; - } - if (bRep1Null) { - return 1; - } - return compare(ref0.toString(false, false), ref1.toString(false, false)); - } + if (bRep0Null && bRep1Null) { + return 0; + } + if (bRep0Null) { + return -1; + } + if (bRep1Null) { + return 1; + } + return compare(ref0.toString(false, false), ref1.toString(false, false)); + } - /** - * Compares two strings. - * - * @param str0 The first string. - * @param str1 The second string. - * @return The comparison result. - * @see Comparator - */ - public int compare(final String str0, final String str1) { - return str0.compareTo(str1); - } + /** + * Compares two strings. + * + * @param str0 The first string. + * @param str1 The second string. + * @return The comparison result. + * @see Comparator + */ + public int compare(final String str0, final String str1) { + return str0.compareTo(str1); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/BeanInfoUtils.java b/org.restlet/src/main/java/org/restlet/engine/util/BeanInfoUtils.java index 5906558b73..f197179f7d 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/BeanInfoUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/BeanInfoUtils.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import java.beans.BeanInfo; @@ -17,43 +16,46 @@ /** * Utilities to get the {@link BeanInfo} of a class. - * + * * @author Manuel Boillod */ public class BeanInfoUtils { - /** BeanInfo cache. */ - private static final ConcurrentMap, BeanInfo> cache = new ConcurrentHashMap, BeanInfo>(); - - /** - * Get a BeanInfo from cache or create it. Stop introspection to {@link Object} - * or {@link Throwable} if the class is a subtype of {@link Throwable} - * - * @param clazz The class - * @return BeanInfo of the class - */ - public static BeanInfo getBeanInfo(Class clazz) { - BeanInfo result = cache.get(clazz); - - if (result == null) { - // Inspect the class itself for annotations - - Class stopClass = Throwable.class.isAssignableFrom(clazz) ? Throwable.class : Object.class; - try { - result = Introspector.getBeanInfo(clazz, stopClass, Introspector.IGNORE_ALL_BEANINFO); - } catch (IntrospectionException e) { - throw new RuntimeException("Could not get BeanInfo of class " + clazz.getName(), e); - } - - // Put the list in the cache if no one was previously present - BeanInfo prev = cache.putIfAbsent(clazz, result); - - if (prev != null) { - // Reuse the previous entry - result = prev; - } - } - - return result; - } + /** BeanInfo cache. */ + private static final ConcurrentMap, BeanInfo> cache = new ConcurrentHashMap<>(); + + /** + * Get a BeanInfo from the cache or create it. Stop introspection to {@link Object} or {@link + * Throwable} if the class is a subtype of {@link Throwable} + * + * @param clazz The class + * @return BeanInfo of the class + */ + public static BeanInfo getBeanInfo(Class clazz) { + BeanInfo result = cache.get(clazz); + + if (result == null) { + // Inspect the class itself for annotations + + Class stopClass = + Throwable.class.isAssignableFrom(clazz) ? Throwable.class : Object.class; + try { + result = + Introspector.getBeanInfo( + clazz, stopClass, Introspector.IGNORE_ALL_BEANINFO); + } catch (IntrospectionException e) { + throw new RuntimeException("Could not get BeanInfo of class " + clazz.getName(), e); + } + + // Put the list in the cache if no one was previously present + BeanInfo prev = cache.putIfAbsent(clazz, result); + + if (prev != null) { + // Reuse the previous entry + result = prev; + } + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/CallResolver.java b/org.restlet/src/main/java/org/restlet/engine/util/CallResolver.java index 090288c982..09a1ab7321 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/CallResolver.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/CallResolver.java @@ -1,14 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; +import static org.restlet.engine.util.DateUtils.FORMAT_RFC_1123; + +import java.util.Date; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.ChallengeResponse; @@ -18,226 +20,252 @@ import org.restlet.representation.Representation; import org.restlet.util.Resolver; -import java.util.Date; - -import static org.restlet.engine.util.DateUtils.FORMAT_RFC_1123; - /** * Resolves variable values based on a request and a response. - * + * * @author Jerome Louvel */ public class CallResolver extends Resolver { - /** The request to use as a model. */ - private final Request request; - - /** The response to use as a model. */ - private final Response response; - - /** - * Constructor. - * - * @param request The request to use as a model. - * @param response The response to use as a model. - */ - public CallResolver(Request request, Response response) { - this.request = request; - this.response = response; - } - - @Override - public Object resolve(String variableName) { - // Check for a matching response attribute - Object result = (this.response != null) ? this.response.getAttributes().get(variableName) : null; - if (result != null) { - return result; - } - - // Check for a matching request attribute - result = (this.request != null) ? this.request.getAttributes().get(variableName) : null; - if (result != null) { - return result; - } - - if (variableName == null) { - return null; - } - - // Check for a matching request or response property - if (this.request != null) { - ChallengeResponse cr = this.request.getChallengeResponse(); - Representation entity = this.request.getEntity(); - - switch (variableName) { - case "c": - return Boolean.toString(this.request.isConfidential()); - case "cia": - return this.request.getClientInfo().getAddress(); - case "ciua": - return this.request.getClientInfo().getUpstreamAddress(); - case "cig": - return this.request.getClientInfo().getAgent(); - case "cri": - return (cr != null) ? cr.getIdentifier() : null; - case "crs": - return (cr != null && cr.getScheme() != null) ? cr.getScheme().getTechnicalName() : null; - case "d": - return DateUtils.format(new Date(), FORMAT_RFC_1123.get(0)); - case "ecs": - return (entity != null && entity.getCharacterSet() != null) ? entity.getCharacterSet().getName() : null; - case "ee": - return getEncodingsAsString(entity); - case "eed": - return getExpirationDateAsString(entity); - case "el": - return getLanguagesAsString(entity); - case "emd": - return getModificationDateAsString(entity); - case "emt": - return (entity != null && entity.getMediaType() != null) ? entity.getMediaType().getName() : null; - case "es": - return (entity != null && entity.getSize() != -1) ? Long.toString(entity.getSize()) : null; - case "et": - return (entity != null && entity.getTag() != null) ? entity.getTag().getName() : null; - case "m": - return (this.request.getMethod() != null) ? this.request.getMethod().getName() : null; - case "p": - return (this.request.getProtocol() != null) ? this.request.getProtocol().getName() : null; - default: - if (variableName.startsWith("o")) { - return getReferenceContent(variableName.substring(1), this.request.getRootRef()); - } else if (variableName.startsWith("f")) { - return getReferenceContent(variableName.substring(1), this.request.getReferrerRef()); - } else if (variableName.startsWith("h")) { - return getReferenceContent(variableName.substring(1), this.request.getHostRef()); - } else if (variableName.startsWith("r")) { - return getReferenceContent(variableName.substring(1), this.request.getResourceRef()); - } - break; - } - } - - if (this.response != null) { - Representation entity = this.response.getEntity(); - Status status = this.response.getStatus(); - ServerInfo serverInfo = this.response.getServerInfo(); - - switch (variableName) { - case "ECS": - return (entity != null && entity.getCharacterSet() != null) ? entity.getCharacterSet().getName() : null; - case "EE": - return getEncodingsAsString(entity); - case "EED": - return getExpirationDateAsString(entity); - case "EL": - return getLanguagesAsString(entity); - case "EMD": - return getModificationDateAsString(entity); - case "EMT": - return (entity != null && entity.getMediaType() != null) ? entity.getMediaType().getName() : null; - case "ES": - return (entity != null && entity.getSize() != -1) ? Long.toString(entity.getSize()) : null; - case "ET": - return (entity != null && entity.getTag() != null) ? entity.getTag().getName() : null; - case "S": - return (status != null) ? Integer.toString(status.getCode()) : null; - case "SIA": - return serverInfo.getAddress(); - case "SIG": - return serverInfo.getAgent(); - case "SIP": - return (serverInfo.getPort() != -1) ? Integer.toString(serverInfo.getPort()) : null; - default: - if (variableName.startsWith("R")) { - return getReferenceContent(variableName.substring(1), this.response.getLocationRef()); - } - } - } - - return null; - } - - /** - * Returns the content corresponding to a reference property. - * - * @param partName The variable sub-part name. - * @param reference The reference to use as a model. - * @return The content corresponding to a reference property. - */ - private String getReferenceContent(String partName, Reference reference) { - if (reference == null || partName == null) { - return null; - } - - switch (partName) { - case "a": - return reference.getAuthority(); - case "e": - return reference.getRelativePart(); - case "f": - return reference.getFragment(); - case "h": - return reference.getHostIdentifier(); - case "i": - return reference.getIdentifier(); - case "p": - return reference.getPath(); - case "q": - return reference.getQuery(); - case "r": - return reference.getRemainingPart(); - default: - if (partName.startsWith("b")) { - return getReferenceContent(partName.substring(1), reference.getBaseRef()); - } else if (partName.startsWith("t")) { - return getReferenceContent(partName.substring(1), reference.getTargetRef()); - } else if (partName.isEmpty()) { - return reference.toString(false, false); - } - break; - } - - return null; - } - - private Object getModificationDateAsString(Representation entity) { - return (entity != null && (entity.getModificationDate() != null)) - ? DateUtils.format(entity.getModificationDate(), FORMAT_RFC_1123.get(0)) - : null; - } - - private Object getExpirationDateAsString(Representation entity) { - return (entity != null && entity.getExpirationDate() != null) - ? DateUtils.format(entity.getExpirationDate(), FORMAT_RFC_1123.get(0)) - : null; - } - - private Object getLanguagesAsString(Representation entity) { - if (entity != null && !entity.getLanguages().isEmpty()) { - final StringBuilder value = new StringBuilder(); - for (int i = 0; i < entity.getLanguages().size(); i++) { - if (i > 0) { - value.append(", "); - } - value.append(entity.getLanguages().get(i).getName()); - } - return value.toString(); - } - return null; - } - - private Object getEncodingsAsString(Representation entity) { - if (entity != null && !entity.getEncodings().isEmpty()) { - final StringBuilder value = new StringBuilder(); - for (int i = 0; i < entity.getEncodings().size(); i++) { - if (i > 0) { - value.append(", "); - } - value.append(entity.getEncodings().get(i).getName()); - } - return value.toString(); - } - return null; - } + /** The request to use as a model. */ + private final Request request; + + /** The response to use as a model. */ + private final Response response; + + /** + * Constructor. + * + * @param request The request to use as a model. + * @param response The response to use as a model. + */ + public CallResolver(Request request, Response response) { + this.request = request; + this.response = response; + } + + @Override + public Object resolve(String variableName) { + // Check for a matching response attribute + Object result = + (this.response != null) ? this.response.getAttributes().get(variableName) : null; + if (result != null) { + return result; + } + + // Check for a matching request attribute + result = (this.request != null) ? this.request.getAttributes().get(variableName) : null; + if (result != null) { + return result; + } + + if (variableName == null) { + return null; + } + + // Check for a matching request or response property + if (this.request != null) { + ChallengeResponse cr = this.request.getChallengeResponse(); + Representation entity = this.request.getEntity(); + + switch (variableName) { + case "c": + return Boolean.toString(this.request.isConfidential()); + case "cia": + return this.request.getClientInfo().getAddress(); + case "ciua": + return this.request.getClientInfo().getUpstreamAddress(); + case "cig": + return this.request.getClientInfo().getAgent(); + case "cri": + return (cr != null) ? cr.getIdentifier() : null; + case "crs": + return (cr != null && cr.getScheme() != null) + ? cr.getScheme().getTechnicalName() + : null; + case "d": + return DateUtils.format(new Date(), FORMAT_RFC_1123.getFirst()); + case "ecs": + return (entity != null && entity.getCharacterSet() != null) + ? entity.getCharacterSet().getName() + : null; + case "ee": + return getEncodingsAsString(entity); + case "eed": + return getExpirationDateAsString(entity); + case "el": + return getLanguagesAsString(entity); + case "emd": + return getModificationDateAsString(entity); + case "emt": + return (entity != null && entity.getMediaType() != null) + ? entity.getMediaType().getName() + : null; + case "es": + return (entity != null && entity.getSize() != -1) + ? Long.toString(entity.getSize()) + : null; + case "et": + return (entity != null && entity.getTag() != null) + ? entity.getTag().getName() + : null; + case "m": + return (this.request.getMethod() != null) + ? this.request.getMethod().getName() + : null; + case "p": + return (this.request.getProtocol() != null) + ? this.request.getProtocol().getName() + : null; + default: + if (variableName.startsWith("o")) { + return getReferenceContent( + variableName.substring(1), this.request.getRootRef()); + } else if (variableName.startsWith("f")) { + return getReferenceContent( + variableName.substring(1), this.request.getReferrerRef()); + } else if (variableName.startsWith("h")) { + return getReferenceContent( + variableName.substring(1), this.request.getHostRef()); + } else if (variableName.startsWith("r")) { + return getReferenceContent( + variableName.substring(1), this.request.getResourceRef()); + } + break; + } + } + + if (this.response != null) { + Representation entity = this.response.getEntity(); + Status status = this.response.getStatus(); + ServerInfo serverInfo = this.response.getServerInfo(); + + switch (variableName) { + case "ECS": + return (entity != null && entity.getCharacterSet() != null) + ? entity.getCharacterSet().getName() + : null; + case "EE": + return getEncodingsAsString(entity); + case "EED": + return getExpirationDateAsString(entity); + case "EL": + return getLanguagesAsString(entity); + case "EMD": + return getModificationDateAsString(entity); + case "EMT": + return (entity != null && entity.getMediaType() != null) + ? entity.getMediaType().getName() + : null; + case "ES": + return (entity != null && entity.getSize() != -1) + ? Long.toString(entity.getSize()) + : null; + case "ET": + return (entity != null && entity.getTag() != null) + ? entity.getTag().getName() + : null; + case "S": + return (status != null) ? Integer.toString(status.getCode()) : null; + case "SIA": + return serverInfo.getAddress(); + case "SIG": + return serverInfo.getAgent(); + case "SIP": + return (serverInfo.getPort() != -1) + ? Integer.toString(serverInfo.getPort()) + : null; + default: + if (variableName.startsWith("R")) { + return getReferenceContent( + variableName.substring(1), this.response.getLocationRef()); + } + } + } + + return null; + } + + /** + * Returns the content corresponding to a reference property. + * + * @param partName The variable subpart name. + * @param reference The reference to use as a model. + * @return The content corresponding to a reference property. + */ + private String getReferenceContent(String partName, Reference reference) { + if (reference == null || partName == null) { + return null; + } + + switch (partName) { + case "a": + return reference.getAuthority(); + case "e": + return reference.getRelativePart(); + case "f": + return reference.getFragment(); + case "h": + return reference.getHostIdentifier(); + case "i": + return reference.getIdentifier(); + case "p": + return reference.getPath(); + case "q": + return reference.getQuery(); + case "r": + return reference.getRemainingPart(); + default: + if (partName.startsWith("b")) { + return getReferenceContent(partName.substring(1), reference.getBaseRef()); + } else if (partName.startsWith("t")) { + return getReferenceContent(partName.substring(1), reference.getTargetRef()); + } else if (partName.isEmpty()) { + return reference.toString(false, false); + } + break; + } + + return null; + } + + private Object getModificationDateAsString(Representation entity) { + return (entity != null && (entity.getModificationDate() != null)) + ? DateUtils.format(entity.getModificationDate(), FORMAT_RFC_1123.getFirst()) + : null; + } + + private Object getExpirationDateAsString(Representation entity) { + return (entity != null && entity.getExpirationDate() != null) + ? DateUtils.format(entity.getExpirationDate(), FORMAT_RFC_1123.getFirst()) + : null; + } + + private Object getLanguagesAsString(Representation entity) { + if (entity != null && !entity.getLanguages().isEmpty()) { + final StringBuilder value = new StringBuilder(); + for (int i = 0; i < entity.getLanguages().size(); i++) { + if (i > 0) { + value.append(", "); + } + value.append(entity.getLanguages().get(i).getName()); + } + return value.toString(); + } + return null; + } + + private Object getEncodingsAsString(Representation entity) { + if (entity != null && !entity.getEncodings().isEmpty()) { + final StringBuilder value = new StringBuilder(); + for (int i = 0; i < entity.getEncodings().size(); i++) { + if (i > 0) { + value.append(", "); + } + value.append(entity.getEncodings().get(i).getName()); + } + return value.toString(); + } + return null; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/CaseInsensitiveHashSet.java b/org.restlet/src/main/java/org/restlet/engine/util/CaseInsensitiveHashSet.java index 5ea5508747..4ee7836744 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/CaseInsensitiveHashSet.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/CaseInsensitiveHashSet.java @@ -1,47 +1,42 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import java.util.Collection; import java.util.HashSet; -/** - * Set implementation that is case insensitive. - */ +/** Implementation of the Set Interface that is case-insensitive. */ public class CaseInsensitiveHashSet extends HashSet { - private static final long serialVersionUID = 1L; - - /** - * Constructor initializing the set with the given collection. - * - * @param source The source collection to use for initialization. - */ - public CaseInsensitiveHashSet(Collection source) { - super(source); - } - - @Override - public boolean add(String element) { - return super.add(element.toLowerCase()); - } - - /** - * Verify containment by ignoring case. - */ - public boolean contains(String element) { - return super.contains(element.toLowerCase()); - } - - @Override - public boolean contains(Object o) { - return contains(o.toString()); - } + private static final long serialVersionUID = 1L; + + /** + * Constructor initializing the set with the given collection. + * + * @param source The source collection to use for initialization. + */ + public CaseInsensitiveHashSet(Collection source) { + super(source); + } + + @Override + public boolean add(String element) { + return super.add(element.toLowerCase()); + } + + /** Verify containment by ignoring the case. */ + public boolean contains(String element) { + return super.contains(element.toLowerCase()); + } + + @Override + public boolean contains(Object o) { + return contains(o.toString()); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/ChildClientDispatcher.java b/org.restlet/src/main/java/org/restlet/engine/util/ChildClientDispatcher.java index c3101106c8..7dc7a0c7b1 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/ChildClientDispatcher.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/ChildClientDispatcher.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import org.restlet.Application; @@ -17,109 +16,111 @@ /** * Client dispatcher for a component child. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state as member variables. - * + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state as member variables. + * * @author Jerome Louvel */ public class ChildClientDispatcher extends TemplateDispatcher { - /** The child context. */ - private ChildContext childContext; - - /** - * Constructor. - * - * @param childContext The child context. - */ - public ChildClientDispatcher(ChildContext childContext) { - this.childContext = childContext; - } - - /** - * Transmits the call to the parent component except if the call is internal as - * denoted by the {@link Protocol#RIAP} protocol and targets this child - * application. - * - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - public int doHandle(Request request, Response response) { - int result = CONTINUE; + /** The child context. */ + private ChildContext childContext; - Protocol protocol = request.getProtocol(); + /** + * Constructor. + * + * @param childContext The child context. + */ + public ChildClientDispatcher(ChildContext childContext) { + this.childContext = childContext; + } - if (protocol.equals(Protocol.RIAP)) { - // Let's dispatch it - LocalReference cr = new LocalReference(request.getResourceRef()); + /** + * Transmits the call to the parent component except if the call is internal as denoted by the + * {@link Protocol#RIAP} protocol and targets this child application. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public int doHandle(Request request, Response response) { + int result = CONTINUE; - if (cr.getRiapAuthorityType() == LocalReference.RIAP_APPLICATION) { - if ((getChildContext() != null) - && (getChildContext().getChild() instanceof Application application)) { - request.getResourceRef().setBaseRef(request.getResourceRef().getHostIdentifier()); - application.getInboundRoot().handle(request, response); - } - } else if (cr.getRiapAuthorityType() == LocalReference.RIAP_COMPONENT) { - parentHandle(request, response); - } else if (cr.getRiapAuthorityType() == LocalReference.RIAP_HOST) { - parentHandle(request, response); - } else { - getLogger().warning( - "Unknown RIAP authority. Only \"component\", \"host\" and \"application\" are supported."); - result = STOP; - } - } else { - if ((getChildContext() != null) - && (getChildContext().getChild() instanceof Application application)) { + Protocol protocol = request.getProtocol(); - if (!application.getConnectorService().getClientProtocols().contains(protocol)) { - getLogger().fine( - "The protocol used by this request is not declared in the application's connector service (" - + protocol - + "). Please update the list of client connectors used by your application and restart it."); - } - } + if (protocol.equals(Protocol.RIAP)) { + // Let's dispatch it + LocalReference cr = new LocalReference(request.getResourceRef()); - parentHandle(request, response); - } + if (cr.getRiapAuthorityType() == LocalReference.RIAP_APPLICATION) { + if ((getChildContext() != null) + && (getChildContext().getChild() instanceof Application application)) { + request.getResourceRef() + .setBaseRef(request.getResourceRef().getHostIdentifier()); + application.getInboundRoot().handle(request, response); + } + } else if (cr.getRiapAuthorityType() == LocalReference.RIAP_COMPONENT) { + parentHandle(request, response); + } else if (cr.getRiapAuthorityType() == LocalReference.RIAP_HOST) { + parentHandle(request, response); + } else { + getLogger() + .warning( + "Unknown RIAP authority. Only \"component\", \"host\" and \"application\" are supported."); + result = STOP; + } + } else { + if ((getChildContext() != null) + && (getChildContext().getChild() instanceof Application application) + && !application.getConnectorService().getClientProtocols().contains(protocol)) { + getLogger() + .fine( + "The protocol used by this request is not declared in the application's connector service (" + + protocol + + "). Please update the list of client connectors used by your application and restart it."); + } - return result; - } + parentHandle(request, response); + } - /** - * Returns the child context. - * - * @return The child context. - */ - private ChildContext getChildContext() { - return childContext; - } + return result; + } - /** - * Asks to the parent component to handle the call. - * - * @param request The request to handle. - * @param response The response to update. - */ - private void parentHandle(Request request, Response response) { - if (getChildContext() != null) { - if (getChildContext().getParentContext() != null) { - if (getChildContext().getParentContext().getClientDispatcher() != null) { - getChildContext().getParentContext().getClientDispatcher().handle(request, response); - } else { - getLogger().warning( - "The parent context doesn't have a client dispatcher available. Unable to handle call."); - } - } else { - getLogger().warning("Your Restlet doesn't have a parent context available."); - } - } else { - getLogger().warning("Your Restlet doesn't have a context available."); - } - } + /** + * Returns the child context. + * + * @return The child context. + */ + private ChildContext getChildContext() { + return childContext; + } + /** + * Asks to the parent component to handle the call. + * + * @param request The request to handle. + * @param response The response to update. + */ + private void parentHandle(Request request, Response response) { + if (getChildContext() != null) { + if (getChildContext().getParentContext() != null) { + if (getChildContext().getParentContext().getClientDispatcher() != null) { + getChildContext() + .getParentContext() + .getClientDispatcher() + .handle(request, response); + } else { + getLogger() + .warning( + "The parent context doesn't have a client dispatcher available. Unable to handle call."); + } + } else { + getLogger().warning("Your Restlet doesn't have a parent context available."); + } + } else { + getLogger().warning("Your Restlet doesn't have a context available."); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/ChildContext.java b/org.restlet/src/main/java/org/restlet/engine/util/ChildContext.java index c66ce1dd88..f23d7e7d53 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/ChildContext.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/ChildContext.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import org.restlet.Context; @@ -14,60 +13,64 @@ import org.restlet.engine.log.LogUtils; /** - * Context based on a parent component's context but dedicated to a child - * Restlet, typically to an application. - * + * Context based on a parent component's context but dedicated to a child Restlet, typically to an + * application. + * * @author Jerome Louvel */ public class ChildContext extends Context { - /** The child delegate, typically an application. */ - private volatile Restlet child; - - /** The parent context. */ - private volatile Context parentContext; + /** The child delegate, typically an application. */ + private volatile Restlet child; - /** - * Constructor. - * - * @param parentContext The parent context. - */ - public ChildContext(Context parentContext) { - this.child = null; - this.parentContext = parentContext; - setClientDispatcher(new ChildClientDispatcher(this)); - setServerDispatcher((parentContext != null) ? getParentContext().getServerDispatcher() : null); - setExecutorService((parentContext != null) ? ((parentContext.getExecutorService() != null) - ? new WrapperScheduledExecutorService(parentContext.getExecutorService()) - : null) : null); - } + /** The parent context. */ + private volatile Context parentContext; - /** - * Returns the child. - * - * @return the child. - */ - public Restlet getChild() { - return this.child; - } + /** + * Constructor. + * + * @param parentContext The parent context. + */ + public ChildContext(Context parentContext) { + this.child = null; + this.parentContext = parentContext; + setClientDispatcher(new ChildClientDispatcher(this)); + setServerDispatcher( + (parentContext != null) ? getParentContext().getServerDispatcher() : null); + setExecutorService( + (parentContext != null) + ? ((parentContext.getExecutorService() != null) + ? new WrapperScheduledExecutorService( + parentContext.getExecutorService()) + : null) + : null); + } - /** - * Returns the parent context. - * - * @return The parent context. - */ - protected Context getParentContext() { - return this.parentContext; - } + /** + * Returns the child. + * + * @return the child. + */ + public Restlet getChild() { + return this.child; + } - /** - * Sets the child. - * - * @param child The child. - */ - public void setChild(Restlet child) { - this.child = child; - setLogger(LogUtils.getLoggerName(this.parentContext.getLogger().getName(), child)); - } + /** + * Returns the parent context. + * + * @return The parent context. + */ + protected Context getParentContext() { + return this.parentContext; + } + /** + * Sets the child. + * + * @param child The child. + */ + public void setChild(Restlet child) { + this.child = child; + setLogger(LogUtils.getLoggerName(this.parentContext.getLogger().getName(), child)); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/ContextualRunnable.java b/org.restlet/src/main/java/org/restlet/engine/util/ContextualRunnable.java index 9a8aa29442..6526566259 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/ContextualRunnable.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/ContextualRunnable.java @@ -1,47 +1,42 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; /** - * {@link Runnable} That allows to define the context class loader. - * + * {@link Runnable} That allows defining the context class loader. + * * @author Jerome Louvel - * */ public abstract class ContextualRunnable implements Runnable { - /** The contextual class loader used at run time. */ - private ClassLoader contextClassLoader; - - /** - * Constructor. - */ - public ContextualRunnable() { - this.contextClassLoader = Thread.currentThread().getContextClassLoader(); - } + /** The contextual class loader used at run time. */ + private ClassLoader contextClassLoader; - /** - * Returns the runnable's context class loader. - * - * @return The runnable's context class loader. - */ - public ClassLoader getContextClassLoader() { - return contextClassLoader; - } + /** Constructor. */ + protected ContextualRunnable() { + this.contextClassLoader = Thread.currentThread().getContextClassLoader(); + } - /** - * Sets the runnable's context class loader. - * - * @param contextClassLoader The runnable's context class loader. - */ - public void setContextClassLoader(ClassLoader contextClassLoader) { - this.contextClassLoader = contextClassLoader; - } + /** + * Returns the runnable's context class loader. + * + * @return The runnable's context class loader. + */ + public ClassLoader getContextClassLoader() { + return contextClassLoader; + } + /** + * Sets the runnable's context class loader. + * + * @param contextClassLoader The runnable's context class loader. + */ + public void setContextClassLoader(ClassLoader contextClassLoader) { + this.contextClassLoader = contextClassLoader; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/DateUtils.java b/org.restlet/src/main/java/org/restlet/engine/util/DateUtils.java index df8cfba2e4..6102f45c49 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/DateUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/DateUtils.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import java.util.Arrays; @@ -16,218 +15,216 @@ /** * Date manipulation utilities. - * + * * @author Jerome Louvel */ public final class DateUtils { - /** - * Obsoleted HTTP date format (ANSI C asctime() format). Pattern: "EEE MMM dd - * HH:mm:ss yyyy". - */ - public static final List FORMAT_ASC_TIME = unmodifiableList("EEE MMM dd HH:mm:ss yyyy"); - - /** - * Obsoleted HTTP date format (RFC 1036). Pattern: "EEEE, dd-MMM-yy HH:mm:ss - * zzz". - */ - public static final List FORMAT_RFC_1036 = unmodifiableList("EEEE, dd-MMM-yy HH:mm:ss zzz"); - - /** - * Preferred HTTP date format (RFC 1123). Pattern: "EEE, dd MMM yyyy HH:mm:ss - * zzz". - */ - public static final List FORMAT_RFC_1123 = unmodifiableList("EEE, dd MMM yyyy HH:mm:ss zzz"); - - /** W3C date format (RFC 3339). Pattern: "yyyy-MM-dd'T'HH:mm:ssz". */ - public static final List FORMAT_RFC_3339 = unmodifiableList("yyyy-MM-dd'T'HH:mm:ssz"); - - /** AWS date format (ISO 8601). Pattern: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'". */ - public static final List FORMAT_ISO_8601 = unmodifiableList("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - - /** - * Common date format (RFC 822). Patterns: "EEE, dd MMM yy HH:mm:ss z" or "EEE, - * dd MMM yy HH:mm z", "dd MMM yy HH:mm:ss z" or "dd MMM yy HH:mm z". - */ - public static final List FORMAT_RFC_822 = unmodifiableList("EEE, dd MMM yy HH:mm:ss z", - "EEE, dd MMM yy HH:mm z", "dd MMM yy HH:mm:ss z", "dd MMM yy HH:mm z"); - - /** Remember the often used GMT time zone. */ - private static final java.util.TimeZone TIMEZONE_GMT = java.util.TimeZone.getTimeZone("GMT"); - - /** - * Compares two date with a precision of one second. - * - * @param baseDate The base date - * @param afterDate The date supposed to be after. - * @return True if the afterDate is indeed after the baseDate. - */ - public static boolean after(final Date baseDate, final Date afterDate) { - if ((baseDate == null) || (afterDate == null)) { - throw new IllegalArgumentException("Can't compare the dates, at least one of them is null"); - } - - long baseTime = baseDate.getTime() / 1000; - long afterTime = afterDate.getTime() / 1000; - return baseTime < afterTime; - } - - /** - * Compares two date with a precision of one second. - * - * @param baseDate The base date - * @param beforeDate The date supposed to be before. - * @return True if the beforeDate is indeed before the baseDate. - */ - public static boolean before(final Date baseDate, final Date beforeDate) { - if ((baseDate == null) || (beforeDate == null)) { - throw new IllegalArgumentException("Can't compare the dates, at least one of them is null"); - } - - long baseTime = baseDate.getTime() / 1000; - long beforeTime = beforeDate.getTime() / 1000; - return beforeTime < baseTime; - } - - /** - * Compares two date with a precision of one second. - * - * @param baseDate The base date - * @param otherDate The other date supposed to be equals. - * @return True if both dates are equals. - */ - public static boolean equals(final Date baseDate, final Date otherDate) { - if ((baseDate == null) || (otherDate == null)) { - throw new IllegalArgumentException("Can't compare the dates, at least one of them is null"); - } - - long baseTime = baseDate.getTime() / 1000; - long otherTime = otherDate.getTime() / 1000; - return otherTime == baseTime; - } - - /** - * Formats a Date in the default HTTP format (RFC 1123). - * - * @param date The date to format. - * @return The formatted date. - */ - public static String format(final Date date) { - return format(date, DateUtils.FORMAT_RFC_1123.get(0)); - } - - /** - * Formats a Date according to the first format in the array. - * - * @param date The date to format. - * @param formats The array of date formats to use. - * @return The formatted date. - */ - public static String format(final Date date, final List formats) { - return format(date, formats != null ? formats.get(0) : null); - } - - /** - * Formats a Date according to the given format. - * - * @param date The date to format. - * @param format The date format to use. - * @return The formatted date. - */ - public static String format(final Date date, final String format) { - if (date == null) { - throw new IllegalArgumentException("Date is null"); - } - - java.text.DateFormat formatter = null; - - if (FORMAT_RFC_3339.get(0).equals(format)) { - formatter = new InternetDateFormat(TIMEZONE_GMT); - } else { - formatter = new java.text.SimpleDateFormat(format, java.util.Locale.US); - formatter.setTimeZone(TIMEZONE_GMT); - } - - return formatter.format(date); - } - - /** - * Parses a formatted date into a Date object using the default HTTP format (RFC - * 1123). - * - * @param date The date to parse. - * @return The parsed date. - */ - public static Date parse(String date) { - return parse(date, FORMAT_RFC_1123); - } - - /** - * Parses a formatted date into a Date object. - * - * @param date The date to parse. - * @param formats The date formats to use sorted by completeness. - * @return The parsed date. - */ - public static Date parse(String date, List formats) { - if (date == null) { - throw new IllegalArgumentException("Date is null"); - } - - Date result = null; - - String format = null; - int formatsSize = formats.size(); - - for (int i = 0; (result == null) && (i < formatsSize); i++) { - format = formats.get(i); - java.text.DateFormat parser = null; - - if (FORMAT_RFC_3339.get(0).equals(format)) { - parser = new InternetDateFormat(TIMEZONE_GMT); - } else { - parser = new java.text.SimpleDateFormat(format, java.util.Locale.US); - parser.setTimeZone(TIMEZONE_GMT); - } - try { - result = parser.parse(date); - } catch (Exception e) { - // Ignores error as the next format may work better - } - } - - return result; - } - - /** - * Returns an immutable version of a given date. - * - * @param date The modifiable date. - * @return An immutable version of a given date. - */ - public static Date unmodifiable(Date date) { - return (date == null) ? null : new ImmutableDate(date); - } - - /** - * Helper method to help initialize this class by providing unmodifiable lists - * based on arrays. - * - * @param Any valid java object - * @param array to be converted into an unmodifiable list - * @return unmodifiable list based on the provided array - */ - @SafeVarargs - private static List unmodifiableList(final T... array) { - return Collections.unmodifiableList(Arrays.asList(array)); - } - - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e. it isn't instantiable and extensible. - */ - private DateUtils() { - - } - + /** + * Obsoleted HTTP date format (ANSI C asctime() format). Pattern: "EEE MMM dd HH:mm:ss yyyy". + */ + public static final List FORMAT_ASC_TIME = unmodifiableList("EEE MMM dd HH:mm:ss yyyy"); + + /** Obsoleted HTTP date format (RFC 1036). Pattern: "EEEE, dd-MMM-yy HH:mm:ss zzz". */ + public static final List FORMAT_RFC_1036 = + unmodifiableList("EEEE, dd-MMM-yy HH:mm:ss zzz"); + + /** Preferred HTTP date format (RFC 1123). Pattern: "EEE, dd MMM yyyy HH:mm:ss zzz". */ + public static final List FORMAT_RFC_1123 = + unmodifiableList("EEE, dd MMM yyyy HH:mm:ss zzz"); + + /** W3C date format (RFC 3339). Pattern: "yyyy-MM-dd'T'HH:mm:ssz". */ + public static final List FORMAT_RFC_3339 = unmodifiableList("yyyy-MM-dd'T'HH:mm:ssz"); + + /** AWS date format (ISO 8601). Pattern: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'". */ + public static final List FORMAT_ISO_8601 = + unmodifiableList("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + + /** + * Common date format (RFC 822). Patterns: "EEE, dd MMM yy HH:mm:ss z" or "EEE, dd MMM yy HH:mm + * z", "dd MMM yy HH:mm:ss z" or "dd MMM yy HH:mm z". + */ + public static final List FORMAT_RFC_822 = + unmodifiableList( + "EEE, dd MMM yy HH:mm:ss z", + "EEE, dd MMM yy HH:mm z", + "dd MMM yy HH:mm:ss z", + "dd MMM yy HH:mm z"); + + /** Remember the often used GMT time zone. */ + private static final java.util.TimeZone TIMEZONE_GMT = java.util.TimeZone.getTimeZone("GMT"); + + /** + * Compares two date with a precision of one second. + * + * @param baseDate The base date + * @param afterDate The date supposed to be after. + * @return True if the afterDate is indeed after the baseDate. + */ + public static boolean after(final Date baseDate, final Date afterDate) { + if ((baseDate == null) || (afterDate == null)) { + throw new IllegalArgumentException( + "Can't compare the dates, at least one of them is null"); + } + + long baseTime = baseDate.getTime() / 1000; + long afterTime = afterDate.getTime() / 1000; + return baseTime < afterTime; + } + + /** + * Compares two date with a precision of one second. + * + * @param baseDate The base date + * @param beforeDate The date supposed to be before. + * @return True if the beforeDate is indeed before the baseDate. + */ + public static boolean before(final Date baseDate, final Date beforeDate) { + if ((baseDate == null) || (beforeDate == null)) { + throw new IllegalArgumentException( + "Can't compare the dates, at least one of them is null"); + } + + long baseTime = baseDate.getTime() / 1000; + long beforeTime = beforeDate.getTime() / 1000; + return beforeTime < baseTime; + } + + /** + * Compares two date with a precision of one second. + * + * @param baseDate The base date + * @param otherDate The other date supposed to be equals. + * @return True if both dates are equals. + */ + public static boolean equals(final Date baseDate, final Date otherDate) { + if ((baseDate == null) || (otherDate == null)) { + throw new IllegalArgumentException( + "Can't compare the dates, at least one of them is null"); + } + + long baseTime = baseDate.getTime() / 1000; + long otherTime = otherDate.getTime() / 1000; + return otherTime == baseTime; + } + + /** + * Formats a Date in the default HTTP format (RFC 1123). + * + * @param date The date to format. + * @return The formatted date. + */ + public static String format(final Date date) { + return format(date, DateUtils.FORMAT_RFC_1123.getFirst()); + } + + /** + * Formats a Date according to the first format in the array. + * + * @param date The date to format. + * @param formats The array of date formats to use. + * @return The formatted date. + */ + public static String format(final Date date, final List formats) { + return format(date, formats != null ? formats.getFirst() : null); + } + + /** + * Formats a Date according to the given format. + * + * @param date The date to format. + * @param format The date format to use. + * @return The formatted date. + */ + public static String format(final Date date, final String format) { + if (date == null) { + throw new IllegalArgumentException("Date is null"); + } + + final java.text.DateFormat formatter; + + if (FORMAT_RFC_3339.getFirst().equals(format)) { + formatter = new InternetDateFormat(TIMEZONE_GMT); + } else { + formatter = new java.text.SimpleDateFormat(format, java.util.Locale.US); + formatter.setTimeZone(TIMEZONE_GMT); + } + + return formatter.format(date); + } + + /** + * Parses a formatted date into a Date object using the default HTTP format (RFC 1123). + * + * @param date The date to parse. + * @return The parsed date. + */ + public static Date parse(String date) { + return parse(date, FORMAT_RFC_1123); + } + + /** + * Parses a formatted date into a Date object. + * + * @param date The date to parse. + * @param formats The date formats to use sorted by completeness. + * @return The parsed date. + */ + public static Date parse(String date, List formats) { + if (date == null) { + throw new IllegalArgumentException("Date is null"); + } + + Date result = null; + + String format = null; + int formatsSize = formats.size(); + + for (int i = 0; (result == null) && (i < formatsSize); i++) { + format = formats.get(i); + java.text.DateFormat parser = null; + + if (FORMAT_RFC_3339.getFirst().equals(format)) { + parser = new InternetDateFormat(TIMEZONE_GMT); + } else { + parser = new java.text.SimpleDateFormat(format, java.util.Locale.US); + parser.setTimeZone(TIMEZONE_GMT); + } + try { + result = parser.parse(date); + } catch (Exception e) { + // Ignores error as the next format may work better + } + } + + return result; + } + + /** + * Returns an immutable version of a given date. + * + * @param date The modifiable date. + * @return An immutable version of a given date. + */ + public static Date unmodifiable(Date date) { + return (date == null) ? null : new ImmutableDate(date); + } + + /** + * Helper method to help initialize this class by providing unmodifiable lists based on arrays. + * + * @param Any valid java object + * @param array to be converted into an unmodifiable list + * @return unmodifiable list based on the provided array + */ + @SafeVarargs + private static List unmodifiableList(final T... array) { + return Collections.unmodifiableList(Arrays.asList(array)); + } + + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private DateUtils() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/DefaultSaxHandler.java b/org.restlet/src/main/java/org/restlet/engine/util/DefaultSaxHandler.java index 729106a8d5..aba10cb42d 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/DefaultSaxHandler.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/DefaultSaxHandler.java @@ -1,145 +1,152 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; import org.restlet.Context; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - /** - * A Utility class which extends the provided {@link DefaultHandler} and - * implements the {@link org.w3c.dom.ls.LSResourceResolver} interface. All the - * methods of this class do nothing besides generating log messages. - * + * A Utility class which extends the provided {@link DefaultHandler} and implements the {@link + * org.w3c.dom.ls.LSResourceResolver} interface. All the methods of this class do nothing besides + * generating log messages. + * * @author Raif S. Naffah * @author Jerome Louvel */ -public class DefaultSaxHandler extends DefaultHandler implements org.w3c.dom.ls.LSResourceResolver -{ +public class DefaultSaxHandler extends DefaultHandler implements org.w3c.dom.ls.LSResourceResolver { + + /** + * A class field set to {@code true} if the JAXP debug property is turned on; {@code false} + * otherwise. This is used to control the degree of output generated in the logs. + */ + private static boolean debug; - /** - * A class field set to {@code true} if the JAXP debug property is turned - * on; {@code false} otherwise. This is used to control the degree of output - * generated in the logs. - */ - private static boolean debug; + static { + try { + final String debugStr = System.getProperty("jaxp.debug"); + debug = debugStr != null && !"false".equalsIgnoreCase(debugStr); + } catch (SecurityException x) { + debug = false; + } + } - static { - try { - final String debugStr = System.getProperty("jaxp.debug"); - debug = debugStr != null && !"false".equalsIgnoreCase(debugStr); - } catch (SecurityException x) { - debug = false; - } - } + /** + * Set to {@code true} if the current Context's logger is capable of outputting messages at the + * CONFIG level; {@code false} otherwise. + */ + private volatile boolean loggable; - /** - * Set to {@code true} if the current Context's logger is capable of outputting - * messages at the CONFIG level; {@code false} otherwise. - */ - private volatile boolean loggable; + /** The current context JDK {@link Logger} to use for message output. */ + private volatile Logger logger; - /** The current context JDK {@link Logger} to use for message output. */ - private volatile Logger logger; + /** Trivial constructor. */ + public DefaultSaxHandler() { + super(); + logger = Context.getCurrentLogger(); + loggable = logger.isLoggable(Level.CONFIG); + } - /** - * Trivial constructor. - */ - public DefaultSaxHandler() { - super(); - logger = Context.getCurrentLogger(); - loggable = logger.isLoggable(Level.CONFIG); - } + @Override + public void error(SAXParseException x) throws SAXException { + log("ERROR", x); + } - @Override - public void error(SAXParseException x) throws SAXException { - if (loggable) { - final String msg = "[ERROR] - Unexpected exception while parsing " + "an instance of PUBLIC [" - + x.getPublicId() + "], SYSTEM [" + x.getSystemId() + "] - line #" + x.getLineNumber() - + ", column #" + x.getColumnNumber(); - if (debug) { - Context.getCurrentLogger().log(Level.CONFIG, msg, x); - } else { - logger.config(msg + ": " + x.getLocalizedMessage()); - } - } - } + @Override + public void fatalError(SAXParseException x) { + log("FATAL", x); + } - @Override - public void fatalError(SAXParseException x) throws SAXException { - if (loggable) { - final String msg = "[FATAL] - Unexpected exception while parsing " + "an instance of PUBLIC [" - + x.getPublicId() + "], SYSTEM [" + x.getSystemId() + "] - line #" + x.getLineNumber() - + ", column #" + x.getColumnNumber(); - if (debug) { - Context.getCurrentLogger().log(Level.CONFIG, msg, x); - } else { - logger.config(msg + ": " + x.getLocalizedMessage()); - } - } - } + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws IOException, SAXException { + if (loggable) { + logger.config( + () -> + "Resolve entity with PUBLIC [" + + publicId + + "], and SYSTEM [" + + systemId + + "]"); + } + return super.resolveEntity(publicId, systemId); + } - @Override - public InputSource resolveEntity(String publicId, String systemId) throws IOException, SAXException { - if (loggable) { - logger.config("Resolve entity with PUBLIC [" + publicId + "], and SYSTEM [" + systemId + "]"); - } - return super.resolveEntity(publicId, systemId); - } + /** + * Allow the application to resolve external resources. + * + *

This implementation always returns a {@code null}. + * + * @param type The type of the resource being resolved. + * @param namespaceUri The namespace of the resource being resolved. + * @param publicId The public identifier. + * @param systemId The system identifier. + * @param baseUri The absolute base URI of the resource being parsed. + * @return Always {@code null}. + */ + public org.w3c.dom.ls.LSInput resolveResource( + String type, String namespaceUri, String publicId, String systemId, String baseUri) { + if (loggable) { + logger.config( + () -> + "Resolve resource with type [" + + type + + "], namespace URI [" + + namespaceUri + + "], PUBLIC [" + + publicId + + "], SYSTEM [" + + systemId + + "], and base URI [" + + baseUri + + "]"); + } + return null; + } - /** - * Allow the application to resolve external resources. - *

- * This implementation always returns a {@code null}. - * - * @param type The type of the resource being resolved. - * @param namespaceUri The namespace of the resource being resolved. - * @param publicId The public identifier. - * @param systemId The system identifier. - * @param baseUri The absolute base URI of the resource being parsed. - * @return Always {@code null}. - */ - public org.w3c.dom.ls.LSInput resolveResource(String type, String namespaceUri, String publicId, String systemId, - String baseUri) { - if (loggable) { - logger.config("Resolve resource with type [" + type + "], namespace URI [" + namespaceUri + "], PUBLIC [" - + publicId + "], SYSTEM [" + systemId + "], and base URI [" + baseUri + "]"); - } - return null; - } + @Override + public void skippedEntity(String name) throws SAXException { + super.skippedEntity(name); + if (loggable) { + logger.config(() -> "Skipped entity named [" + name + "]"); + } + } - @Override - public void skippedEntity(String name) throws SAXException { - super.skippedEntity(name); - if (loggable) { - logger.config("Skipped entity named [" + name + "]"); - } - } + @Override + public void warning(SAXParseException x) throws SAXException { + log("WARN", x); + } - @Override - public void warning(SAXParseException x) throws SAXException { - if (loggable) { - final String msg = "[WARN] - Unexpected exception while parsing " + "an instance of PUBLIC [" - + x.getPublicId() + "], SYSTEM [" + x.getSystemId() + "] - line #" + x.getLineNumber() - + ", column #" + x.getColumnNumber(); - if (debug) { - Context.getCurrentLogger().log(Level.CONFIG, msg, x); - } else { - logger.config(msg + ": " + x.getLocalizedMessage()); - } - } - } + private void log(String level, SAXParseException x) { + if (loggable) { + final String msg = + "[" + + level + + "] - Unexpected exception while parsing " + + "an instance of PUBLIC [" + + x.getPublicId() + + "], SYSTEM [" + + x.getSystemId() + + "] - line #" + + x.getLineNumber() + + ", column #" + + x.getColumnNumber(); + if (debug) { + Context.getCurrentLogger().log(Level.CONFIG, msg, x); + } else { + logger.config(() -> msg + ": " + x.getLocalizedMessage()); + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/EngineClassLoader.java b/org.restlet/src/main/java/org/restlet/engine/util/EngineClassLoader.java index ab92cdb16b..2b45121bbb 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/EngineClassLoader.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/EngineClassLoader.java @@ -1,168 +1,162 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; -import org.restlet.engine.Edition; -import org.restlet.engine.Engine; - import java.io.IOException; import java.net.URL; import java.util.Enumeration; import java.util.Vector; +import org.restlet.engine.Edition; +import org.restlet.engine.Engine; /** - * Flexible engine class loader. Uses the current class's class loader as its - * parent. Can also check with the user class loader defined by - * {@link Engine#getUserClassLoader()} or with - * {@link Thread#getContextClassLoader()} or with {@link Class#forName(String)}. - * + * Flexible engine class loader. Uses the current class's class loader as its parent. Can also check + * with the user class loader defined by {@link Engine#getUserClassLoader()} or with {@link + * Thread#getContextClassLoader()} or with {@link Class#forName(String)}. + * * @author Jerome Louvel */ public class EngineClassLoader extends ClassLoader { - /** The parent Restlet engine. */ - private final Engine engine; - - /** - * Constructor. - */ - public EngineClassLoader(Engine engine) { - super(EngineClassLoader.class.getClassLoader()); - this.engine = engine; - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - Class result = null; - - // First try the user class loader - ClassLoader cl = getEngine().getUserClassLoader(); - - if (cl != null) { - try { - result = cl.loadClass(name); - } catch (ClassNotFoundException cnfe) { - // Ignore - } - } - - // Then try the current thread's class loader - if (result == null) { - cl = Thread.currentThread().getContextClassLoader(); - - if (cl != null) { - try { - result = cl.loadClass(name); - } catch (ClassNotFoundException cnfe) { - // Ignore - } - } - } - - // Finally try with this ultimate approach - if (result == null) { - try { - result = Class.forName(name); - } catch (ClassNotFoundException cnfe) { - // Ignore - } - } - - // Otherwise throw an exception - if (result == null) { - throw new ClassNotFoundException(name); - } - - return result; - } - - @Override - protected URL findResource(String name) { - URL result = null; - - // First try the user class loader - ClassLoader cl = getEngine().getUserClassLoader(); - - if (cl != null) { - result = cl.getResource(name); - } - - // Then try the current thread's class loader - if (result == null) { - cl = Thread.currentThread().getContextClassLoader(); - - if (cl != null) { - result = cl.getResource(name); - } - } - - return result; - } - - @Override - protected Enumeration findResources(String name) throws IOException { - Enumeration result = null; - - // First try the user class loader - ClassLoader cl = getEngine().getUserClassLoader(); - - if (cl != null) { - result = cl.getResources(name); - } - - // Then try the current thread's class loader - if (result == null) { - cl = Thread.currentThread().getContextClassLoader(); - - if (cl != null) { - result = cl.getResources(name); - } - } - - return result; - } - - /** - * Returns the parent Restlet engine. - * - * @return The parent Restlet engine. - */ - protected Engine getEngine() { - return engine; - } - - @Override - public Enumeration getResources(String name) throws IOException { - Enumeration allUrls = super.getResources(name); - Vector result = new Vector(); - - if (allUrls != null) { - try { - URL url; - while (allUrls.hasMoreElements()) { - url = allUrls.nextElement(); - - if (!result.contains(url)) { - result.add(url); - } - } - } catch (NullPointerException e) { - // At this time (June 2009) a NPE is thrown with Dalvik JVM. - // Let's throw the NPE for the other editions. - if (Edition.ANDROID.isNotCurrentEdition()) { - throw e; - } - } - } - - return result.elements(); - } - + /** The parent Restlet engine. */ + private final Engine engine; + + /** Constructor. */ + public EngineClassLoader(Engine engine) { + super(EngineClassLoader.class.getClassLoader()); + this.engine = engine; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + Class result = null; + + // First, try the user class loader + ClassLoader cl = getEngine().getUserClassLoader(); + + if (cl != null) { + try { + result = cl.loadClass(name); + } catch (ClassNotFoundException cnfe) { + // Ignore + } + } + + // Then try the current thread's class loader + if (result == null) { + cl = Thread.currentThread().getContextClassLoader(); + + if (cl != null) { + try { + result = cl.loadClass(name); + } catch (ClassNotFoundException ignored) { + // Ignore + } + } + } + + // Finally try with this ultimate approach + if (result == null) { + try { + result = Class.forName(name); + } catch (ClassNotFoundException ignored) { + // Ignore + } + } + + // Otherwise throw an exception + if (result == null) { + throw new ClassNotFoundException(name); + } + + return result; + } + + @Override + protected URL findResource(String name) { + URL result = null; + + // First, try the user class loader + ClassLoader cl = getEngine().getUserClassLoader(); + + if (cl != null) { + result = cl.getResource(name); + } + + // Then try the current thread's class loader + if (result == null) { + cl = Thread.currentThread().getContextClassLoader(); + + if (cl != null) { + result = cl.getResource(name); + } + } + + return result; + } + + @Override + protected Enumeration findResources(String name) throws IOException { + Enumeration result = null; + + // First, try the user class loader + ClassLoader cl = getEngine().getUserClassLoader(); + + if (cl != null) { + result = cl.getResources(name); + } + + // Then try the current thread's class loader + if (result == null) { + cl = Thread.currentThread().getContextClassLoader(); + + if (cl != null) { + result = cl.getResources(name); + } + } + + return result; + } + + /** + * Returns the parent Restlet engine. + * + * @return The parent Restlet engine. + */ + protected Engine getEngine() { + return engine; + } + + @Override + public Enumeration getResources(String name) throws IOException { + Enumeration allUrls = super.getResources(name); + Vector result = new Vector<>(); + + if (allUrls != null) { + try { + URL url; + while (allUrls.hasMoreElements()) { + url = allUrls.nextElement(); + + if (!result.contains(url)) { + result.add(url); + } + } + } catch (NullPointerException e) { + // At this time (June 2009) a NPE is thrown with Dalvik JVM. + // Let's throw the NPE for the other editions. + if (Edition.ANDROID.isNotCurrentEdition()) { + throw e; + } + } + } + + return result.elements(); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/FormReader.java b/org.restlet/src/main/java/org/restlet/engine/util/FormReader.java index bdc8c28732..8b7a6d382d 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/FormReader.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/FormReader.java @@ -1,370 +1,314 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; -import org.restlet.Context; -import org.restlet.data.CharacterSet; -import org.restlet.data.Form; -import org.restlet.data.Parameter; -import org.restlet.representation.Representation; -import org.restlet.util.Series; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.data.CharacterSet; +import org.restlet.data.Form; +import org.restlet.data.Parameter; +import org.restlet.representation.Representation; +import org.restlet.util.NamedValuesCollector; +import org.restlet.util.Series; /** * Form reader. - * + * * @author Jerome Louvel */ public class FormReader { - /** The encoding to use, decoding is enabled, see {@link #decode}. */ - private volatile CharacterSet characterSet; - - /** Indicates if the parameters should be decoded. */ - private volatile boolean decode; - - /** The separator character used between parameters. */ - private volatile char separator; - - /** The form stream. */ - private volatile InputStream stream; - - /** - * Constructor.
- * In case the representation does not define a character set, the UTF-8 - * character set is used. - * - * @param representation The web form content. - * @throws IOException if the stream of the representation could not be opened. - */ - public FormReader(Representation representation) throws IOException { - this(representation, true); - } - - /** - * Constructor.
- * In case the representation does not define a character set, the UTF-8 - * character set is used. - * - * @param representation The web form content. - * @param decode Indicates if the parameters should be decoded using the - * given character set. - * @throws IOException if the stream of the representation could not be opened. - */ - public FormReader(Representation representation, boolean decode) throws IOException { - this.decode = decode; - this.stream = representation.getStream(); - this.separator = '&'; - - if (representation.getCharacterSet() != null) { - this.characterSet = representation.getCharacterSet(); - } else { - this.characterSet = CharacterSet.UTF_8; - } - } - - /** - * Constructor. Will leave the parsed data encoded. - * - * @param parametersString The parameters string. - * @param separator The separator character used between parameters. - */ - public FormReader(String parametersString, char separator) { - this(parametersString, null, separator, false); - } - - /** - * Constructor. - * - * @param parametersString The parameters string. - * @param characterSet The supported character encoding. Set to null to - * leave the data encoded. - * @param separator The separator character used between parameters. - */ - public FormReader(String parametersString, CharacterSet characterSet, char separator) { - this(parametersString, characterSet, separator, true); - } - - /** - * Constructor. - * - * @param parametersString The parameters string. - * @param characterSet The supported character encoding. Set to null to - * leave the data encoded. - * @param separator The separator character used between parameters. - * @param decode Indicates if the parameters should be decoded using - * the given character set. - */ - public FormReader(String parametersString, CharacterSet characterSet, char separator, boolean decode) { - this.decode = decode; - this.stream = new ByteArrayInputStream(parametersString.getBytes()); - this.characterSet = characterSet; - this.separator = separator; - } - - /** - * Adds the parameters into a given series. - * - * @param parameters The target parameter series. - */ - public void addParameters(Series parameters) { - boolean readNext = true; - Parameter param = null; - - if (this.stream != null) { - // Let's read all form parameters - try { - while (readNext) { - param = readNextParameter(); - - if (param != null) { - // Add parsed parameter to the form - parameters.add(param); - } else { - // Last parameter parsed - readNext = false; - } - } - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.WARNING, - "Unable to parse a form parameter. Skipping the remaining parameters.", ioe); - } - - try { - this.stream.close(); - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to close the form input stream", ioe); - } - } - } - - /** - * Reads all the parameters. - * - * @return The form read. - * @throws IOException If the parameters could not be read. - */ - public Form read() throws IOException { - Form result = new Form(); - - if (this.stream != null) { - Parameter param = readNextParameter(); - - while (param != null) { - result.add(param); - param = readNextParameter(); - } - - this.stream.close(); - } - - return result; - } - - /** - * Reads the first parameter with the given name. - * - * @param name The parameter name to match. - * @return The parameter value. - * @throws IOException - */ - public Parameter readFirstParameter(String name) throws IOException { - Parameter result = null; - - if (this.stream != null) { - Parameter param = readNextParameter(); - - while ((param != null) && (result == null)) { - if (param.getName().equals(name)) { - result = param; - } - - param = readNextParameter(); - } - - this.stream.close(); - } - - return result; - } - - /** - * Reads the next parameter available or null. - * - * @return The next parameter available or null. - * @throws IOException If the next parameter could not be read. - */ - public Parameter readNextParameter() throws IOException { - Parameter result = null; - - if (this.stream != null) { - try { - boolean readingName = true; - StringBuilder nameBuffer = new StringBuilder(); - StringBuilder valueBuffer = new StringBuilder(); - int nextChar = 0; - - while ((result == null) && (nextChar != -1)) { - nextChar = this.stream.read(); - - if (readingName) { - if (nextChar == '=') { - if (!nameBuffer.isEmpty()) { - readingName = false; - } else { - throw new IOException("Empty parameter name detected. Please check your form data"); - } - } else if (endOfCurrentParameterReached(nextChar)) { - if (!nameBuffer.isEmpty()) { - result = FormUtils.create(nameBuffer, null, this.decode, this.characterSet); - } else if (nextChar == -1) { - // Do nothing return null preference - } else { - Context.getCurrentLogger() - .fine("Empty parameter name detected. Please check your form data"); - } - } else { - nameBuffer.append((char) nextChar); - } - } else { - // reading value - if (endOfCurrentParameterReached(nextChar)) { - result = FormUtils.create(nameBuffer, valueBuffer, this.decode, this.characterSet); - } else { - valueBuffer.append((char) nextChar); - } - } - } - } catch (UnsupportedEncodingException uee) { - throw new IOException("Unsupported encoding. Please contact the administrator"); - } - } - - return result; - } - - private boolean endOfCurrentParameterReached(int nextChar) { - return (nextChar == this.separator) || (nextChar == -1); - } - - /** - * Reads the parameters with the given name. If multiple values are found, a - * list is returned created. - * - * @param name The parameter name to match. - * @return The parameter value or list of values. - * @throws IOException If the parameters could not be read. - */ - @SuppressWarnings("unchecked") - public Object readParameter(String name) throws IOException { - Object result = null; - - if (this.stream != null) { - Parameter param = readNextParameter(); - - while (param != null) { - if (param.getName().equals(name)) { - if (result != null) { - List values = null; - - if (result instanceof List) { - // Multiple values already found for this parameter - values = (List) result; - } else { - // Second value found for this parameter - // Create a list of values - values = new ArrayList(); - values.add(result); - result = values; - } - - if (param.getValue() == null) { - values.add(Series.EMPTY_VALUE); - } else { - values.add(param.getValue()); - } - } else { - if (param.getValue() == null) { - result = Series.EMPTY_VALUE; - } else { - result = param.getValue(); - } - } - } - - param = readNextParameter(); - } - - this.stream.close(); - } - - return result; - } - - /** - * Reads the parameters whose name is a key in the given map. If a matching - * parameter is found, its value is put in the map. If multiple values are - * found, a list is created and set in the map. - * - * @param parameters The parameters map controlling the reading. - * @throws IOException If the parameters could not be read. - */ - @SuppressWarnings("unchecked") - public void readParameters(Map parameters) throws IOException { - if (this.stream != null) { - Parameter param = readNextParameter(); - Object currentValue = null; - - while (param != null) { - if (parameters.containsKey(param.getName())) { - currentValue = parameters.get(param.getName()); - - if (currentValue != null) { - List values = null; - - if (currentValue instanceof List) { - // Multiple values already found for this parameter - values = (List) currentValue; - } else { - // Second value found for this parameter - // Create a list of values - values = new ArrayList(); - values.add(currentValue); - parameters.put(param.getName(), values); - } - - if (param.getValue() == null) { - values.add(Series.EMPTY_VALUE); - } else { - values.add(param.getValue()); - } - } else { - if (param.getValue() == null) { - parameters.put(param.getName(), Series.EMPTY_VALUE); - } else { - parameters.put(param.getName(), param.getValue()); - } - } - } - - param = readNextParameter(); - } - - this.stream.close(); - } - } + /** The encoding to use, decoding is enabled, see {@link #decode}. */ + private volatile CharacterSet characterSet; + + /** Indicates if the parameters should be decoded. */ + private volatile boolean decode; + + /** The separator character used between parameters. */ + private volatile char separator; + + /** The form stream. */ + private volatile InputStream stream; + + /** + * Constructor.
+ * In case the representation does not define a character set, the UTF-8 character set is used. + * + * @param representation The web form content. + * @throws IOException if the stream of the representation could not be opened. + */ + public FormReader(Representation representation) throws IOException { + this(representation, true); + } + + /** + * Constructor.
+ * In case the representation does not define a character set, the UTF-8 character set is used. + * + * @param representation The web form content. + * @param decode Indicates if the parameters should be decoded using the given character set. + * @throws IOException if the stream of the representation could not be opened. + */ + public FormReader(Representation representation, boolean decode) throws IOException { + this.decode = decode; + this.stream = representation.getStream(); + this.separator = '&'; + + if (representation.getCharacterSet() != null) { + this.characterSet = representation.getCharacterSet(); + } else { + this.characterSet = CharacterSet.UTF_8; + } + } + + /** + * Constructor. Will leave the parsed data encoded. + * + * @param parametersString The parameters string. + * @param separator The separator character used between parameters. + */ + public FormReader(String parametersString, char separator) { + this(parametersString, null, separator, false); + } + + /** + * Constructor. + * + * @param parametersString The parameters string. + * @param characterSet The supported character encoding. Set to null to leave the data encoded. + * @param separator The separator character used between parameters. + */ + public FormReader(String parametersString, CharacterSet characterSet, char separator) { + this(parametersString, characterSet, separator, true); + } + + /** + * Constructor. + * + * @param parametersString The parameters string. + * @param characterSet The supported character encoding. Set to null to leave the data encoded. + * @param separator The separator character used between parameters. + * @param decode Indicates if the parameters should be decoded using the given character set. + */ + public FormReader( + String parametersString, CharacterSet characterSet, char separator, boolean decode) { + this.decode = decode; + this.stream = new ByteArrayInputStream(parametersString.getBytes()); + this.characterSet = characterSet; + this.separator = separator; + } + + /** + * Adds the parameters into a given series. + * + * @param parameters The target parameter series. + */ + public void addParameters(Series parameters) { + boolean readNext = true; + Parameter param = null; + + if (this.stream != null) { + // Let's read all form parameters + try { + while (readNext) { + param = readNextParameter(); + + if (param != null) { + // Add parsed parameter to the form + parameters.add(param); + } else { + // Last parameter parsed + readNext = false; + } + } + } catch (IOException ioe) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Unable to parse a form parameter. Skipping the remaining parameters.", + ioe); + } + + try { + this.stream.close(); + } catch (IOException ioe) { + Context.getCurrentLogger() + .log(Level.WARNING, "Unable to close the form input stream", ioe); + } + } + } + + /** + * Reads all the parameters. + * + * @return The form read. + * @throws IOException If the parameters could not be read. + */ + public Form read() throws IOException { + Form result = new Form(); + + if (this.stream != null) { + Parameter param = readNextParameter(); + + while (param != null) { + result.add(param); + param = readNextParameter(); + } + + this.stream.close(); + } + + return result; + } + + /** + * Reads the first parameter with the given name. + * + * @param name The parameter name to match. + * @return The parameter value. + * @throws IOException + */ + public Parameter readFirstParameter(String name) throws IOException { + Parameter result = null; + + if (this.stream != null) { + Parameter param = readNextParameter(); + + while ((param != null) && (result == null)) { + if (param.getName().equals(name)) { + result = param; + } + + param = readNextParameter(); + } + + this.stream.close(); + } + + return result; + } + + /** + * Reads the next parameter available or null. + * + * @return The next parameter available or null. + * @throws IOException If the next parameter could not be read. + */ + public Parameter readNextParameter() throws IOException { + Parameter result = null; + + if (this.stream != null) { + try { + boolean readingName = true; + StringBuilder nameBuffer = new StringBuilder(); + StringBuilder valueBuffer = new StringBuilder(); + int nextChar = 0; + + while ((result == null) && (nextChar != -1)) { + nextChar = this.stream.read(); + + if (readingName) { + if (nextChar == '=') { + if (!nameBuffer.isEmpty()) { + readingName = false; + } else { + throw new IOException( + "Empty parameter name detected. Please check your form data"); + } + } else if (endOfCurrentParameterReached(nextChar)) { + if (!nameBuffer.isEmpty()) { + result = + FormUtils.create( + nameBuffer, null, this.decode, this.characterSet); + } else if (nextChar == -1) { + // Do nothing and return a null preference + } else { + Context.getCurrentLogger() + .fine( + "Empty parameter name detected. Please check your form data"); + } + } else { + nameBuffer.append((char) nextChar); + } + } else { + // reading value + if (endOfCurrentParameterReached(nextChar)) { + result = + FormUtils.create( + nameBuffer, + valueBuffer, + this.decode, + this.characterSet); + } else { + valueBuffer.append((char) nextChar); + } + } + } + } catch (UnsupportedEncodingException uee) { + throw new IOException("Unsupported encoding. Please contact the administrator"); + } + } + + return result; + } + + private boolean endOfCurrentParameterReached(int nextChar) { + return (nextChar == this.separator) || (nextChar == -1); + } + + /** + * Reads the parameters with the given name. If multiple values are found, a list is returned + * created. + * + * @param name The parameter name to match. + * @return The parameter value or list of values. + * @throws IOException If the parameters could not be read. + */ + public Object readParameter(final String name) throws IOException { + + NamedValuesCollector collector = new NamedValuesCollector(name); + + if (this.stream != null) { + Parameter parameter; + + while ((parameter = readNextParameter()) != null) { + collector.collect(parameter); + } + + this.stream.close(); + } + + return collector.getCollectedValues().get(name); + } + + /** + * Reads the parameters whose name is a key in the given map. If a matching parameter is found, + * its value is put in the map. If multiple values are found, a list is created and set in the + * map. + * + * @param parameters The parameters map controlling the reading. + * @throws IOException If the parameters could not be read. + */ + public void readParameters(Map parameters) throws IOException { + if (this.stream != null) { + NamedValuesCollector collector = new NamedValuesCollector(parameters); + + Parameter param; + while ((param = readNextParameter()) != null) { + collector.collect(param); + } + + this.stream.close(); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/FormUtils.java b/org.restlet/src/main/java/org/restlet/engine/util/FormUtils.java index abe6899fd1..aa2bb35f68 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/FormUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/FormUtils.java @@ -1,239 +1,256 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; -import org.restlet.Context; -import org.restlet.data.*; -import org.restlet.representation.Representation; - import java.io.IOException; import java.util.Iterator; import java.util.Map; import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.data.CharacterSet; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Parameter; +import org.restlet.data.Reference; +import org.restlet.representation.Representation; /** * Representation of a Web form containing submitted parameters. - * + * * @author Jerome Louvel */ public class FormUtils { - /** - * Creates a parameter. - * - * @param name The parameter name buffer. - * @param value The parameter value buffer (can be null). - * @param decode If true, the name and values are decoded with the given - * {@link CharacterSet}, if false, than nothing is decoded. - * @param characterSet The supported character encoding. - * @return The created parameter. - */ - public static Parameter create(CharSequence name, CharSequence value, boolean decode, CharacterSet characterSet) { - Parameter result = null; - - if (name != null) { - String nameStr; - if (decode) { - nameStr = Reference.decode(name.toString(), characterSet); - } else { - nameStr = name.toString(); - } - if (value != null) { - String valueStr; - if (decode) { - valueStr = Reference.decode(value.toString(), characterSet); - } else { - valueStr = value.toString(); - } - result = new Parameter(nameStr, valueStr); - } else { - result = new Parameter(nameStr, null); - } - } - return result; - } - - /** - * Reads the first parameter with the given name. - * - * @param post The web form representation. - * @param name The parameter name to match. - * @return The parameter. - * @throws IOException - */ - public static Parameter getFirstParameter(Representation post, String name) throws IOException { - if (!post.isAvailable()) { - throw new IllegalStateException( - "The Web form cannot be parsed as no fresh content is available. If this entity has been already read once, caching of the entity is required"); - } - - return new FormReader(post).readFirstParameter(name); - } - - /** - * Reads the first parameter with the given name. - * - * @param query The query string. - * @param name The parameter name to match. - * @param characterSet The supported character encoding. - * @param separator The separator character to append between parameters. - * @param decode Indicates if the parameters should be decoded using the - * given character set. - * @return The parameter. - * @throws IOException - */ - public static Parameter getFirstParameter(String query, String name, CharacterSet characterSet, char separator, - boolean decode) throws IOException { - return new FormReader(query, characterSet, separator, decode).readFirstParameter(name); - } - - /** - * Reads the parameters with the given name.
- * If multiple values are found, a list is returned created. - * - * @param form The web form representation. - * @param name The parameter name to match. - * @return The parameter value or list of values. - * @throws IOException If the parameters could not be read. - */ - public static Object getParameter(Representation form, String name) throws IOException { - if (!form.isAvailable()) { - throw new IllegalStateException( - "The Web form cannot be parsed as no fresh content is available. If this entity has been already read once, caching of the entity is required"); - } - - return new FormReader(form).readParameter(name); - } - - /** - * Reads the parameters with the given name.
- * If multiple values are found, a list is returned created. - * - * @param query The query string. - * @param name The parameter name to match. - * @param characterSet The supported character encoding. - * @param separator The separator character to append between parameters. - * @param decode Indicates if the parameters should be decoded using the - * given character set. s * @return The parameter value or - * list of values. - * @throws IOException If the parameters could not be read. - */ - public static Object getParameter(String query, String name, CharacterSet characterSet, char separator, - boolean decode) throws IOException { - return new FormReader(query, characterSet, separator, decode).readParameter(name); - } - - /** - * Reads the parameters whose name is a key in the given map.
- * If a matching parameter is found, its value is put in the map.
- * If multiple values are found, a list is created and set in the map. - * - * @param post The web form representation. - * @param parameters The parameters map controlling the reading. - * @throws IOException If the parameters could not be read. - */ - public static void getParameters(Representation post, Map parameters) throws IOException { - if (!post.isAvailable()) { - throw new IllegalStateException( - "The Web form cannot be parsed as no fresh content is available. If this entity has been already read once, caching of the entity is required"); - } - - new FormReader(post).readParameters(parameters); - } - - /** - * Reads the parameters whose name is a key in the given map.
- * If a matching parameter is found, its value is put in the map.
- * If multiple values are found, a list is created and set in the map. - * - * @param parametersString The query string. - * @param parameters The parameters map controlling the reading. - * @param characterSet The supported character encoding. - * @param separator The separator character to append between parameters. - * @param decode Indicates if the parameters should be decoded using - * the given character set. - * @throws IOException If the parameters could not be read. - */ - public static void getParameters(String parametersString, Map parameters, CharacterSet characterSet, - char separator, boolean decode) throws IOException { - new FormReader(parametersString, characterSet, separator, decode).readParameters(parameters); - } - - /** - * Indicates if the searched parameter is specified in the given media range. - * - * @param searchedParam The searched parameter. - * @param mediaRange The media range to inspect. - * @return True if the searched parameter is specified in the given media range. - */ - public static boolean isParameterFound(Parameter searchedParam, MediaType mediaRange) { - boolean result = false; - - for (Iterator iter = mediaRange.getParameters().iterator(); !result && iter.hasNext();) { - result = searchedParam.equals(iter.next()); - } - - return result; - } - - /** - * Parses a post into a given form. - * - * @param form The target form. - * @param post The posted form. - * @param decode Indicates if the parameters should be decoded. - */ - public static void parse(Form form, Representation post, boolean decode) { - if (post != null) { - if (post.isAvailable()) { - FormReader fr = null; - - try { - fr = new FormReader(post, decode); - } catch (IOException ioe) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to create a form reader. Parsing aborted.", - ioe); - } - - if (fr != null) { - fr.addParameters(form); - } - } else { - Context.getCurrentLogger().log(Level.FINE, - "The form wasn't changed as the given representation isn't available."); - } - } - } - - /** - * Parses a parameters string into a given form. - * - * @param form The target form. - * @param parametersString The parameters string. - * @param characterSet The supported character encoding. - * @param decode Indicates if the parameters should be decoded using - * the given character set. - * @param separator The separator character to append between parameters. - */ - public static void parse(Form form, String parametersString, CharacterSet characterSet, boolean decode, - char separator) { - if ((parametersString != null) && !parametersString.isEmpty()) { - FormReader fr = new FormReader(parametersString, characterSet, separator, decode); - fr.addParameters(form); - } - } - - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e., it isn't instantiable and extensible. - */ - private FormUtils() { - } + /** + * Creates a parameter. + * + * @param name The parameter name buffer. + * @param value The parameter value buffer (can be null). + * @param decode If true, the name and values are decoded with the given {@link CharacterSet}, + * if false, than nothing is decoded. + * @param characterSet The supported character encoding. + * @return The created parameter. + */ + public static Parameter create( + CharSequence name, CharSequence value, boolean decode, CharacterSet characterSet) { + Parameter result = null; + + if (name != null) { + String nameStr; + if (decode) { + nameStr = Reference.decode(name.toString(), characterSet); + } else { + nameStr = name.toString(); + } + if (value != null) { + String valueStr; + if (decode) { + valueStr = Reference.decode(value.toString(), characterSet); + } else { + valueStr = value.toString(); + } + result = new Parameter(nameStr, valueStr); + } else { + result = new Parameter(nameStr, null); + } + } + return result; + } + + /** + * Reads the first parameter with the given name. + * + * @param post The web form representation. + * @param name The parameter name to match. + * @return The parameter. + * @throws IOException + */ + public static Parameter getFirstParameter(Representation post, String name) throws IOException { + if (!post.isAvailable()) { + throw new IllegalStateException( + "The Web form cannot be parsed as no fresh content is available. If this entity has been already read once, caching of the entity is required"); + } + + return new FormReader(post).readFirstParameter(name); + } + + /** + * Reads the first parameter with the given name. + * + * @param query The query string. + * @param name The parameter name to match. + * @param characterSet The supported character encoding. + * @param separator The separator character to append between parameters. + * @param decode Indicates if the parameters should be decoded using the given character set. + * @return The parameter. + * @throws IOException + */ + public static Parameter getFirstParameter( + String query, String name, CharacterSet characterSet, char separator, boolean decode) + throws IOException { + return new FormReader(query, characterSet, separator, decode).readFirstParameter(name); + } + + /** + * Reads the parameters with the given name.
+ * If multiple values are found, a list is returned created. + * + * @param form The web form representation. + * @param name The parameter name to match. + * @return The parameter value or list of values. + * @throws IOException If the parameters could not be read. + */ + public static Object getParameter(Representation form, String name) throws IOException { + if (!form.isAvailable()) { + throw new IllegalStateException( + "The Web form cannot be parsed as no fresh content is available. If this entity has been already read once, caching of the entity is required"); + } + + return new FormReader(form).readParameter(name); + } + + /** + * Reads the parameters with the given name.
+ * If multiple values are found, a list is returned created. + * + * @param query The query string. + * @param name The parameter name to match. + * @param characterSet The supported character encoding. + * @param separator The separator character to append between parameters. + * @param decode Indicates if the parameters should be decoded using the given character set. s + * * @return The parameter value or list of values. + * @throws IOException If the parameters could not be read. + */ + public static Object getParameter( + String query, String name, CharacterSet characterSet, char separator, boolean decode) + throws IOException { + return new FormReader(query, characterSet, separator, decode).readParameter(name); + } + + /** + * Reads the parameters whose name is a key in the given map.
+ * If a matching parameter is found, its value is put in the map.
+ * If multiple values are found, a list is created and set in the map. + * + * @param post The web form representation. + * @param parameters The parameters map controlling the reading. + * @throws IOException If the parameters could not be read. + */ + public static void getParameters(Representation post, Map parameters) + throws IOException { + if (!post.isAvailable()) { + throw new IllegalStateException( + "The Web form cannot be parsed as no fresh content is available. If this entity has been already read once, caching of the entity is required"); + } + + new FormReader(post).readParameters(parameters); + } + + /** + * Reads the parameters whose name is a key in the given map.
+ * If a matching parameter is found, its value is put in the map.
+ * If multiple values are found, a list is created and set in the map. + * + * @param parametersString The query string. + * @param parameters The parameters map controlling the reading. + * @param characterSet The supported character encoding. + * @param separator The separator character to append between parameters. + * @param decode Indicates if the parameters should be decoded using the given character set. + * @throws IOException If the parameters could not be read. + */ + public static void getParameters( + String parametersString, + Map parameters, + CharacterSet characterSet, + char separator, + boolean decode) + throws IOException { + new FormReader(parametersString, characterSet, separator, decode) + .readParameters(parameters); + } + + /** + * Indicates if the searched parameter is specified in the given media range. + * + * @param searchedParam The searched parameter. + * @param mediaRange The media range to inspect. + * @return True if the searched parameter is specified in the given media range. + */ + public static boolean isParameterFound(Parameter searchedParam, MediaType mediaRange) { + boolean result = false; + + for (Iterator iter = mediaRange.getParameters().iterator(); + !result && iter.hasNext(); ) { + result = searchedParam.equals(iter.next()); + } + + return result; + } + + /** + * Parses a post into a given form. + * + * @param form The target form. + * @param post The posted form. + * @param decode Indicates if the parameters should be decoded. + */ + public static void parse(Form form, Representation post, boolean decode) { + if (post != null) { + if (post.isAvailable()) { + FormReader fr = null; + + try { + fr = new FormReader(post, decode); + } catch (IOException ioe) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Unable to create a form reader. Parsing aborted.", + ioe); + } + + if (fr != null) { + fr.addParameters(form); + } + } else { + Context.getCurrentLogger() + .log( + Level.FINE, + "The form wasn't changed as the given representation isn't available."); + } + } + } + + /** + * Parses a parameters string into a given form. + * + * @param form The target form. + * @param parametersString The parameters string. + * @param characterSet The supported character encoding. + * @param decode Indicates if the parameters should be decoded using the given character set. + * @param separator The separator character to append between parameters. + */ + public static void parse( + Form form, + String parametersString, + CharacterSet characterSet, + boolean decode, + char separator) { + if ((parametersString != null) && !parametersString.isEmpty()) { + FormReader fr = new FormReader(parametersString, characterSet, separator, decode); + fr.addParameters(form); + } + } + + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private FormUtils() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/ImmutableDate.java b/org.restlet/src/main/java/org/restlet/engine/util/ImmutableDate.java index 9e077da20e..f935c749e9 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/ImmutableDate.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/ImmutableDate.java @@ -1,105 +1,102 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import java.util.Date; /** * Class acting as an immutable date class based on the {@link Date} class. - * - * Throws {@link UnsupportedOperationException} when mutable methods are - * invoked. - * + * + *

Throws {@link UnsupportedOperationException} when mutable methods are invoked. + * * @author Piyush Purang (ppurang@gmail.com) * @see java.util.Date */ public final class ImmutableDate extends Date { - private static final long serialVersionUID = -5946186780670229206L; - - /** - * Private constructor. A factory method is provided. - * - * @param date date to be made immutable - */ - public ImmutableDate(Date date) { - super(date.getTime()); - } + private static final long serialVersionUID = -5946186780670229206L; - /** {@inheritDoc} */ - @Override - public Object clone() throws UnsupportedOperationException { - throw new UnsupportedOperationException("ImmutableDate is immutable"); - } + /** + * Private constructor. A factory method is provided. + * + * @param date date to be made immutable + */ + public ImmutableDate(Date date) { + super(date.getTime()); + } - /** - * As an ImmutableDate is immutable, this method throws an - * UnsupportedOperationException exception. - */ - @Override - public void setDate(int arg0) { - throw new UnsupportedOperationException("ImmutableDate is immutable"); - } + /** {@inheritDoc} */ + @Override + public Object clone() throws UnsupportedOperationException { + throw new UnsupportedOperationException("ImmutableDate is immutable"); + } - /** - * As an ImmutableDate is immutable, this method throws an - * UnsupportedOperationException exception. - */ - @Override - public void setHours(int arg0) { - throw new UnsupportedOperationException("ImmutableDate is immutable"); - } + /** + * As an ImmutableDate is immutable, this method throws an UnsupportedOperationException + * exception. + */ + @Override + public void setDate(int arg0) { + throw new UnsupportedOperationException("ImmutableDate is immutable"); + } - /** - * As an ImmutableDate is immutable, this method throws an - * UnsupportedOperationException exception. - */ - @Override - public void setMinutes(int arg0) { - throw new UnsupportedOperationException("ImmutableDate is immutable"); - } + /** + * As an ImmutableDate is immutable, this method throws an UnsupportedOperationException + * exception. + */ + @Override + public void setHours(int arg0) { + throw new UnsupportedOperationException("ImmutableDate is immutable"); + } - /** - * As an ImmutableDate is immutable, this method throws an - * UnsupportedOperationException exception. - */ - @Override - public void setMonth(int arg0) { - throw new UnsupportedOperationException("ImmutableDate is immutable"); - } + /** + * As an ImmutableDate is immutable, this method throws an UnsupportedOperationException + * exception. + */ + @Override + public void setMinutes(int arg0) { + throw new UnsupportedOperationException("ImmutableDate is immutable"); + } - /** - * As an ImmutableDate is immutable, this method throws an - * UnsupportedOperationException exception. - */ - @Override - public void setSeconds(int arg0) { - throw new UnsupportedOperationException("ImmutableDate is immutable"); - } + /** + * As an ImmutableDate is immutable, this method throws an UnsupportedOperationException + * exception. + */ + @Override + public void setMonth(int arg0) { + throw new UnsupportedOperationException("ImmutableDate is immutable"); + } - /** - * As an ImmutableDate is immutable, this method throws an - * UnsupportedOperationException exception. - */ - @Override - public void setTime(long arg0) { - throw new UnsupportedOperationException("ImmutableDate is immutable"); - } + /** + * As an ImmutableDate is immutable, this method throws an UnsupportedOperationException + * exception. + */ + @Override + public void setSeconds(int arg0) { + throw new UnsupportedOperationException("ImmutableDate is immutable"); + } - /** - * As an ImmutableDate is immutable, this method throws an - * UnsupportedOperationException exception. - */ - @Override - public void setYear(int arg0) { - throw new UnsupportedOperationException("ImmutableDate is immutable"); - } + /** + * As an ImmutableDate is immutable, this method throws an UnsupportedOperationException + * exception. + */ + @Override + public void setTime(long arg0) { + throw new UnsupportedOperationException("ImmutableDate is immutable"); + } + /** + * As an ImmutableDate is immutable, this method throws an UnsupportedOperationException + * exception. + */ + @Override + public void setYear(int arg0) { + throw new UnsupportedOperationException("ImmutableDate is immutable"); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/InternetDateFormat.java b/org.restlet/src/main/java/org/restlet/engine/util/InternetDateFormat.java index c17a6d6675..587ab6d657 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/InternetDateFormat.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/InternetDateFormat.java @@ -1,458 +1,448 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; -import java.text.*; -import java.util.*; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.SimpleTimeZone; +import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * This class handles Internet date/time strings in accordance with RFC 3339. It - * provides static methods to convert from various Java constructs (long, Date, - * and Calendar) to RFC 3339 format strings and to parse these strings back into - * the same Java constructs. - *

- * In addition to the static utility methods, this class also wraps a Calendar - * object allowing this class to be used as a value object in place of a Java - * construct. - *

- * Strings are parsed in accordance with the RFC 3339 format: - * + * This class handles Internet date/time strings in accordance with RFC 3339. It provides static + * methods to convert from various Java constructs (long, Date, and Calendar) to RFC 3339 format + * strings and to parse these strings back into the same Java constructs. + * + *

In addition to the static utility methods, this class also wraps a Calendar object allowing + * this class to be used as a value object in place of a Java construct. + * + *

Strings are parsed in accordance with the RFC 3339 format: + * *

  * YYYY-MM-DD(T|t|\s)hh:mm:ss[.ddd][tzd]
  * 
- * - * The tzd represents the time zone designator and is either an - * upper or lower case 'Z' indicating UTC or a signed hh:mm offset. - * + * + * The tzd represents the time zone designator and is either an upper or lower case 'Z' + * indicating UTC or a signed hh:mm offset. + * * @author Frank Hellwig (frank@hellwig.org) */ public class InternetDateFormat extends DateFormat { - private static volatile DecimalFormat df2 = new DecimalFormat("00"); - - private static volatile DecimalFormat df4 = new DecimalFormat("0000"); - - /** The Regex pattern to match. */ - private static volatile Pattern pattern; - - private static final long serialVersionUID = 1L; - - /** - * A time zone with zero offset and no DST. - */ - public static final TimeZone UTC = new SimpleTimeZone(0, "Z"); - - static { - String reDate = "(\\d{4})-(\\d{2})-(\\d{2})"; - String reTime = "(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?"; - String reZone = "(?:([zZ])|(?:(\\+|\\-)(\\d{2}):(\\d{2})))"; - String re = reDate + "[tT\\s]" + reTime + reZone; - pattern = Pattern.compile(re); - } - - /** - * Returns the current date and time as an RFC 3339 date/time string using the - * UTC (Z) time zone. - * - * @return an RFC 3339 date/time string (does not include milliseconds) - */ - public static String now() { - return now(UTC); - } - - /** - * Returns the current date and time as an RFC 3339 date/time string using the - * specified time zone. - * - * @param zone the time zone to use - * @return an RFC 3339 date/time string (does not include milliseconds) - */ - public static String now(TimeZone zone) { - return toString(System.currentTimeMillis(), zone); - } - - /** - * Our private parse utility that parses the string, clears the calendar, and - * then sets the fields. - * - * @param s the string to parse - * @param cal the calendar object to populate - * @throws IllegalArgumentException if the string is not a valid RFC 3339 - * date/time string - */ - private static void parse(String s, Calendar cal) { - Matcher m = pattern.matcher(s); - if (!m.matches()) { - throw new IllegalArgumentException("Invalid date/time: " + s); - } - cal.clear(); - cal.set(Calendar.YEAR, Integer.parseInt(m.group(1))); - cal.set(Calendar.MONTH, Integer.parseInt(m.group(2)) - 1); - cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(m.group(3))); - cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(m.group(4))); - cal.set(Calendar.MINUTE, Integer.parseInt(m.group(5))); - cal.set(Calendar.SECOND, Integer.parseInt(m.group(6))); - if (m.group(7) != null) { - float fraction = Float.parseFloat(m.group(7)); - cal.set(Calendar.MILLISECOND, (int) (fraction * 1000F)); - } - if (m.group(8) != null) { - cal.setTimeZone(new SimpleTimeZone(0, "Z")); - } else { - int sign = m.group(9).equals("-") ? -1 : 1; - int tzhour; - tzhour = Integer.parseInt(m.group(10)); - int tzminute = Integer.parseInt(m.group(11)); - int offset = sign * ((tzhour * 60) + tzminute); - String id = Integer.toString(offset); - cal.setTimeZone(new SimpleTimeZone(offset * 60000, id)); - } - } - - /** - * Parses an RFC 3339 date/time string to a Calendar object. - * - * @param s the string to parse - * @return the Calendar object - * @throws IllegalArgumentException if the string is not a valid RFC 3339 - * date/time string - */ - public static Calendar parseCalendar(String s) { - Calendar cal = new GregorianCalendar(); - parse(s, cal); - return cal; - } - - /** - * Parses an RFC 3339 date/time string to a Date object. - * - * @param s the string to parse - * @return the Date object - * @throws IllegalArgumentException if the string is not a valid RFC 3339 - * date/time string - */ - public static Date parseDate(String s) { - Calendar cal = new GregorianCalendar(); - parse(s, cal); - return cal.getTime(); - } - - /** - * Parses an RFC 3339 date/time string to a millisecond time value. - * - * @param s the string to parse - * @return the millisecond time value - * @throws IllegalArgumentException if the string is not a valid RFC 3339 - * date/time string - */ - public static long parseTime(String s) { - Calendar cal = new GregorianCalendar(); - parse(s, cal); - return cal.getTimeInMillis(); - } - - /** - * Converts the specified Calendar object to an RFC 3339 date/time string. - * Unlike the toString methods for Date and long, no additional variant of this - * method taking a time zone is provided since the time zone is built into the - * Calendar object. - * - * @param cal the Calendar object - * @return an RFC 3339 date/time string (does not include milliseconds) - */ - public static String toString(Calendar cal) { - StringBuilder buf = new StringBuilder(); - buf.append(df4.format(cal.get(Calendar.YEAR))); - buf.append("-"); - buf.append(df2.format(cal.get(Calendar.MONTH) + 1L)); - buf.append("-"); - buf.append(df2.format(cal.get(Calendar.DAY_OF_MONTH))); - buf.append("T"); - buf.append(df2.format(cal.get(Calendar.HOUR_OF_DAY))); - buf.append(":"); - buf.append(df2.format(cal.get(Calendar.MINUTE))); - buf.append(":"); - buf.append(df2.format(cal.get(Calendar.SECOND))); - - int ms = cal.get(Calendar.MILLISECOND); - if (ms != 0) { - buf.append(".").append((int) (ms / 10F)); - } - - int tzminute = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 60000; - if (tzminute == 0) { - buf.append("Z"); - } else { - if (tzminute < 0) { - tzminute = -tzminute; - buf.append("-"); - } else { - buf.append("+"); - } - int tzhour = tzminute / 60; - tzminute -= tzhour * 60; - buf.append(df2.format(tzhour)); - buf.append(":"); - buf.append(df2.format(tzminute)); - } - return buf.toString(); - } - - /** - * Converts the specified Date object to an RFC 3339 date/time string using the - * UTC (Z) time zone. - * - * @param date the Date object - * @return an RFC 3339 date/time string (does not include milliseconds) - */ - public static String toString(Date date) { - return toString(date, UTC); - } - - /** - * Converts the specified Date object to an RFC 3339 date/time string using the - * specified time zone. - * - * @param date the Date object - * @param zone the time zone to use - * @return an RFC 3339 date/time string (does not include milliseconds) - */ - public static String toString(Date date, TimeZone zone) { - InternetDateFormat dt = new InternetDateFormat(date, zone); - return dt.toString(); - } - - /** - * Converts the specified millisecond time value to an RFC 3339 date/time string - * using the UTC (Z) time zone. - * - * @param time the millisecond time value - * @return an RFC 3339 date/time string (does not include milliseconds) - */ - public static String toString(long time) { - return toString(time, UTC); - } - - /** - * Converts the specified millisecond time value to an RFC 3339 date/time string - * using the specified time zone. - * - * @param time the millisecond time value - * @param zone the time zone to use - * @return an RFC 3339 date/time string (does not include milliseconds) - */ - public static String toString(long time, TimeZone zone) { - InternetDateFormat dt = new InternetDateFormat(time, zone); - return dt.toString(); - } - - /** - * Creates a new InternetDateFormat object from the specified Date object using - * the UTC (Z) time zone. - * - * @param date the Date object - * @return the InternetDateFormat object - */ - public static InternetDateFormat valueOf(Date date) { - return new InternetDateFormat(date); - } - - /** - * Creates a new InternetDateFormat object from the specified Date object using - * the specified time zone. - * - * @param date the Date object - * @param zone the time zone to use - * @return the InternetDateFormat object - */ - public static InternetDateFormat valueOf(Date date, TimeZone zone) { - return new InternetDateFormat(date, zone); - } - - /** - * Creates a new InternetDateFormat object from the specified millisecond time - * value using the UTC (Z) time zone. - * - * @param time the millisecond time value - * @return the InternetDateFormat object - */ - public static InternetDateFormat valueOf(long time) { - return new InternetDateFormat(time); - } - - /** - * Creates a new InternetDateFormat object from the specified millisecond time - * value using the specified time zone. - * - * @param time the millisecond time value - * @param zone the time zone to use - * @return the InternetDateFormat object - */ - public static InternetDateFormat valueOf(long time, TimeZone zone) { - return new InternetDateFormat(time, zone); - } - - /** - * Creates a new InternetDateFormat object by parsing an RFC 3339 date/time - * string. - * - * @param s the string to parse - * @return the InternetDateFormat object - * @throws IllegalArgumentException if the string is not a valid RFC 3339 - * date/time string - */ - public static InternetDateFormat valueOf(String s) { - return new InternetDateFormat(s); - } - - /** - * The Calendar object that allows this class to act as a value holder. - */ - private Calendar cal; - - /** - * Creates a new InternetDateFormat object set to the current time using the UTC - * (Z) time zone. - */ - public InternetDateFormat() { - this(UTC); - } - - /** - * Creates a new InternetDateFormat object initialized from a Calendar object. - * The specified calendar object is cloned thereby isolating this - * InternetDateFormat object from any changes made to the specified calendar - * object after calling this constructor. - * - * @param cal the Calendar object - */ - public InternetDateFormat(Calendar cal) { - this.cal = (Calendar) cal.clone(); - } - - /** - * Creates a new InternetDateFormat object initialized from a Date object using - * the UTC (Z) time zone. - * - * @param date the Date object - */ - public InternetDateFormat(Date date) { - this(date, UTC); - } - - /** - * Creates a new InternetDateFormat object initialized from a Date object using - * the specified time zone. - * - * @param date the Date object - * @param zone the time zone to use - */ - public InternetDateFormat(Date date, TimeZone zone) { - cal = new GregorianCalendar(zone); - cal.setTime(date); - } - - /** - * Creates a new InternetDateFormat object initialized from a millisecond time - * value using the UTC (Z) time zone. - * - * @param time the millisecond time value - */ - public InternetDateFormat(long time) { - this(time, UTC); - } - - /** - * Creates a new InternetDateFormat object initialized from a millisecond time - * value using the specified time zone. - * - * @param time the millisecond time value - * @param zone the time zone to use - */ - public InternetDateFormat(long time, TimeZone zone) { - cal = new GregorianCalendar(zone); - cal.setTimeInMillis(time); - } - - /** - * Creates a new InternetDateFormat object by parsing an RFC 3339 date/time - * string. - * - * @param s the string to parse - * @throws IllegalArgumentException if the string is not a valid RFC 3339 - * date/time string - */ - public InternetDateFormat(String s) { - cal = parseCalendar(s); - } - - /** - * Creates a new InternetDateFormat object set to the current time using the - * specified time zone. - * - * @param zone the time zone to use - */ - public InternetDateFormat(TimeZone zone) { - cal = new GregorianCalendar(zone); - } - - @Override - public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { - return toAppendTo.append(valueOf(date)); - } - - /** - * Gets the Calendar object wrapped by this InternetDateFormat object. - * - * @return the cloned Calendar object - */ - public Calendar getCalendar() { - return (Calendar) cal.clone(); - } - - /** - * Gets the value of this InternetDateFormat object as a Date object. - * - * @return the Date object - */ - public Date getDate() { - return cal.getTime(); - } - - /** - * Gets the value of this InternetDateFormat object as millisecond time value. - * - * @return the millisecond time value - */ - public long getTime() { - return cal.getTimeInMillis(); - } - - @Override - public Date parse(String source) throws ParseException { - return parse(source, (ParsePosition) null); - } - - @Override - public Date parse(String source, ParsePosition pos) { - return parseDate(source); - } - - /** - * Converts this InternetDateFormat object to an RFC 3339 date/time string. - * - * @return an RFC 3339 date/time string (does not include milliseconds) - */ - public String toString() { - return toString(cal); - } + private static volatile DecimalFormat df2 = new DecimalFormat("00"); + + private static volatile DecimalFormat df4 = new DecimalFormat("0000"); + + /** The Regex pattern to match. */ + private static volatile Pattern pattern; + + private static final long serialVersionUID = 1L; + + /** A time zone with zero offset and no DST. */ + public static final TimeZone UTC = new SimpleTimeZone(0, "Z"); + + static { + String reDate = "(\\d{4})-(\\d{2})-(\\d{2})"; + String reTime = "(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?"; + String reZone = "(?:([zZ])|(?:(\\+|\\-)(\\d{2}):(\\d{2})))"; + String re = reDate + "[tT\\s]" + reTime + reZone; + pattern = Pattern.compile(re); + } + + /** + * Returns the current date and time as an RFC 3339 date/time string using the UTC (Z) time + * zone. + * + * @return an RFC 3339 date/time string (does not include milliseconds) + */ + public static String now() { + return now(UTC); + } + + /** + * Returns the current date and time as an RFC 3339 date/time string using the specified time + * zone. + * + * @param zone the time zone to use + * @return an RFC 3339 date/time string (does not include milliseconds) + */ + public static String now(TimeZone zone) { + return toString(System.currentTimeMillis(), zone); + } + + /** + * Our private parse utility that parses the string, clears the calendar, and then sets the + * fields. + * + * @param s the string to parse + * @param cal the calendar object to populate + * @throws IllegalArgumentException if the string is not a valid RFC 3339 date/time string + */ + private static void parse(String s, Calendar cal) { + Matcher m = pattern.matcher(s); + if (!m.matches()) { + throw new IllegalArgumentException("Invalid date/time: " + s); + } + cal.clear(); + cal.set(Calendar.YEAR, Integer.parseInt(m.group(1))); + cal.set(Calendar.MONTH, Integer.parseInt(m.group(2)) - 1); + cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(m.group(3))); + cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(m.group(4))); + cal.set(Calendar.MINUTE, Integer.parseInt(m.group(5))); + cal.set(Calendar.SECOND, Integer.parseInt(m.group(6))); + if (m.group(7) != null) { + float fraction = Float.parseFloat(m.group(7)); + cal.set(Calendar.MILLISECOND, (int) (fraction * 1000F)); + } + if (m.group(8) != null) { + cal.setTimeZone(new SimpleTimeZone(0, "Z")); + } else { + int sign = m.group(9).equals("-") ? -1 : 1; + int tzhour; + tzhour = Integer.parseInt(m.group(10)); + int tzminute = Integer.parseInt(m.group(11)); + int offset = sign * ((tzhour * 60) + tzminute); + String id = Integer.toString(offset); + cal.setTimeZone(new SimpleTimeZone(offset * 60000, id)); + } + } + + /** + * Parses an RFC 3339 date/time string to a Calendar object. + * + * @param s the string to parse + * @return the Calendar object + * @throws IllegalArgumentException if the string is not a valid RFC 3339 date/time string + */ + public static Calendar parseCalendar(String s) { + Calendar cal = new GregorianCalendar(); + parse(s, cal); + return cal; + } + + /** + * Parses an RFC 3339 date/time string to a Date object. + * + * @param s the string to parse + * @return the Date object + * @throws IllegalArgumentException if the string is not a valid RFC 3339 date/time string + */ + public static Date parseDate(String s) { + Calendar cal = new GregorianCalendar(); + parse(s, cal); + return cal.getTime(); + } + + /** + * Parses an RFC 3339 date/time string to a millisecond time value. + * + * @param s the string to parse + * @return the millisecond time value + * @throws IllegalArgumentException if the string is not a valid RFC 3339 date/time string + */ + public static long parseTime(String s) { + Calendar cal = new GregorianCalendar(); + parse(s, cal); + return cal.getTimeInMillis(); + } + + /** + * Converts the specified Calendar object to an RFC 3339 date/time string. Unlike the toString + * methods for Date and long, no additional variant of this method taking a time zone is + * provided since the time zone is built into the Calendar object. + * + * @param cal the Calendar object + * @return an RFC 3339 date/time string (does not include milliseconds) + */ + public static String toString(Calendar cal) { + StringBuilder buf = new StringBuilder(); + buf.append(df4.format(cal.get(Calendar.YEAR))); + buf.append("-"); + buf.append(df2.format(cal.get(Calendar.MONTH) + 1L)); + buf.append("-"); + buf.append(df2.format(cal.get(Calendar.DAY_OF_MONTH))); + buf.append("T"); + buf.append(df2.format(cal.get(Calendar.HOUR_OF_DAY))); + buf.append(":"); + buf.append(df2.format(cal.get(Calendar.MINUTE))); + buf.append(":"); + buf.append(df2.format(cal.get(Calendar.SECOND))); + + int ms = cal.get(Calendar.MILLISECOND); + if (ms != 0) { + buf.append(".").append((int) (ms / 10F)); + } + + int tzminute = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 60000; + if (tzminute == 0) { + buf.append("Z"); + } else { + if (tzminute < 0) { + tzminute = -tzminute; + buf.append("-"); + } else { + buf.append("+"); + } + int tzhour = tzminute / 60; + tzminute -= tzhour * 60; + buf.append(df2.format(tzhour)); + buf.append(":"); + buf.append(df2.format(tzminute)); + } + return buf.toString(); + } + + /** + * Converts the specified Date object to an RFC 3339 date/time string using the UTC (Z) time + * zone. + * + * @param date the Date object + * @return an RFC 3339 date/time string (does not include milliseconds) + */ + public static String toString(Date date) { + return toString(date, UTC); + } + + /** + * Converts the specified Date object to an RFC 3339 date/time string using the specified time + * zone. + * + * @param date the Date object + * @param zone the time zone to use + * @return an RFC 3339 date/time string (does not include milliseconds) + */ + public static String toString(Date date, TimeZone zone) { + InternetDateFormat dt = new InternetDateFormat(date, zone); + return dt.toString(); + } + + /** + * Converts the specified millisecond time value to an RFC 3339 date/time string using the UTC + * (Z) time zone. + * + * @param time the millisecond time value + * @return an RFC 3339 date/time string (does not include milliseconds) + */ + public static String toString(long time) { + return toString(time, UTC); + } + + /** + * Converts the specified millisecond time value to an RFC 3339 date/time string using the + * specified time zone. + * + * @param time the millisecond time value + * @param zone the time zone to use + * @return an RFC 3339 date/time string (does not include milliseconds) + */ + public static String toString(long time, TimeZone zone) { + InternetDateFormat dt = new InternetDateFormat(time, zone); + return dt.toString(); + } + + /** + * Creates a new InternetDateFormat object from the specified Date object using the UTC (Z) time + * zone. + * + * @param date the Date object + * @return the InternetDateFormat object + */ + public static InternetDateFormat valueOf(Date date) { + return new InternetDateFormat(date); + } + + /** + * Creates a new InternetDateFormat object from the specified Date object using the specified + * time zone. + * + * @param date the Date object + * @param zone the time zone to use + * @return the InternetDateFormat object + */ + public static InternetDateFormat valueOf(Date date, TimeZone zone) { + return new InternetDateFormat(date, zone); + } + + /** + * Creates a new InternetDateFormat object from the specified millisecond time value using the + * UTC (Z) time zone. + * + * @param time the millisecond time value + * @return the InternetDateFormat object + */ + public static InternetDateFormat valueOf(long time) { + return new InternetDateFormat(time); + } + + /** + * Creates a new InternetDateFormat object from the specified millisecond time value using the + * specified time zone. + * + * @param time the millisecond time value + * @param zone the time zone to use + * @return the InternetDateFormat object + */ + public static InternetDateFormat valueOf(long time, TimeZone zone) { + return new InternetDateFormat(time, zone); + } + + /** + * Creates a new InternetDateFormat object by parsing an RFC 3339 date/time string. + * + * @param s the string to parse + * @return the InternetDateFormat object + * @throws IllegalArgumentException if the string is not a valid RFC 3339 date/time string + */ + public static InternetDateFormat valueOf(String s) { + return new InternetDateFormat(s); + } + + /** The Calendar object that allows this class to act as a value holder. */ + private Calendar cal; + + /** + * Creates a new InternetDateFormat object set to the current time using the UTC (Z) time zone. + */ + public InternetDateFormat() { + this(UTC); + } + + /** + * Creates a new InternetDateFormat object initialized from a Calendar object. The specified + * calendar object is cloned thereby isolating this InternetDateFormat object from any changes + * made to the specified calendar object after calling this constructor. + * + * @param cal the Calendar object + */ + public InternetDateFormat(Calendar cal) { + this.cal = (Calendar) cal.clone(); + } + + /** + * Creates a new InternetDateFormat object initialized from a Date object using the UTC (Z) time + * zone. + * + * @param date the Date object + */ + public InternetDateFormat(Date date) { + this(date, UTC); + } + + /** + * Creates a new InternetDateFormat object initialized from a Date object using the specified + * time zone. + * + * @param date the Date object + * @param zone the time zone to use + */ + public InternetDateFormat(Date date, TimeZone zone) { + cal = new GregorianCalendar(zone); + cal.setTime(date); + } + + /** + * Creates a new InternetDateFormat object initialized from a millisecond time value using the + * UTC (Z) time zone. + * + * @param time the millisecond time value + */ + public InternetDateFormat(long time) { + this(time, UTC); + } + + /** + * Creates a new InternetDateFormat object initialized from a millisecond time value using the + * specified time zone. + * + * @param time the millisecond time value + * @param zone the time zone to use + */ + public InternetDateFormat(long time, TimeZone zone) { + cal = new GregorianCalendar(zone); + cal.setTimeInMillis(time); + } + + /** + * Creates a new InternetDateFormat object by parsing an RFC 3339 date/time string. + * + * @param s the string to parse + * @throws IllegalArgumentException if the string is not a valid RFC 3339 date/time string + */ + public InternetDateFormat(String s) { + cal = parseCalendar(s); + } + + /** + * Creates a new InternetDateFormat object set to the current time using the specified time + * zone. + * + * @param zone the time zone to use + */ + public InternetDateFormat(TimeZone zone) { + cal = new GregorianCalendar(zone); + } + + @Override + public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { + return toAppendTo.append(valueOf(date)); + } + + /** + * Gets the Calendar object wrapped by this InternetDateFormat object. + * + * @return the cloned Calendar object + */ + public Calendar getCalendar() { + return (Calendar) cal.clone(); + } + + /** + * Gets the value of this InternetDateFormat object as a Date object. + * + * @return the Date object + */ + public Date getDate() { + return cal.getTime(); + } + + /** + * Gets the value of this InternetDateFormat object as millisecond time value. + * + * @return the millisecond time value + */ + public long getTime() { + return cal.getTimeInMillis(); + } + + @Override + public Date parse(String source) throws ParseException { + return parse(source, (ParsePosition) null); + } + + @Override + public Date parse(String source, ParsePosition pos) { + return parseDate(source); + } + + /** + * Converts this InternetDateFormat object to an RFC 3339 date/time string. + * + * @return an RFC 3339 date/time string (does not include milliseconds) + */ + public String toString() { + return toString(cal); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/ListUtils.java b/org.restlet/src/main/java/org/restlet/engine/util/ListUtils.java index 7715de55cd..ac9a22ebb0 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/ListUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/ListUtils.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import java.util.ArrayList; @@ -14,34 +13,32 @@ /** * Emulate List functions missing from GWT port of List - * + * * @author Rob Heittman */ public class ListUtils { - /** - * Unlike List.subList(), which returns a live view of a set of List elements, - * this method returns a new copy of the list. List.subList() is not available - * in GWT 1.5 and was removed on purpose. - * - * @param list The source List - * @param fromIndex Starting index in the source List - * @param toIndex Ending index in the source List - * @throws IndexOutOfBoundsException Call exceeds the bounds of the source List - * @throws IllegalArgumentException fromIndex and toIndex are not in sequence - * @return a copy of the selected range - */ - public static List copySubList(List list, int fromIndex, int toIndex) { - if (fromIndex < 0) - throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); - if (toIndex > list.size()) - throw new IndexOutOfBoundsException("toIndex = " + toIndex); - if (fromIndex > toIndex) - throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); - ArrayList subList = new ArrayList(); - for (int i = fromIndex; i <= toIndex; i++) { - subList.add(list.get(i)); - } - return subList; - } - + /** + * Unlike List.subList(), which returns a live view of a set of List elements, this method + * returns a new copy of the list. List.subList() is not available in GWT 1.5 and was removed on + * purpose. + * + * @param list The source List + * @param fromIndex Starting index in the source List + * @param toIndex Ending index in the source List + * @throws IndexOutOfBoundsException Call exceeds the bounds of the source List + * @throws IllegalArgumentException fromIndex and toIndex are not in sequence + * @return a copy of the selected range + */ + public static List copySubList(List list, int fromIndex, int toIndex) { + if (fromIndex < 0) throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); + if (toIndex > list.size()) throw new IndexOutOfBoundsException("toIndex = " + toIndex); + if (fromIndex > toIndex) + throw new IllegalArgumentException( + "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); + ArrayList subList = new ArrayList<>(); + for (int i = fromIndex; i <= toIndex; i++) { + subList.add(list.get(i)); + } + return subList; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/MapResolver.java b/org.restlet/src/main/java/org/restlet/engine/util/MapResolver.java index 03f065a281..b118598cb2 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/MapResolver.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/MapResolver.java @@ -1,39 +1,37 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; -import org.restlet.util.Resolver; - import java.util.Map; +import org.restlet.util.Resolver; /** * Resolves variable values based on a map. - * + * * @author Jerome Louvel */ public class MapResolver extends Resolver { - /** The variables to use when formatting. */ - private final Map map; + /** The variables to use when formatting. */ + private final Map map; - /** - * Constructor. - * - * @param map The variables to use when formatting. - */ - public MapResolver(Map map) { - this.map = map; - } + /** + * Constructor. + * + * @param map The variables to use when formatting. + */ + public MapResolver(Map map) { + this.map = map; + } - @Override - public Object resolve(String variableName) { - return this.map.get(variableName); - } + @Override + public Object resolve(String variableName) { + return this.map.get(variableName); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/Pool.java b/org.restlet/src/main/java/org/restlet/engine/util/Pool.java index 6e5d09cfc9..d3a8576944 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/Pool.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/Pool.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import java.util.Queue; @@ -14,113 +13,104 @@ /** * Generic object pool. - * + * * @author Jerome Louvel - * * @param */ public abstract class Pool { - /** Store of reusable objects. */ - private final Queue store; - - /** - * Default constructor. - */ - public Pool() { - this.store = createStore(); - } - - /** - * Constructor. Pre-creates the minimum number of objects if needed using the - * {@link #preCreate(int)} method. - * - * @param initialSize The initial number of objects in the pool. - */ - public Pool(int initialSize) { - this(); - preCreate(initialSize); - } - - /** - * Checks in an object into the pool. - * - * @param object The object to check in. - */ - public void checkin(T object) { - if (object != null) { - clear(object); - this.store.offer(object); - } - } - - /** - * Checks out an object from the pool. Creates a new one if the pool is empty. - * - * @return An object from the pool. - */ - public T checkout() { - T result; - - if ((result = this.store.poll()) == null) { - result = createObject(); - } - - return result; - } - - /** - * Clears the store of reusable objects. - */ - public void clear() { - getStore().clear(); - } - - /** - * Clears the given object when it is checked in the pool. Does nothing by - * default. - * - * @param object The object to clear. - */ - protected void clear(T object) { - - } - - /** - * Creates a new reusable object. - * - * @return A new reusable object. - */ - protected abstract T createObject(); - - /** - * Creates the store of reusable objects. - * - * @return The store of reusable objects. - */ - protected Queue createStore() { - return new ConcurrentLinkedQueue(); - } - - /** - * Returns the store containing the reusable objects. - * - * @return The store containing the reusable objects. - */ - protected Queue getStore() { - return store; - } - - /** - * Pre-creates the initial objects using the {@link #createObject()} method and - * check them in the pool using the {@link #checkin(Object)} method. - * - * @param initialSize The initial number of objects. - */ - public void preCreate(int initialSize) { - for (int i = 0; i < initialSize; i++) { - checkin(createObject()); - } - } - + /** Store of reusable objects. */ + private final Queue store; + + /** Default constructor. */ + protected Pool() { + this.store = createStore(); + } + + /** + * Constructor. Pre-creates the minimum number of objects if needed using the {@link + * #preCreate(int)} method. + * + * @param initialSize The initial number of objects in the pool. + */ + protected Pool(int initialSize) { + this(); + preCreate(initialSize); + } + + /** + * Checks in an object into the pool. + * + * @param object The object to check in. + */ + public void checkin(T object) { + if (object != null) { + clear(object); + this.store.offer(object); + } + } + + /** + * Checks out an object from the pool. Creates a new one if the pool is empty. + * + * @return An object from the pool. + */ + public T checkout() { + T result; + + if ((result = this.store.poll()) == null) { + result = createObject(); + } + + return result; + } + + /** Clears the store of reusable objects. */ + public void clear() { + getStore().clear(); + } + + /** + * Clears the given object when it is checked in the pool. Does nothing by default. + * + * @param object The object to clear. + */ + protected void clear(T object) {} + + /** + * Creates a new reusable object. + * + * @return A new reusable object. + */ + protected abstract T createObject(); + + /** + * Creates the store of reusable objects. + * + * @return The store of reusable objects. + */ + protected Queue createStore() { + return new ConcurrentLinkedQueue<>(); + } + + /** + * Returns the store containing the reusable objects. + * + * @return The store containing the reusable objects. + */ + protected Queue getStore() { + return store; + } + + /** + * Pre-creates the initial objects using the {@link #createObject()} method and check them in + * the pool using the {@link #checkin(Object)} method. + * + * @param initialSize The initial number of objects. + */ + public void preCreate(int initialSize) { + for (int i = 0; i < initialSize; i++) { + checkin(createObject()); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/ReferenceUtils.java b/org.restlet/src/main/java/org/restlet/engine/util/ReferenceUtils.java index 899f5086e8..a14ac20469 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/ReferenceUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/ReferenceUtils.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import org.restlet.Request; @@ -18,89 +17,84 @@ /** * Utilities related to URI references. - * + * * @author Jerome Louvel */ public class ReferenceUtils { - /** - * Returns the request URI. - * - * @param resourceRef The resource reference. - * @param request The parent request. - * @return The absolute request URI. - */ - public static Reference update(Reference resourceRef, Request request) { - Reference result = resourceRef.isAbsolute() ? resourceRef : resourceRef.getTargetRef(); - - // Optionally update the request before formatting its URI - result = AuthenticatorUtils.updateReference(result, request.getChallengeResponse(), request); - - return result; - } - - /** - * Returns the request URI. - * - * @param resourceRef The resource reference. - * @param isProxied Indicates if the request goes through a proxy and requires - * an absolute URI. - * @param request The parent request. - * @return The absolute request URI. - */ - public static String format(Reference resourceRef, boolean isProxied, Request request) { - String result = null; - Reference requestRef = update(resourceRef, request); - - if (isProxied) { - result = requestRef.getIdentifier(); - } else { - if (requestRef.hasQuery()) { - result = requestRef.getPath() + "?" + requestRef.getQuery(); - } else { - result = requestRef.getPath(); - } - - if ((result == null) || (result.isEmpty())) { - result = "/"; - } - } - - return result; - } - - /** - * Returns the original reference especially by detecting potential proxy - * forwardings. - * - * @param resourceRef The request's resource reference. - * @param headers The set of request's headers. - * @return The original reference. - */ - public static Reference getOriginalRef(Reference resourceRef, Series

headers) { - Reference originalRef = resourceRef.getTargetRef(); - - if (headers == null) { - return originalRef; - } - - String value = headers.getFirstValue(HeaderConstants.HEADER_X_FORWARDED_PORT); - if (value != null) { - originalRef.setHostPort(Integer.parseInt(value)); - } - - value = headers.getFirstValue(HeaderConstants.HEADER_X_FORWARDED_PROTO); - if (value != null) { - originalRef.setScheme(value); - } - - return originalRef; - } - - /** - * Constructor. - */ - private ReferenceUtils() { - } - + /** + * Returns the request URI. + * + * @param resourceRef The resource reference. + * @param request The parent request. + * @return The absolute request URI. + */ + public static Reference update(Reference resourceRef, Request request) { + Reference result = resourceRef.isAbsolute() ? resourceRef : resourceRef.getTargetRef(); + + // Optionally update the request before formatting its URI + result = + AuthenticatorUtils.updateReference(result, request.getChallengeResponse(), request); + + return result; + } + + /** + * Returns the request URI. + * + * @param resourceRef The resource reference. + * @param isProxied Indicates if the request goes through a proxy and requires an absolute URI. + * @param request The parent request. + * @return The absolute request URI. + */ + public static String format(Reference resourceRef, boolean isProxied, Request request) { + String result = null; + Reference requestRef = update(resourceRef, request); + + if (isProxied) { + result = requestRef.getIdentifier(); + } else { + if (requestRef.hasQuery()) { + result = requestRef.getPath() + "?" + requestRef.getQuery(); + } else { + result = requestRef.getPath(); + } + + if ((result == null) || (result.isEmpty())) { + result = "/"; + } + } + + return result; + } + + /** + * Returns the original reference, especially by detecting potential proxy forwarding. + * + * @param resourceRef The request's resource reference. + * @param headers The set of request's headers. + * @return The original reference. + */ + public static Reference getOriginalRef(Reference resourceRef, Series
headers) { + Reference originalRef = resourceRef.getTargetRef(); + + if (headers == null) { + return originalRef; + } + + String value = headers.getFirstValue(HeaderConstants.HEADER_X_FORWARDED_PORT); + if (value != null) { + originalRef.setHostPort(Integer.parseInt(value)); + } + + value = headers.getFirstValue(HeaderConstants.HEADER_X_FORWARDED_PROTO); + if (value != null) { + originalRef.setScheme(value); + } + + return originalRef; + } + + /** Constructor. */ + private ReferenceUtils() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/SetUtils.java b/org.restlet/src/main/java/org/restlet/engine/util/SetUtils.java index e23c9610da..44fc34fce9 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/SetUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/SetUtils.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import java.util.Collections; @@ -15,22 +14,21 @@ /** * Utilities for manipulation of {@link Set}. - * + * * @author Manuel Boillod */ public class SetUtils { - /** - * Returns a new {@link java.util.HashSet} with the given elements - * - * @param elements The elements - * @return A new {@link java.util.HashSet} with the given elements - */ - @SafeVarargs - public static Set newHashSet(E... elements) { - HashSet set = new HashSet<>(elements.length); - Collections.addAll(set, elements); - return set; - } - + /** + * Returns a new {@link java.util.HashSet} with the given elements + * + * @param elements The elements + * @return A new {@link java.util.HashSet} with the given elements + */ + @SafeVarargs + public static Set newHashSet(E... elements) { + HashSet set = HashSet.newHashSet(elements.length); + Collections.addAll(set, elements); + return set; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/StringUtils.java b/org.restlet/src/main/java/org/restlet/engine/util/StringUtils.java index d836193698..9e689f6214 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/StringUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/StringUtils.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import java.nio.charset.StandardCharsets; @@ -22,601 +21,581 @@ */ public class StringUtils { - /** - * Represents an XML or HTML character entity reference. - * - * @author Thierry Boileau - */ - private static class CharacterEntity { - /** - * Character reference. - */ - private String name; - - /** - * Numeric character reference. - */ - private Integer numericValue; + /** + * Represents an XML or HTML character entity reference. + * + * @author Thierry Boileau + */ + private static class CharacterEntity { + /** Character reference. */ + private String name; - /** - * Constructor. - * - * @param numericValue The numeric value of the entity. - * @param name the name of the entity. - */ - public CharacterEntity(Integer numericValue, String name) { - super(); - this.numericValue = numericValue; - this.name = name; - } + /** Numeric character reference. */ + private Integer numericValue; - /** - * Returns the name of the entity. - * - * @return The name of the entity. - */ - public String getName() { - return name; - } + /** + * Constructor. + * + * @param numericValue The numeric value of the entity. + * @param name the name of the entity. + */ + public CharacterEntity(Integer numericValue, String name) { + super(); + this.numericValue = numericValue; + this.name = name; + } - /** - * Returns the numeric value of the entity. - * - * @return The numeric value of the entity. - */ - public Integer getNumericValue() { - return numericValue; - } - } + /** + * Returns the name of the entity. + * + * @return The name of the entity. + */ + public String getName() { + return name; + } - /** - * Stores a list en entities and is able to return one given its numeric value - * or name. - * - * @author Thierry Boileau - */ - private static class CharacterEntitySolver { - /** Map of names of entities according to their numeric value. */ - private final String[] toName; + /** + * Returns the numeric value of the entity. + * + * @return The numeric value of the entity. + */ + public Integer getNumericValue() { + return numericValue; + } + } - /** Map of numeric values of entities according to their name. */ - private final Map toValue; + /** + * Stores a list of entities and is able to return one given its numeric value or name. + * + * @author Thierry Boileau + */ + private static class CharacterEntitySolver { + /** Map of names of entities according to their numeric value. */ + private final String[] toName; - /** - * Constructor. - */ - public CharacterEntitySolver() { - toName = new String[10000]; - toValue = new HashMap<>(); - } + /** Map of numeric values of entities according to their name. */ + private final Map toValue; - /** - * Adds an entity to solve. - * - * @param value The numeric value of the entity. - * @param name The name of the entity. - */ - public void add(Integer value, String name) { - toName[value] = name; - toValue.put(name, value); - } + /** Constructor. */ + public CharacterEntitySolver() { + toName = new String[10000]; + toValue = new HashMap<>(); + } - /** - * Returns the entity name according to its numeric value. - * - * @param value The numeric value of the entity. - * @return The entity name according to its numeric value. - */ - public String getName(int value) { - return toName[value]; - } + /** + * Adds an entity to solve. + * + * @param value The numeric value of the entity. + * @param name The name of the entity. + */ + public void add(Integer value, String name) { + toName[value] = name; + toValue.put(name, value); + } - /** - * Returns the numeric value of an entity according to its name. - * - * @param name The name of the entity. - * @return The numeric value of an entity according to its name. - */ - public Integer getValue(String name) { - return toValue.get(name); - } + /** + * Returns the entity name according to its numeric value. + * + * @param value The numeric value of the entity. + * @return The entity name according to its numeric value. + */ + public String getName(int value) { + return toName[value]; + } - } + /** + * Returns the numeric value of an entity according to its name. + * + * @param name The name of the entity. + * @return The numeric value of an entity according to its name. + */ + public Integer getValue(String name) { + return toValue.get(name); + } + } - /** Entities defined for HTML 4.0. */ - private static CharacterEntitySolver html40Entities; + /** Entities defined for HTML 4.0. */ + private static final CharacterEntitySolver html40Entities; - /** Entities defined in http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent. */ - private static List htmlLat1; + static { + // Basic entities + final List xml10 = new ArrayList<>(); + xml10.add(new CharacterEntity(34, "quot")); + xml10.add(new CharacterEntity(38, "amp")); + xml10.add(new CharacterEntity(62, "gt")); + xml10.add(new CharacterEntity(60, "lt")); - /** Entities defined in http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent. */ - private static List htmlSpecial; + // cf. http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent + final List htmlLat1 = new ArrayList<>(); + htmlLat1.add(new CharacterEntity(160, "nbsp")); + htmlLat1.add(new CharacterEntity(161, "iexcl")); + htmlLat1.add(new CharacterEntity(162, "cent")); + htmlLat1.add(new CharacterEntity(163, "pound")); + htmlLat1.add(new CharacterEntity(164, "curren")); + htmlLat1.add(new CharacterEntity(165, "yen")); + htmlLat1.add(new CharacterEntity(166, "brvbar")); + htmlLat1.add(new CharacterEntity(167, "sect")); + htmlLat1.add(new CharacterEntity(168, "uml")); + htmlLat1.add(new CharacterEntity(169, "copy")); + htmlLat1.add(new CharacterEntity(170, "ordf")); + htmlLat1.add(new CharacterEntity(171, "laquo")); + htmlLat1.add(new CharacterEntity(172, "not")); + htmlLat1.add(new CharacterEntity(173, "shy")); + htmlLat1.add(new CharacterEntity(174, "reg")); + htmlLat1.add(new CharacterEntity(175, "macr")); + htmlLat1.add(new CharacterEntity(176, "deg")); + htmlLat1.add(new CharacterEntity(177, "plusmn")); + htmlLat1.add(new CharacterEntity(178, "sup2")); + htmlLat1.add(new CharacterEntity(179, "sup3")); + htmlLat1.add(new CharacterEntity(180, "acute")); + htmlLat1.add(new CharacterEntity(181, "micro")); + htmlLat1.add(new CharacterEntity(182, "para")); + htmlLat1.add(new CharacterEntity(183, "middot")); + htmlLat1.add(new CharacterEntity(184, "cedil")); + htmlLat1.add(new CharacterEntity(185, "sup1")); + htmlLat1.add(new CharacterEntity(186, "ordm")); + htmlLat1.add(new CharacterEntity(187, "raquo")); + htmlLat1.add(new CharacterEntity(188, "frac14")); + htmlLat1.add(new CharacterEntity(189, "frac12")); + htmlLat1.add(new CharacterEntity(190, "frac34")); + htmlLat1.add(new CharacterEntity(191, "iquest")); + htmlLat1.add(new CharacterEntity(192, "Agrave")); + htmlLat1.add(new CharacterEntity(193, "Aacute")); + htmlLat1.add(new CharacterEntity(194, "Acirc")); + htmlLat1.add(new CharacterEntity(195, "Atilde")); + htmlLat1.add(new CharacterEntity(196, "Auml")); + htmlLat1.add(new CharacterEntity(197, "Aring")); + htmlLat1.add(new CharacterEntity(198, "AElig")); + htmlLat1.add(new CharacterEntity(199, "Ccedil")); + htmlLat1.add(new CharacterEntity(200, "Egrave")); + htmlLat1.add(new CharacterEntity(201, "Eacute")); + htmlLat1.add(new CharacterEntity(202, "Ecirc")); + htmlLat1.add(new CharacterEntity(203, "Euml")); + htmlLat1.add(new CharacterEntity(204, "Igrave")); + htmlLat1.add(new CharacterEntity(205, "Iacute")); + htmlLat1.add(new CharacterEntity(206, "Icirc")); + htmlLat1.add(new CharacterEntity(207, "Iuml")); + htmlLat1.add(new CharacterEntity(208, "ETH")); + htmlLat1.add(new CharacterEntity(209, "Ntilde")); + htmlLat1.add(new CharacterEntity(210, "Ograve")); + htmlLat1.add(new CharacterEntity(211, "Oacute")); + htmlLat1.add(new CharacterEntity(212, "Ocirc")); + htmlLat1.add(new CharacterEntity(213, "Otilde")); + htmlLat1.add(new CharacterEntity(214, "Ouml")); + htmlLat1.add(new CharacterEntity(215, "times")); + htmlLat1.add(new CharacterEntity(216, "Oslash")); + htmlLat1.add(new CharacterEntity(217, "Ugrave")); + htmlLat1.add(new CharacterEntity(218, "Uacute")); + htmlLat1.add(new CharacterEntity(219, "Ucirc")); + htmlLat1.add(new CharacterEntity(220, "Uuml")); + htmlLat1.add(new CharacterEntity(221, "Yacute")); + htmlLat1.add(new CharacterEntity(222, "THORN")); + htmlLat1.add(new CharacterEntity(223, "szlig")); + htmlLat1.add(new CharacterEntity(224, "agrave")); + htmlLat1.add(new CharacterEntity(225, "aacute")); + htmlLat1.add(new CharacterEntity(226, "acirc")); + htmlLat1.add(new CharacterEntity(227, "atilde")); + htmlLat1.add(new CharacterEntity(228, "auml")); + htmlLat1.add(new CharacterEntity(229, "aring")); + htmlLat1.add(new CharacterEntity(230, "aelig")); + htmlLat1.add(new CharacterEntity(231, "ccedil")); + htmlLat1.add(new CharacterEntity(232, "egrave")); + htmlLat1.add(new CharacterEntity(233, "eacute")); + htmlLat1.add(new CharacterEntity(234, "ecirc")); + htmlLat1.add(new CharacterEntity(235, "euml")); + htmlLat1.add(new CharacterEntity(236, "igrave")); + htmlLat1.add(new CharacterEntity(237, "iacute")); + htmlLat1.add(new CharacterEntity(238, "icirc")); + htmlLat1.add(new CharacterEntity(239, "iuml")); + htmlLat1.add(new CharacterEntity(240, "eth")); + htmlLat1.add(new CharacterEntity(241, "ntilde")); + htmlLat1.add(new CharacterEntity(242, "ograve")); + htmlLat1.add(new CharacterEntity(243, "oacute")); + htmlLat1.add(new CharacterEntity(244, "ocirc")); + htmlLat1.add(new CharacterEntity(245, "otilde")); + htmlLat1.add(new CharacterEntity(246, "ouml")); + htmlLat1.add(new CharacterEntity(247, "divide")); + htmlLat1.add(new CharacterEntity(248, "oslash")); + htmlLat1.add(new CharacterEntity(249, "ugrave")); + htmlLat1.add(new CharacterEntity(250, "uacute")); + htmlLat1.add(new CharacterEntity(251, "ucirc")); + htmlLat1.add(new CharacterEntity(252, "uuml")); + htmlLat1.add(new CharacterEntity(253, "yacute")); + htmlLat1.add(new CharacterEntity(254, "thorn")); + htmlLat1.add(new CharacterEntity(255, "yuml")); - /** Entities defined in http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent. */ - private static List htmlSymbol; + // cf. http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent + final List htmlSymbol = new ArrayList<>(); + htmlSymbol.add(new CharacterEntity(402, "fnof")); + htmlSymbol.add(new CharacterEntity(913, "Alpha")); + htmlSymbol.add(new CharacterEntity(914, "Beta")); + htmlSymbol.add(new CharacterEntity(915, "Gamma")); + htmlSymbol.add(new CharacterEntity(916, "Delta")); + htmlSymbol.add(new CharacterEntity(917, "Epsilon")); + htmlSymbol.add(new CharacterEntity(918, "Zeta")); + htmlSymbol.add(new CharacterEntity(919, "Eta")); + htmlSymbol.add(new CharacterEntity(920, "Theta")); + htmlSymbol.add(new CharacterEntity(921, "Iota")); + htmlSymbol.add(new CharacterEntity(922, "Kappa")); + htmlSymbol.add(new CharacterEntity(923, "Lambda")); + htmlSymbol.add(new CharacterEntity(924, "Mu")); + htmlSymbol.add(new CharacterEntity(925, "Nu")); + htmlSymbol.add(new CharacterEntity(926, "Xi")); + htmlSymbol.add(new CharacterEntity(927, "Omicron")); + htmlSymbol.add(new CharacterEntity(928, "Pi")); + htmlSymbol.add(new CharacterEntity(929, "Rho")); + htmlSymbol.add(new CharacterEntity(931, "Sigma")); + htmlSymbol.add(new CharacterEntity(932, "Tau")); + htmlSymbol.add(new CharacterEntity(933, "Upsilon")); + htmlSymbol.add(new CharacterEntity(934, "Phi")); + htmlSymbol.add(new CharacterEntity(935, "Chi")); + htmlSymbol.add(new CharacterEntity(936, "Psi")); + htmlSymbol.add(new CharacterEntity(937, "Omega")); + htmlSymbol.add(new CharacterEntity(945, "alpha")); + htmlSymbol.add(new CharacterEntity(946, "beta")); + htmlSymbol.add(new CharacterEntity(947, "gamma")); + htmlSymbol.add(new CharacterEntity(948, "delta")); + htmlSymbol.add(new CharacterEntity(949, "epsilon")); + htmlSymbol.add(new CharacterEntity(950, "zeta")); + htmlSymbol.add(new CharacterEntity(951, "eta")); + htmlSymbol.add(new CharacterEntity(952, "theta")); + htmlSymbol.add(new CharacterEntity(953, "iota")); + htmlSymbol.add(new CharacterEntity(954, "kappa")); + htmlSymbol.add(new CharacterEntity(955, "lambda")); + htmlSymbol.add(new CharacterEntity(956, "mu")); + htmlSymbol.add(new CharacterEntity(957, "nu")); + htmlSymbol.add(new CharacterEntity(958, "xi")); + htmlSymbol.add(new CharacterEntity(959, "omicron")); + htmlSymbol.add(new CharacterEntity(960, "pi")); + htmlSymbol.add(new CharacterEntity(961, "rho")); + htmlSymbol.add(new CharacterEntity(962, "sigmaf")); + htmlSymbol.add(new CharacterEntity(963, "sigma")); + htmlSymbol.add(new CharacterEntity(964, "tau")); + htmlSymbol.add(new CharacterEntity(965, "upsilon")); + htmlSymbol.add(new CharacterEntity(966, "phi")); + htmlSymbol.add(new CharacterEntity(967, "chi")); + htmlSymbol.add(new CharacterEntity(968, "psi")); + htmlSymbol.add(new CharacterEntity(969, "omega")); + htmlSymbol.add(new CharacterEntity(977, "thetasym")); + htmlSymbol.add(new CharacterEntity(978, "upsih")); + htmlSymbol.add(new CharacterEntity(982, "piv")); + htmlSymbol.add(new CharacterEntity(8230, "hellip")); + htmlSymbol.add(new CharacterEntity(8242, "prime")); + htmlSymbol.add(new CharacterEntity(8243, "Prime")); + htmlSymbol.add(new CharacterEntity(8254, "oline")); + htmlSymbol.add(new CharacterEntity(8260, "frasl")); + htmlSymbol.add(new CharacterEntity(8465, "image")); + htmlSymbol.add(new CharacterEntity(8472, "weierp")); + htmlSymbol.add(new CharacterEntity(8476, "real")); + htmlSymbol.add(new CharacterEntity(8482, "trade")); + htmlSymbol.add(new CharacterEntity(8501, "alefsym")); + htmlSymbol.add(new CharacterEntity(8592, "larr")); + htmlSymbol.add(new CharacterEntity(8593, "uarr")); + htmlSymbol.add(new CharacterEntity(8594, "rarr")); + htmlSymbol.add(new CharacterEntity(8595, "darr")); + htmlSymbol.add(new CharacterEntity(8596, "harr")); + htmlSymbol.add(new CharacterEntity(8629, "crarr")); + htmlSymbol.add(new CharacterEntity(8656, "lArr")); + htmlSymbol.add(new CharacterEntity(8657, "uArr")); + htmlSymbol.add(new CharacterEntity(8658, "rArr")); + htmlSymbol.add(new CharacterEntity(8659, "dArr")); + htmlSymbol.add(new CharacterEntity(8660, "hArr")); + htmlSymbol.add(new CharacterEntity(8704, "forall")); + htmlSymbol.add(new CharacterEntity(8706, "part")); + htmlSymbol.add(new CharacterEntity(8707, "exist")); + htmlSymbol.add(new CharacterEntity(8709, "empty")); + htmlSymbol.add(new CharacterEntity(8711, "nabla")); + htmlSymbol.add(new CharacterEntity(8712, "isin")); + htmlSymbol.add(new CharacterEntity(8713, "notin")); + htmlSymbol.add(new CharacterEntity(8715, "ni")); + htmlSymbol.add(new CharacterEntity(8719, "prod")); + htmlSymbol.add(new CharacterEntity(8721, "sum")); + htmlSymbol.add(new CharacterEntity(8722, "minus")); + htmlSymbol.add(new CharacterEntity(8727, "lowast")); + htmlSymbol.add(new CharacterEntity(8730, "radic")); + htmlSymbol.add(new CharacterEntity(8733, "prop")); + htmlSymbol.add(new CharacterEntity(8734, "infin")); + htmlSymbol.add(new CharacterEntity(8736, "ang")); + htmlSymbol.add(new CharacterEntity(8743, "and")); + htmlSymbol.add(new CharacterEntity(8744, "or")); + htmlSymbol.add(new CharacterEntity(8745, "cap")); + htmlSymbol.add(new CharacterEntity(8746, "cup")); + htmlSymbol.add(new CharacterEntity(8747, "int")); + htmlSymbol.add(new CharacterEntity(8756, "there4")); + htmlSymbol.add(new CharacterEntity(8764, "sim")); + htmlSymbol.add(new CharacterEntity(8773, "cong")); + htmlSymbol.add(new CharacterEntity(8776, "asymp")); + htmlSymbol.add(new CharacterEntity(8800, "ne")); + htmlSymbol.add(new CharacterEntity(8801, "equiv")); + htmlSymbol.add(new CharacterEntity(8804, "le")); + htmlSymbol.add(new CharacterEntity(8805, "ge")); + htmlSymbol.add(new CharacterEntity(8834, "sub")); + htmlSymbol.add(new CharacterEntity(8835, "sup")); + htmlSymbol.add(new CharacterEntity(8836, "nsub")); + htmlSymbol.add(new CharacterEntity(8838, "sube")); + htmlSymbol.add(new CharacterEntity(8839, "supe")); + htmlSymbol.add(new CharacterEntity(8853, "oplus")); + htmlSymbol.add(new CharacterEntity(8855, "otimes")); + htmlSymbol.add(new CharacterEntity(8869, "perp")); + htmlSymbol.add(new CharacterEntity(8901, "sdot")); + htmlSymbol.add(new CharacterEntity(8968, "lceil")); + htmlSymbol.add(new CharacterEntity(8969, "rceil")); + htmlSymbol.add(new CharacterEntity(8970, "lfloor")); + htmlSymbol.add(new CharacterEntity(8971, "rfloor")); + htmlSymbol.add(new CharacterEntity(9001, "lang")); + htmlSymbol.add(new CharacterEntity(9002, "rang")); + htmlSymbol.add(new CharacterEntity(9674, "loz")); + htmlSymbol.add(new CharacterEntity(9824, "spades")); + htmlSymbol.add(new CharacterEntity(9827, "clubs")); + htmlSymbol.add(new CharacterEntity(9829, "hearts")); + htmlSymbol.add(new CharacterEntity(9830, "diams")); - /** Basic entities. */ - private static List xml10; + // cf. http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent + final List htmlSpecial = new ArrayList<>(); + htmlSpecial.add(new CharacterEntity(34, "quot")); + htmlSpecial.add(new CharacterEntity(38, "amp")); + htmlSpecial.add(new CharacterEntity(39, "apos")); + htmlSpecial.add(new CharacterEntity(60, "lt")); + htmlSpecial.add(new CharacterEntity(62, "gt")); + htmlSpecial.add(new CharacterEntity(338, "OElig")); + htmlSpecial.add(new CharacterEntity(339, "oelig")); + htmlSpecial.add(new CharacterEntity(352, "Scaron")); + htmlSpecial.add(new CharacterEntity(353, "scaron")); + htmlSpecial.add(new CharacterEntity(376, "Yuml")); + htmlSpecial.add(new CharacterEntity(710, "circ")); + htmlSpecial.add(new CharacterEntity(732, "tilde")); + htmlSpecial.add(new CharacterEntity(8194, "ensp")); + htmlSpecial.add(new CharacterEntity(8195, "emsp")); + htmlSpecial.add(new CharacterEntity(8201, "thinsp")); + htmlSpecial.add(new CharacterEntity(8204, "zwnj")); + htmlSpecial.add(new CharacterEntity(8205, "zwj")); + htmlSpecial.add(new CharacterEntity(8206, "lrm")); + htmlSpecial.add(new CharacterEntity(8207, "rlm")); + htmlSpecial.add(new CharacterEntity(8211, "ndash")); + htmlSpecial.add(new CharacterEntity(8212, "mdash")); + htmlSpecial.add(new CharacterEntity(8216, "lsquo")); + htmlSpecial.add(new CharacterEntity(8217, "rsquo")); + htmlSpecial.add(new CharacterEntity(8218, "sbquo")); + htmlSpecial.add(new CharacterEntity(8220, "ldquo")); + htmlSpecial.add(new CharacterEntity(8221, "rdquo")); + htmlSpecial.add(new CharacterEntity(8222, "bdquo")); + htmlSpecial.add(new CharacterEntity(8224, "dagger")); + htmlSpecial.add(new CharacterEntity(8225, "Dagger")); + htmlSpecial.add(new CharacterEntity(8226, "bull")); + htmlSpecial.add(new CharacterEntity(8240, "permil")); + htmlSpecial.add(new CharacterEntity(8249, "lsaquo")); + htmlSpecial.add(new CharacterEntity(8250, "rsaquo")); + htmlSpecial.add(new CharacterEntity(8364, "euro")); - static { - xml10 = new ArrayList(); - xml10.add(new CharacterEntity(34, "quot")); - xml10.add(new CharacterEntity(38, "amp")); - xml10.add(new CharacterEntity(62, "gt")); - xml10.add(new CharacterEntity(60, "lt")); - htmlLat1 = new ArrayList(); - htmlLat1.add(new CharacterEntity(160, "nbsp")); - htmlLat1.add(new CharacterEntity(161, "iexcl")); - htmlLat1.add(new CharacterEntity(162, "cent")); - htmlLat1.add(new CharacterEntity(163, "pound")); - htmlLat1.add(new CharacterEntity(164, "curren")); - htmlLat1.add(new CharacterEntity(165, "yen")); - htmlLat1.add(new CharacterEntity(166, "brvbar")); - htmlLat1.add(new CharacterEntity(167, "sect")); - htmlLat1.add(new CharacterEntity(168, "uml")); - htmlLat1.add(new CharacterEntity(169, "copy")); - htmlLat1.add(new CharacterEntity(170, "ordf")); - htmlLat1.add(new CharacterEntity(171, "laquo")); - htmlLat1.add(new CharacterEntity(172, "not")); - htmlLat1.add(new CharacterEntity(173, "shy")); - htmlLat1.add(new CharacterEntity(174, "reg")); - htmlLat1.add(new CharacterEntity(175, "macr")); - htmlLat1.add(new CharacterEntity(176, "deg")); - htmlLat1.add(new CharacterEntity(177, "plusmn")); - htmlLat1.add(new CharacterEntity(178, "sup2")); - htmlLat1.add(new CharacterEntity(179, "sup3")); - htmlLat1.add(new CharacterEntity(180, "acute")); - htmlLat1.add(new CharacterEntity(181, "micro")); - htmlLat1.add(new CharacterEntity(182, "para")); - htmlLat1.add(new CharacterEntity(183, "middot")); - htmlLat1.add(new CharacterEntity(184, "cedil")); - htmlLat1.add(new CharacterEntity(185, "sup1")); - htmlLat1.add(new CharacterEntity(186, "ordm")); - htmlLat1.add(new CharacterEntity(187, "raquo")); - htmlLat1.add(new CharacterEntity(188, "frac14")); - htmlLat1.add(new CharacterEntity(189, "frac12")); - htmlLat1.add(new CharacterEntity(190, "frac34")); - htmlLat1.add(new CharacterEntity(191, "iquest")); - htmlLat1.add(new CharacterEntity(192, "Agrave")); - htmlLat1.add(new CharacterEntity(193, "Aacute")); - htmlLat1.add(new CharacterEntity(194, "Acirc")); - htmlLat1.add(new CharacterEntity(195, "Atilde")); - htmlLat1.add(new CharacterEntity(196, "Auml")); - htmlLat1.add(new CharacterEntity(197, "Aring")); - htmlLat1.add(new CharacterEntity(198, "AElig")); - htmlLat1.add(new CharacterEntity(199, "Ccedil")); - htmlLat1.add(new CharacterEntity(200, "Egrave")); - htmlLat1.add(new CharacterEntity(201, "Eacute")); - htmlLat1.add(new CharacterEntity(202, "Ecirc")); - htmlLat1.add(new CharacterEntity(203, "Euml")); - htmlLat1.add(new CharacterEntity(204, "Igrave")); - htmlLat1.add(new CharacterEntity(205, "Iacute")); - htmlLat1.add(new CharacterEntity(206, "Icirc")); - htmlLat1.add(new CharacterEntity(207, "Iuml")); - htmlLat1.add(new CharacterEntity(208, "ETH")); - htmlLat1.add(new CharacterEntity(209, "Ntilde")); - htmlLat1.add(new CharacterEntity(210, "Ograve")); - htmlLat1.add(new CharacterEntity(211, "Oacute")); - htmlLat1.add(new CharacterEntity(212, "Ocirc")); - htmlLat1.add(new CharacterEntity(213, "Otilde")); - htmlLat1.add(new CharacterEntity(214, "Ouml")); - htmlLat1.add(new CharacterEntity(215, "times")); - htmlLat1.add(new CharacterEntity(216, "Oslash")); - htmlLat1.add(new CharacterEntity(217, "Ugrave")); - htmlLat1.add(new CharacterEntity(218, "Uacute")); - htmlLat1.add(new CharacterEntity(219, "Ucirc")); - htmlLat1.add(new CharacterEntity(220, "Uuml")); - htmlLat1.add(new CharacterEntity(221, "Yacute")); - htmlLat1.add(new CharacterEntity(222, "THORN")); - htmlLat1.add(new CharacterEntity(223, "szlig")); - htmlLat1.add(new CharacterEntity(224, "agrave")); - htmlLat1.add(new CharacterEntity(225, "aacute")); - htmlLat1.add(new CharacterEntity(226, "acirc")); - htmlLat1.add(new CharacterEntity(227, "atilde")); - htmlLat1.add(new CharacterEntity(228, "auml")); - htmlLat1.add(new CharacterEntity(229, "aring")); - htmlLat1.add(new CharacterEntity(230, "aelig")); - htmlLat1.add(new CharacterEntity(231, "ccedil")); - htmlLat1.add(new CharacterEntity(232, "egrave")); - htmlLat1.add(new CharacterEntity(233, "eacute")); - htmlLat1.add(new CharacterEntity(234, "ecirc")); - htmlLat1.add(new CharacterEntity(235, "euml")); - htmlLat1.add(new CharacterEntity(236, "igrave")); - htmlLat1.add(new CharacterEntity(237, "iacute")); - htmlLat1.add(new CharacterEntity(238, "icirc")); - htmlLat1.add(new CharacterEntity(239, "iuml")); - htmlLat1.add(new CharacterEntity(240, "eth")); - htmlLat1.add(new CharacterEntity(241, "ntilde")); - htmlLat1.add(new CharacterEntity(242, "ograve")); - htmlLat1.add(new CharacterEntity(243, "oacute")); - htmlLat1.add(new CharacterEntity(244, "ocirc")); - htmlLat1.add(new CharacterEntity(245, "otilde")); - htmlLat1.add(new CharacterEntity(246, "ouml")); - htmlLat1.add(new CharacterEntity(247, "divide")); - htmlLat1.add(new CharacterEntity(248, "oslash")); - htmlLat1.add(new CharacterEntity(249, "ugrave")); - htmlLat1.add(new CharacterEntity(250, "uacute")); - htmlLat1.add(new CharacterEntity(251, "ucirc")); - htmlLat1.add(new CharacterEntity(252, "uuml")); - htmlLat1.add(new CharacterEntity(253, "yacute")); - htmlLat1.add(new CharacterEntity(254, "thorn")); - htmlLat1.add(new CharacterEntity(255, "yuml")); - htmlSymbol = new ArrayList(); - htmlSymbol.add(new CharacterEntity(402, "fnof")); - htmlSymbol.add(new CharacterEntity(913, "Alpha")); - htmlSymbol.add(new CharacterEntity(914, "Beta")); - htmlSymbol.add(new CharacterEntity(915, "Gamma")); - htmlSymbol.add(new CharacterEntity(916, "Delta")); - htmlSymbol.add(new CharacterEntity(917, "Epsilon")); - htmlSymbol.add(new CharacterEntity(918, "Zeta")); - htmlSymbol.add(new CharacterEntity(919, "Eta")); - htmlSymbol.add(new CharacterEntity(920, "Theta")); - htmlSymbol.add(new CharacterEntity(921, "Iota")); - htmlSymbol.add(new CharacterEntity(922, "Kappa")); - htmlSymbol.add(new CharacterEntity(923, "Lambda")); - htmlSymbol.add(new CharacterEntity(924, "Mu")); - htmlSymbol.add(new CharacterEntity(925, "Nu")); - htmlSymbol.add(new CharacterEntity(926, "Xi")); - htmlSymbol.add(new CharacterEntity(927, "Omicron")); - htmlSymbol.add(new CharacterEntity(928, "Pi")); - htmlSymbol.add(new CharacterEntity(929, "Rho")); - htmlSymbol.add(new CharacterEntity(931, "Sigma")); - htmlSymbol.add(new CharacterEntity(932, "Tau")); - htmlSymbol.add(new CharacterEntity(933, "Upsilon")); - htmlSymbol.add(new CharacterEntity(934, "Phi")); - htmlSymbol.add(new CharacterEntity(935, "Chi")); - htmlSymbol.add(new CharacterEntity(936, "Psi")); - htmlSymbol.add(new CharacterEntity(937, "Omega")); - htmlSymbol.add(new CharacterEntity(945, "alpha")); - htmlSymbol.add(new CharacterEntity(946, "beta")); - htmlSymbol.add(new CharacterEntity(947, "gamma")); - htmlSymbol.add(new CharacterEntity(948, "delta")); - htmlSymbol.add(new CharacterEntity(949, "epsilon")); - htmlSymbol.add(new CharacterEntity(950, "zeta")); - htmlSymbol.add(new CharacterEntity(951, "eta")); - htmlSymbol.add(new CharacterEntity(952, "theta")); - htmlSymbol.add(new CharacterEntity(953, "iota")); - htmlSymbol.add(new CharacterEntity(954, "kappa")); - htmlSymbol.add(new CharacterEntity(955, "lambda")); - htmlSymbol.add(new CharacterEntity(956, "mu")); - htmlSymbol.add(new CharacterEntity(957, "nu")); - htmlSymbol.add(new CharacterEntity(958, "xi")); - htmlSymbol.add(new CharacterEntity(959, "omicron")); - htmlSymbol.add(new CharacterEntity(960, "pi")); - htmlSymbol.add(new CharacterEntity(961, "rho")); - htmlSymbol.add(new CharacterEntity(962, "sigmaf")); - htmlSymbol.add(new CharacterEntity(963, "sigma")); - htmlSymbol.add(new CharacterEntity(964, "tau")); - htmlSymbol.add(new CharacterEntity(965, "upsilon")); - htmlSymbol.add(new CharacterEntity(966, "phi")); - htmlSymbol.add(new CharacterEntity(967, "chi")); - htmlSymbol.add(new CharacterEntity(968, "psi")); - htmlSymbol.add(new CharacterEntity(969, "omega")); - htmlSymbol.add(new CharacterEntity(977, "thetasym")); - htmlSymbol.add(new CharacterEntity(978, "upsih")); - htmlSymbol.add(new CharacterEntity(982, "piv")); - htmlSymbol.add(new CharacterEntity(8230, "hellip")); - htmlSymbol.add(new CharacterEntity(8242, "prime")); - htmlSymbol.add(new CharacterEntity(8243, "Prime")); - htmlSymbol.add(new CharacterEntity(8254, "oline")); - htmlSymbol.add(new CharacterEntity(8260, "frasl")); - htmlSymbol.add(new CharacterEntity(8465, "image")); - htmlSymbol.add(new CharacterEntity(8472, "weierp")); - htmlSymbol.add(new CharacterEntity(8476, "real")); - htmlSymbol.add(new CharacterEntity(8482, "trade")); - htmlSymbol.add(new CharacterEntity(8501, "alefsym")); - htmlSymbol.add(new CharacterEntity(8592, "larr")); - htmlSymbol.add(new CharacterEntity(8593, "uarr")); - htmlSymbol.add(new CharacterEntity(8594, "rarr")); - htmlSymbol.add(new CharacterEntity(8595, "darr")); - htmlSymbol.add(new CharacterEntity(8596, "harr")); - htmlSymbol.add(new CharacterEntity(8629, "crarr")); - htmlSymbol.add(new CharacterEntity(8656, "lArr")); - htmlSymbol.add(new CharacterEntity(8657, "uArr")); - htmlSymbol.add(new CharacterEntity(8658, "rArr")); - htmlSymbol.add(new CharacterEntity(8659, "dArr")); - htmlSymbol.add(new CharacterEntity(8660, "hArr")); - htmlSymbol.add(new CharacterEntity(8704, "forall")); - htmlSymbol.add(new CharacterEntity(8706, "part")); - htmlSymbol.add(new CharacterEntity(8707, "exist")); - htmlSymbol.add(new CharacterEntity(8709, "empty")); - htmlSymbol.add(new CharacterEntity(8711, "nabla")); - htmlSymbol.add(new CharacterEntity(8712, "isin")); - htmlSymbol.add(new CharacterEntity(8713, "notin")); - htmlSymbol.add(new CharacterEntity(8715, "ni")); - htmlSymbol.add(new CharacterEntity(8719, "prod")); - htmlSymbol.add(new CharacterEntity(8721, "sum")); - htmlSymbol.add(new CharacterEntity(8722, "minus")); - htmlSymbol.add(new CharacterEntity(8727, "lowast")); - htmlSymbol.add(new CharacterEntity(8730, "radic")); - htmlSymbol.add(new CharacterEntity(8733, "prop")); - htmlSymbol.add(new CharacterEntity(8734, "infin")); - htmlSymbol.add(new CharacterEntity(8736, "ang")); - htmlSymbol.add(new CharacterEntity(8743, "and")); - htmlSymbol.add(new CharacterEntity(8744, "or")); - htmlSymbol.add(new CharacterEntity(8745, "cap")); - htmlSymbol.add(new CharacterEntity(8746, "cup")); - htmlSymbol.add(new CharacterEntity(8747, "int")); - htmlSymbol.add(new CharacterEntity(8756, "there4")); - htmlSymbol.add(new CharacterEntity(8764, "sim")); - htmlSymbol.add(new CharacterEntity(8773, "cong")); - htmlSymbol.add(new CharacterEntity(8776, "asymp")); - htmlSymbol.add(new CharacterEntity(8800, "ne")); - htmlSymbol.add(new CharacterEntity(8801, "equiv")); - htmlSymbol.add(new CharacterEntity(8804, "le")); - htmlSymbol.add(new CharacterEntity(8805, "ge")); - htmlSymbol.add(new CharacterEntity(8834, "sub")); - htmlSymbol.add(new CharacterEntity(8835, "sup")); - htmlSymbol.add(new CharacterEntity(8836, "nsub")); - htmlSymbol.add(new CharacterEntity(8838, "sube")); - htmlSymbol.add(new CharacterEntity(8839, "supe")); - htmlSymbol.add(new CharacterEntity(8853, "oplus")); - htmlSymbol.add(new CharacterEntity(8855, "otimes")); - htmlSymbol.add(new CharacterEntity(8869, "perp")); - htmlSymbol.add(new CharacterEntity(8901, "sdot")); - htmlSymbol.add(new CharacterEntity(8968, "lceil")); - htmlSymbol.add(new CharacterEntity(8969, "rceil")); - htmlSymbol.add(new CharacterEntity(8970, "lfloor")); - htmlSymbol.add(new CharacterEntity(8971, "rfloor")); - htmlSymbol.add(new CharacterEntity(9001, "lang")); - htmlSymbol.add(new CharacterEntity(9002, "rang")); - htmlSymbol.add(new CharacterEntity(9674, "loz")); - htmlSymbol.add(new CharacterEntity(9824, "spades")); - htmlSymbol.add(new CharacterEntity(9827, "clubs")); - htmlSymbol.add(new CharacterEntity(9829, "hearts")); - htmlSymbol.add(new CharacterEntity(9830, "diams")); - htmlSpecial = new ArrayList(); - htmlSpecial.add(new CharacterEntity(34, "quot")); - htmlSpecial.add(new CharacterEntity(38, "amp")); - htmlSpecial.add(new CharacterEntity(39, "apos")); - htmlSpecial.add(new CharacterEntity(60, "lt")); - htmlSpecial.add(new CharacterEntity(62, "gt")); - htmlSpecial.add(new CharacterEntity(338, "OElig")); - htmlSpecial.add(new CharacterEntity(339, "oelig")); - htmlSpecial.add(new CharacterEntity(352, "Scaron")); - htmlSpecial.add(new CharacterEntity(353, "scaron")); - htmlSpecial.add(new CharacterEntity(376, "Yuml")); - htmlSpecial.add(new CharacterEntity(710, "circ")); - htmlSpecial.add(new CharacterEntity(732, "tilde")); - htmlSpecial.add(new CharacterEntity(8194, "ensp")); - htmlSpecial.add(new CharacterEntity(8195, "emsp")); - htmlSpecial.add(new CharacterEntity(8201, "thinsp")); - htmlSpecial.add(new CharacterEntity(8204, "zwnj")); - htmlSpecial.add(new CharacterEntity(8205, "zwj")); - htmlSpecial.add(new CharacterEntity(8206, "lrm")); - htmlSpecial.add(new CharacterEntity(8207, "rlm")); - htmlSpecial.add(new CharacterEntity(8211, "ndash")); - htmlSpecial.add(new CharacterEntity(8212, "mdash")); - htmlSpecial.add(new CharacterEntity(8216, "lsquo")); - htmlSpecial.add(new CharacterEntity(8217, "rsquo")); - htmlSpecial.add(new CharacterEntity(8218, "sbquo")); - htmlSpecial.add(new CharacterEntity(8220, "ldquo")); - htmlSpecial.add(new CharacterEntity(8221, "rdquo")); - htmlSpecial.add(new CharacterEntity(8222, "bdquo")); - htmlSpecial.add(new CharacterEntity(8224, "dagger")); - htmlSpecial.add(new CharacterEntity(8225, "Dagger")); - htmlSpecial.add(new CharacterEntity(8226, "bull")); - htmlSpecial.add(new CharacterEntity(8240, "permil")); - htmlSpecial.add(new CharacterEntity(8249, "lsaquo")); - htmlSpecial.add(new CharacterEntity(8250, "rsaquo")); - htmlSpecial.add(new CharacterEntity(8364, "euro")); - html40Entities = new CharacterEntitySolver(); - for (CharacterEntity entity : xml10) { - html40Entities.add(entity.getNumericValue(), entity.getName()); - } - for (CharacterEntity entity : htmlLat1) { - html40Entities.add(entity.getNumericValue(), entity.getName()); - } - for (CharacterEntity entity : htmlSymbol) { - html40Entities.add(entity.getNumericValue(), entity.getName()); - } - for (CharacterEntity entity : htmlSpecial) { - html40Entities.add(entity.getNumericValue(), entity.getName()); - } - } + html40Entities = new CharacterEntitySolver(); + for (CharacterEntity entity : xml10) { + html40Entities.add(entity.getNumericValue(), entity.getName()); + } + for (CharacterEntity entity : htmlLat1) { + html40Entities.add(entity.getNumericValue(), entity.getName()); + } + for (CharacterEntity entity : htmlSymbol) { + html40Entities.add(entity.getNumericValue(), entity.getName()); + } + for (CharacterEntity entity : htmlSpecial) { + html40Entities.add(entity.getNumericValue(), entity.getName()); + } + } - /** - * Returns the string with the first character capitalized. - * - * @param string a string reference to check - * @return the string with the first character capitalized. - */ - public static String firstLower(String string) { - if (!isNullOrEmpty(string)) { - return string.substring(0, 1).toLowerCase() + string.substring(1); - } - return string; - } + /** + * Returns the string with the first character capitalized. + * + * @param string a string reference to check + * @return the string with the first character capitalized. + */ + public static String firstLower(String string) { + if (!isNullOrEmpty(string)) { + return string.substring(0, 1).toLowerCase() + string.substring(1); + } + return string; + } - /** - * Returns the string with the first character capitalized. - * - * @param string a string reference to check - * @return the string with the first character capitalized. - */ - public static String firstUpper(String string) { - if (!isNullOrEmpty(string)) { - return string.substring(0, 1).toUpperCase() + string.substring(1); - } - return string; - } + /** + * Returns the string with the first character capitalized. + * + * @param string a string reference to check + * @return the string with the first character capitalized. + */ + public static String firstUpper(String string) { + if (!isNullOrEmpty(string)) { + return string.substring(0, 1).toUpperCase() + string.substring(1); + } + return string; + } - /** - * Encodes the given String into a sequence of bytes using the Ascii character - * set. - * - * @param string The string to encode. - * @return The String encoded with the Ascii character set as an array of bytes. - */ - public static byte[] getAsciiBytes(String string) { - if (string != null) { - try { - return string.getBytes(StandardCharsets.US_ASCII); - } catch (Exception e) { - // Should not happen. - return null; - } - } - return null; - } + /** + * Encodes the given String into a sequence of bytes using the Ascii character set. + * + * @param string The string to encode. + * @return The String encoded with the Ascii character set as an array of bytes. + */ + public static byte[] getAsciiBytes(String string) { + if (string != null) { + try { + return string.getBytes(StandardCharsets.US_ASCII); + } catch (Exception e) { + // Should not happen. + return null; + } + } + return null; + } - /** - * Encodes the given String into a sequence of bytes using the Latin1 character - * set. - * - * @param string The string to encode. - * @return The String encoded with the Latin1 character set as an array of - * bytes. - */ - public static byte[] getLatin1Bytes(String string) { - if (string != null) { - try { - return string.getBytes(StandardCharsets.ISO_8859_1); - } catch (Exception e) { - // Should not happen. - return null; - } - } - return null; - } + /** + * Encodes the given String into a sequence of bytes using the Latin1 character set. + * + * @param string The string to encode. + * @return The String encoded with the Latin1 character set as an array of bytes. + */ + public static byte[] getLatin1Bytes(String string) { + if (string != null) { + try { + return string.getBytes(StandardCharsets.ISO_8859_1); + } catch (Exception e) { + // Should not happen. + return null; + } + } + return null; + } - /** - * Returns the given {@link String} according to the HTML 4.0 encoding rules. - * - * @param str The {@link String} to encode. - * @return The converted {@link String} according to the HTML 4.0 encoding - * rules. - */ - public static String htmlEscape(String str) { - if (str == null || str.isEmpty()) { - return str; - } - int len = str.length(); - StringBuilder sb = new StringBuilder((int) (len * 1.5)); - for (int i = 0; i < len; i++) { - char c = str.charAt(i); - String entityName = html40Entities.getName(c); - if (entityName == null) { - if (c > 127) { - // Escape non ASCII characters. - sb.append("&#").append(Integer.toString(c, 10)).append(';'); - } else { - // ASCII characters are not escaped. - sb.append(c); - } - } else { - sb.append('&').append(entityName).append(';'); - } - } - return sb.toString(); - } + /** + * Returns the given {@link String} according to the HTML 4.0 encoding rules. + * + * @param str The {@link String} to encode. + * @return The converted {@link String} according to the HTML 4.0 encoding rules. + */ + public static String htmlEscape(String str) { + if (str == null || str.isEmpty()) { + return str; + } + int len = str.length(); + StringBuilder sb = new StringBuilder((int) (len * 1.5)); + for (int i = 0; i < len; i++) { + char c = str.charAt(i); + String entityName = html40Entities.getName(c); + if (entityName == null) { + if (c > 127) { + // Escape non ASCII characters. + sb.append("&#").append(Integer.toString(c, 10)).append(';'); + } else { + // ASCII characters are not escaped. + sb.append(c); + } + } else { + sb.append('&').append(entityName).append(';'); + } + } + return sb.toString(); + } - /** - * Returns the given {@link String} decoded according to the HTML 4.0 decoding - * rules. - * - * @param str The {@link String} to decode. - * @return The given {@link String} decoded according to the HTML 4.0 decoding - * rules. - */ - public static String htmlUnescape(String str) { - if (str == null || str.isEmpty()) { - return str; - } - int len = str.length(); - StringBuilder sb = new StringBuilder(len); - for (int i = 0; i < len; i++) { - char c = str.charAt(i); - if (c == '&') { - int nextIndex = i + 1; - int semicolonIndex = -1; - int ampersandIndex = -1; - boolean stop = false; - for (int j = nextIndex; !stop && j < len; j++) { - char ch = str.charAt(j); - if (';' == ch) { - semicolonIndex = j; - stop = true; - } else if ('&' == ch) { - ampersandIndex = j; - stop = true; - } - } - if (semicolonIndex != -1) { - // Entity found - if (nextIndex != semicolonIndex) { - int entityValue = -1; - String entityName = str.substring(nextIndex, semicolonIndex); - if (entityName.charAt(0) == '#') { - // Numeric value - if (entityName.length() > 1) { - char hexChar = entityName.charAt(1); - try { - if (hexChar == 'X') { - entityValue = Integer.parseInt(entityName.substring(2), 16); - } else if (hexChar == 'x') { - entityValue = Integer.parseInt(entityName.substring(2), 16); - } else { - entityValue = Integer.parseInt(entityName.substring(1), 10); - } - if (!Character.isValidCodePoint(entityValue)) { - // Invalid Unicode character - entityValue = -1; - } - } catch (NumberFormatException e) { - entityValue = -1; - } - } - } else { - Integer val = html40Entities.getValue(entityName); - if (val != null) { - entityValue = val; - } - } - if (entityValue == -1) { - sb.append('&').append(entityName).append(';'); - } else { - sb.append((char) entityValue); - } - } else { - sb.append("&;"); - } - i = semicolonIndex; - } else if (stop) { - // found a "&" character, it could be another entity - sb.append(str, i, ampersandIndex); // add the consumed characters - i = ampersandIndex - 1; // put the index in order to read the "&" character - } else { - // End of the string reached, no more entities to parse. - sb.append(str, i, len); - i = len; - } - } else { - sb.append(c); - } - } - return sb.toString(); - } + /** + * Returns the given {@link String} decoded according to the HTML 4.0 decoding rules. + * + * @param str The {@link String} to decode. + * @return The given {@link String} decoded according to the HTML 4.0 decoding rules. + */ + public static String htmlUnescape(String str) { + if (str == null || str.isEmpty()) { + return str; + } + int len = str.length(); + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + final char c = str.charAt(i); + if (c == '&') { + int nextIndex = i + 1; + int semicolonIndex = -1; + int ampersandIndex = -1; + boolean stop = false; + for (int j = nextIndex; !stop && j < len; j++) { + char ch = str.charAt(j); + if (';' == ch) { + semicolonIndex = j; + stop = true; + } else if ('&' == ch) { + ampersandIndex = j; + stop = true; + } + } + if (semicolonIndex != -1) { + // Entity found + if (nextIndex != semicolonIndex) { + int entityValue = -1; + String entityName = str.substring(nextIndex, semicolonIndex); + if (entityName.charAt(0) == '#') { + // Numeric value + if (entityName.length() > 1) { + char hexChar = entityName.charAt(1); + try { + if (hexChar == 'X') { + entityValue = Integer.parseInt(entityName.substring(2), 16); + } else if (hexChar == 'x') { + entityValue = Integer.parseInt(entityName.substring(2), 16); + } else { + entityValue = Integer.parseInt(entityName.substring(1), 10); + } + if (!Character.isValidCodePoint(entityValue)) { + // Invalid Unicode character + entityValue = -1; + } + } catch (NumberFormatException e) { + entityValue = -1; + } + } + } else { + Integer val = html40Entities.getValue(entityName); + if (val != null) { + entityValue = val; + } + } + if (entityValue == -1) { + sb.append('&').append(entityName).append(';'); + } else { + sb.append((char) entityValue); + } + } else { + sb.append("&;"); + } + i = semicolonIndex; + } else if (stop) { + // found a "&" character, it could be another entity + sb.append(str, i, ampersandIndex); // add the consumed characters + i = ampersandIndex - 1; // put the index to read the "&" character + } else { + // End of the string reached, no more entities to parse. + sb.append(str, i, len); + i = len; + } + } else { + sb.append(c); + } + } + return sb.toString(); + } - /** - * Returns {@code true} if the given string is null or is the empty string. - * - * Consider normalizing your string references with {@link #nullToEmpty}. If you - * do, you can use {@link String#isEmpty()} instead of this method, and you - * won't need special null-safe forms of methods like {@link String#toUpperCase} - * either. - * - * @param string a string reference to check - * @return {@code true} if the string is null or is the empty string - */ - public static boolean isNullOrEmpty(String string) { - return string == null || string.isEmpty(); - } + /** + * Returns {@code true} if the given string is null or is the empty string. + * + *

Consider normalizing your string references with {@link #nullToEmpty}. If you do, you can + * use {@link String#isEmpty()} instead of this method, and you won't need special null-safe + * forms of methods like {@link String#toUpperCase} either. + * + * @param string a string reference to check + * @return {@code true} if the string is null or is the empty string + */ + public static boolean isNullOrEmpty(String string) { + return string == null || string.isEmpty(); + } - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e. it isn't instantiable and extensible. - */ - private StringUtils() { - } + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private StringUtils() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/SystemUtils.java b/org.restlet/src/main/java/org/restlet/engine/util/SystemUtils.java index 9f1fb55d76..3e794d02ec 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/SystemUtils.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/SystemUtils.java @@ -1,116 +1,108 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; /** * System utilities. - * + * * @author Jerome Louvel */ public class SystemUtils { - /** - * Parses the "java.version" system property and returns the first digit of the - * version number of the Java Runtime Environment (e.g. "1" for "1.3.0"). - * - * @see Official Java - * versioning - * @return The major version number of the Java Runtime Environment. - */ - public static int getJavaMajorVersion() { - int result; - final String javaVersion = System.getProperty("java.version"); - try { - result = Integer.parseInt(javaVersion.substring(0, javaVersion.indexOf("."))); - } catch (Exception e) { - result = 0; - } - - return result; - } + /** + * Parses the "java.version" system property and returns the first digit of the version number + * of the Java Runtime Environment (e.g., "1" for "1.3.0"). + * + * @see Official Java versioning + * @return The major version number of the Java Runtime Environment. + */ + public static int getJavaMajorVersion() { + int result; + final String javaVersion = System.getProperty("java.version"); + try { + result = Integer.parseInt(javaVersion.substring(0, javaVersion.indexOf('.'))); + } catch (Exception e) { + result = 0; + } - /** - * Parses the "java.version" system property and returns the second digit of the - * version number of the Java Runtime Environment (e.g. "3" for "1.3.0"). - * - * @see Official Java - * versioning - * @return The minor version number of the Java Runtime Environment. - */ - public static int getJavaMinorVersion() { - int result; - final String javaVersion = System.getProperty("java.version"); - try { - result = Integer.parseInt(javaVersion.split("\\.")[1]); - } catch (Exception e) { - result = 0; - } + return result; + } - return result; - } + /** + * Parses the "java.version" system property and returns the second digit of the version number + * of the Java Runtime Environment (e.g., "3" for "1.3.0"). + * + * @see Official Java versioning + * @return The minor version number of the Java Runtime Environment. + */ + public static int getJavaMinorVersion() { + int result; + final String javaVersion = System.getProperty("java.version"); + try { + result = Integer.parseInt(javaVersion.split("\\.")[1]); + } catch (Exception e) { + result = 0; + } - /** - * Parses the "java.version" system property and returns the update release - * number of the Java Runtime Environment (e.g. "10" for "1.3.0_10"). - * - * @see Official Java - * versioning - * @return The release number of the Java Runtime Environment or 0 if it does - * not exist. - */ - public static int getJavaUpdateVersion() { - int result; - final String javaVersion = System.getProperty("java.version"); - try { - result = Integer.parseInt(javaVersion.substring(javaVersion.indexOf('_') + 1)); - } catch (Exception e) { - result = 0; - } + return result; + } - return result; - } + /** + * Parses the "java.version" system property and returns the update release number of the Java + * Runtime Environment (e.g., "10" for "1.3.0_10"). + * + * @see Official Java versioning + * @return The release number of the Java Runtime Environment or 0 if it does not exist. + */ + public static int getJavaUpdateVersion() { + int result; + final String javaVersion = System.getProperty("java.version"); + try { + result = Integer.parseInt(javaVersion.substring(javaVersion.indexOf('_') + 1)); + } catch (Exception e) { + result = 0; + } - /** - * Computes the hash code of a set of objects. Follows the algorithm specified - * in List.hasCode(). - * - * @param objects the objects to compute the hashCode - * - * @return The hash code of a set of objects. - */ - public static int hashCode(Object... objects) { - int result = 17; + return result; + } - if (objects != null) { - for (final Object obj : objects) { - result = 31 * result + (obj == null ? 0 : obj.hashCode()); - } - } + /** + * Computes the hash code of a set of objects. Follows the algorithm specified in + * List.hasCode(). + * + * @param objects the objects to compute the hashCode + * @return The hash code of a set of objects. + */ + public static int hashCode(Object... objects) { + int result = 17; - return result; - } + if (objects != null) { + for (final Object obj : objects) { + result = 31 * result + (obj == null ? 0 : obj.hashCode()); + } + } - /** - * Indicates if the current operating system is in the Windows family. - * - * @return True if the current operating system is in the Windows family. - */ - public static boolean isWindows() { - return System.getProperty("os.name").toLowerCase().contains("win"); - } + return result; + } - /** - * Private constructor to ensure that the class acts as a true utility class - * i.e. it isn't instantiable and extensible. - */ - private SystemUtils() { - } + /** + * Indicates if the current operating system is in the Windows family. + * + * @return True if the current operating system is in the Windows family. + */ + public static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("win"); + } + /** + * Private constructor to ensure that the class acts as a true utility class i.e., it isn't + * instantiable and extensible. + */ + private SystemUtils() {} } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/TemplateDispatcher.java b/org.restlet/src/main/java/org/restlet/engine/util/TemplateDispatcher.java index a1dd5a0cfb..5c35c545bc 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/TemplateDispatcher.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/TemplateDispatcher.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import org.restlet.Request; @@ -16,56 +15,57 @@ import org.restlet.routing.Template; /** - * Filter that resolves URI templates in the target resource URI reference using - * the request attributes. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state as member variables. - * + * Filter that resolves URI templates in the target resource URI reference using the request + * attributes. + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state as member variables. + * * @author Jerome Louvel */ public class TemplateDispatcher extends Filter { - /** - * If the response entity comes back with no identifier, automatically set the - * request's resource reference's identifier. This is very useful to resolve - * relative references in XSLT for example. - */ - @Override - protected void afterHandle(Request request, Response response) { - if ((response.getEntity() != null) && (response.getEntity().getLocationRef() == null)) { - response.getEntity().setLocationRef(request.getResourceRef().getTargetRef().toString()); - } - } - - /** - * Handles the call after resolving any URI template on the request's target - * resource reference. - * - * @param request The request to handle. - * @param response The response to update. - */ - public int beforeHandle(Request request, Response response) { - // Associate the response to the current thread - Protocol protocol = request.getProtocol(); + /** + * If the response entity comes back with no identifier, automatically set the request's + * resource reference's identifier. This is very useful to resolve relative references in XSLT, + * for example. + */ + @Override + protected void afterHandle(Request request, Response response) { + if ((response.getEntity() != null) && (response.getEntity().getLocationRef() == null)) { + response.getEntity().setLocationRef(request.getResourceRef().getTargetRef().toString()); + } + } - if (protocol == null) { - throw new UnsupportedOperationException("Unable to determine the protocol to use for this call."); - } + /** + * Handles the call after resolving any URI template on the request's target resource reference. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public int beforeHandle(Request request, Response response) { + // Associate the response to the current thread + Protocol protocol = request.getProtocol(); - String targetUri = request.getResourceRef().toString(true, false); + if (protocol == null) { + throw new UnsupportedOperationException( + "Unable to determine the protocol to use for this call."); + } - if (targetUri.contains("{")) { - // Template URI detected, create the template - Template template = new Template(targetUri); + String targetUri = request.getResourceRef().toString(true, false); - // Set the formatted target URI - request.setResourceRef(template.format(request, response)); - } + if (targetUri.contains("{")) { + // Template URI detected, create the template + Template template = new Template(targetUri); - request.setOriginalRef(ReferenceUtils.getOriginalRef(request.getResourceRef(), request.getHeaders())); - return CONTINUE; - } + // Set the formatted target URI + request.setResourceRef(template.format(request, response)); + } + request.setOriginalRef( + ReferenceUtils.getOriginalRef(request.getResourceRef(), request.getHeaders())); + return CONTINUE; + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/WrapperScheduledExecutorService.java b/org.restlet/src/main/java/org/restlet/engine/util/WrapperScheduledExecutorService.java index 74c66707dd..95e65689d7 100644 --- a/org.restlet/src/main/java/org/restlet/engine/util/WrapperScheduledExecutorService.java +++ b/org.restlet/src/main/java/org/restlet/engine/util/WrapperScheduledExecutorService.java @@ -1,132 +1,141 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; import java.util.Collection; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** - * Wrapper of a {@link ScheduledExecutorService} instance, to prevent - * manipulation of the actual service. - * + * Wrapper of a {@link ScheduledExecutorService} instance, to prevent manipulation of the actual + * service. + * * @author Jerome Louvel */ public class WrapperScheduledExecutorService implements ScheduledExecutorService { - /** The wrapped executor service. */ - private final ScheduledExecutorService wrapped; - - /** - * Constructor. - * - * @param wrapped The wrapped executor service. - */ - public WrapperScheduledExecutorService(ScheduledExecutorService wrapped) { - this.wrapped = wrapped; - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return getWrapped().awaitTermination(timeout, unit); - } - - @Override - public void execute(Runnable command) { - getWrapped().execute(command); - } - - /** - * Returns the wrapped executor service. - * - * @return The wrapped executor service. - */ - protected ScheduledExecutorService getWrapped() { - return wrapped; - } - - @Override - public List> invokeAll(Collection> tasks) throws InterruptedException { - return getWrapped().invokeAll(tasks); - } - - @Override - public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException { - return getWrapped().invokeAll(tasks, timeout, unit); - } - - @Override - public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { - return getWrapped().invokeAny(tasks); - } - - @Override - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return getWrapped().invokeAny(tasks, timeout, unit); - } - - @Override - public boolean isShutdown() { - return getWrapped().isShutdown(); - } - - @Override - public boolean isTerminated() { - return getWrapped().isTerminated(); - } - - @Override - public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - return getWrapped().schedule(callable, delay, unit); - } - - @Override - public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - return getWrapped().schedule(command, delay, unit); - } - - @Override - public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { - return getWrapped().scheduleAtFixedRate(command, initialDelay, period, unit); - } - - @Override - public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { - return getWrapped().scheduleWithFixedDelay(command, initialDelay, delay, unit); - } - - @Override - public void shutdown() { - getWrapped().shutdown(); - } - - @Override - public List shutdownNow() { - return getWrapped().shutdownNow(); - } - - @Override - public Future submit(Callable task) { - return getWrapped().submit(task); - } - - @Override - public Future submit(Runnable task) { - return getWrapped().submit(task); - } - - @Override - public Future submit(Runnable task, T result) { - return getWrapped().submit(task, result); - } - + /** The wrapped executor service. */ + private final ScheduledExecutorService wrapped; + + /** + * Constructor. + * + * @param wrapped The wrapped executor service. + */ + public WrapperScheduledExecutorService(ScheduledExecutorService wrapped) { + this.wrapped = wrapped; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return getWrapped().awaitTermination(timeout, unit); + } + + @Override + public void execute(Runnable command) { + getWrapped().execute(command); + } + + /** + * Returns the wrapped executor service. + * + * @return The wrapped executor service. + */ + protected ScheduledExecutorService getWrapped() { + return wrapped; + } + + @Override + public List> invokeAll(Collection> tasks) + throws InterruptedException { + return getWrapped().invokeAll(tasks); + } + + @Override + public List> invokeAll( + Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return getWrapped().invokeAll(tasks, timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) + throws InterruptedException, ExecutionException { + return getWrapped().invokeAny(tasks); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return getWrapped().invokeAny(tasks, timeout, unit); + } + + @Override + public boolean isShutdown() { + return getWrapped().isShutdown(); + } + + @Override + public boolean isTerminated() { + return getWrapped().isTerminated(); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + return getWrapped().schedule(callable, delay, unit); + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return getWrapped().schedule(command, delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate( + Runnable command, long initialDelay, long period, TimeUnit unit) { + return getWrapped().scheduleAtFixedRate(command, initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + return getWrapped().scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + + @Override + public void shutdown() { + getWrapped().shutdown(); + } + + @Override + public List shutdownNow() { + return getWrapped().shutdownNow(); + } + + @Override + public Future submit(Callable task) { + return getWrapped().submit(task); + } + + @Override + public Future submit(Runnable task) { + return getWrapped().submit(task); + } + + @Override + public Future submit(Runnable task, T result) { + return getWrapped().submit(task, result); + } } diff --git a/org.restlet/src/main/java/org/restlet/engine/util/package-info.java b/org.restlet/src/main/java/org/restlet/engine/util/package-info.java new file mode 100644 index 0000000000..21cef4a769 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/engine/util/package-info.java @@ -0,0 +1,6 @@ +/** + * General utilities. + * + * @since Restlet 2.0 + */ +package org.restlet.engine.util; diff --git a/org.restlet/src/main/java/org/restlet/engine/util/package.html b/org.restlet/src/main/java/org/restlet/engine/util/package.html deleted file mode 100644 index bd7c30a7ff..0000000000 --- a/org.restlet/src/main/java/org/restlet/engine/util/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - -General utilities. -

-@since Restlet 2.0 - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/package-info.java b/org.restlet/src/main/java/org/restlet/package-info.java new file mode 100644 index 0000000000..8e57d8f2e1 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/package-info.java @@ -0,0 +1,12 @@ +/** + * Core classes of the Restlet API. + * + * @since Restlet 1.0 + * @see REST dissertation by Roy + * T. Fielding + * @see User Guide - + * Restlet API + * @see User Guide - + * Base package + */ +package org.restlet; diff --git a/org.restlet/src/main/java/org/restlet/package.html b/org.restlet/src/main/java/org/restlet/package.html deleted file mode 100644 index 0f77683520..0000000000 --- a/org.restlet/src/main/java/org/restlet/package.html +++ /dev/null @@ -1,10 +0,0 @@ - - - Core classes of the Restlet API. -

- @since Restlet 1.0 - @see REST dissertation by Roy T. Fielding - @see User Guide - Restlet API - @see User Guide - Base package - - diff --git a/org.restlet/src/main/java/org/restlet/representation/AppendableRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/AppendableRepresentation.java index 6e585fd1ee..ee1b448e48 100644 --- a/org.restlet/src/main/java/org/restlet/representation/AppendableRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/AppendableRepresentation.java @@ -1,20 +1,18 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; +import java.io.IOException; import org.restlet.data.CharacterSet; import org.restlet.data.Language; import org.restlet.data.MediaType; -import java.io.IOException; - /** * Represents an appendable sequence of characters. * @@ -22,123 +20,122 @@ */ public class AppendableRepresentation extends StringRepresentation implements Appendable { - /** The appendable text. */ - private volatile StringBuilder appendableText; - - /** - * Constructor. The following metadata are used by default: "text/plain" media - * type, no language and the ISO-8859-1 character set. - */ - public AppendableRepresentation() { - this(null); - } - - /** - * Constructor. The following metadata are used by default: "text/plain" media - * type, no language and the ISO-8859-1 character set. - * - * @param text The string value. - */ - public AppendableRepresentation(CharSequence text) { - super(text); - } - - /** - * Constructor. The following metadata are used by default: "text/plain" media - * type, no language and the ISO-8859-1 character set. - * - * @param text The string value. - * @param language The language. - */ - public AppendableRepresentation(CharSequence text, Language language) { - super(text, language); - } - - /** - * Constructor. The following metadata are used by default: no language and the - * ISO-8859-1 character set. - * - * @param text The string value. - * @param mediaType The media type. - */ - public AppendableRepresentation(CharSequence text, MediaType mediaType) { - super(text, mediaType); - } - - /** - * Constructor. The following metadata are used by default: ISO-8859-1 character - * set. - * - * @param text The string value. - * @param mediaType The media type. - * @param language The language. - */ - public AppendableRepresentation(CharSequence text, MediaType mediaType, Language language) { - super(text, mediaType, language); - } - - /** - * Constructor. - * - * @param text The string value. - * @param mediaType The media type. - * @param language The language. - * @param characterSet The character set. - */ - public AppendableRepresentation(CharSequence text, MediaType mediaType, Language language, - CharacterSet characterSet) { - super(text, mediaType, language, characterSet); - } - - @Override - public Appendable append(char c) throws IOException { - if (this.appendableText == null) { - this.appendableText = new StringBuilder().append(c); - } else { - this.appendableText.append(c); - } - - return this; - } - - @Override - public Appendable append(CharSequence csq) throws IOException { - if (this.appendableText == null) { - this.appendableText = new StringBuilder(csq); - } else { - this.appendableText.append(csq); - } - - return this; - } - - @Override - public Appendable append(CharSequence csq, int start, int end) throws IOException { - if (this.appendableText == null) { - this.appendableText = new StringBuilder(); - } - - this.appendableText.append(csq, start, end); - - return this; - } - - @Override - public String getText() { - return (this.appendableText == null) ? null : this.appendableText.toString(); - } - - @Override - public void setText(CharSequence text) { - if (text != null) { - if (this.appendableText == null) { - this.appendableText = new StringBuilder(text); - } else { - this.appendableText.delete(0, this.appendableText.length()); - this.appendableText.append(text); - } - } else { - this.appendableText = null; - } - } + /** The appendable text. */ + private volatile StringBuilder appendableText; + + /** + * Constructor. The following metadata is used by default: "text/plain" media type, no language, + * and the ISO-8859-1 character set. + */ + public AppendableRepresentation() { + this(null); + } + + /** + * Constructor. The following metadata is used by default: "text/plain" media type, no language, + * and the ISO-8859-1 character set. + * + * @param text The string value. + */ + public AppendableRepresentation(CharSequence text) { + super(text); + } + + /** + * Constructor. The following metadata is used by default: "text/plain" media type, no language, + * and the ISO-8859-1 character set. + * + * @param text The string value. + * @param language The language. + */ + public AppendableRepresentation(CharSequence text, Language language) { + super(text, language); + } + + /** + * Constructor. The following metadata is used by default: no language and the ISO-8859-1 + * character set. + * + * @param text The string value. + * @param mediaType The media type. + */ + public AppendableRepresentation(CharSequence text, MediaType mediaType) { + super(text, mediaType); + } + + /** + * Constructor. The following metadata is used by default: ISO-8859-1 character set. + * + * @param text The string value. + * @param mediaType The media type. + * @param language The language. + */ + public AppendableRepresentation(CharSequence text, MediaType mediaType, Language language) { + super(text, mediaType, language); + } + + /** + * Constructor. + * + * @param text The string value. + * @param mediaType The media type. + * @param language The language. + * @param characterSet The character set. + */ + public AppendableRepresentation( + CharSequence text, MediaType mediaType, Language language, CharacterSet characterSet) { + super(text, mediaType, language, characterSet); + } + + @Override + public Appendable append(char c) throws IOException { + if (this.appendableText == null) { + this.appendableText = new StringBuilder().append(c); + } else { + this.appendableText.append(c); + } + + return this; + } + + @Override + public Appendable append(CharSequence csq) throws IOException { + if (this.appendableText == null) { + this.appendableText = new StringBuilder(csq); + } else { + this.appendableText.append(csq); + } + + return this; + } + + @Override + public Appendable append(CharSequence csq, int start, int end) throws IOException { + if (this.appendableText == null) { + this.appendableText = new StringBuilder(); + } + + this.appendableText.append(csq, start, end); + + return this; + } + + @Override + public String getText() { + return (this.appendableText == null) ? null : this.appendableText.toString(); + } + + @Override + public void setText(CharSequence text) { + if (text != null) { + if (this.appendableText == null) { + this.appendableText = new StringBuilder(text); + } else { + this.appendableText.delete(0, this.appendableText.length()); + this.appendableText.append(text); + } + } else { + this.appendableText = null; + } + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/BufferingRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/BufferingRepresentation.java index 70b74a35e1..5ed4a8e7d9 100644 --- a/org.restlet/src/main/java/org/restlet/representation/BufferingRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/BufferingRepresentation.java @@ -1,170 +1,173 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.engine.io.IoUtils; import org.restlet.util.WrapperRepresentation; -import java.io.*; -import java.util.logging.Level; - /** - * Representation capable of buffering the wrapped representation. This is - * useful when you want to prevent chunk encoding from being used for dynamic - * representations or when you want to reuse a transient representation several - * times.
+ * Representation capable of buffering the wrapped representation. This is useful when you want to + * prevent chunk encoding from being used for dynamic representations or when you want to reuse a + * transient representation several times.
*
- * Be careful as this class could create potentially very large byte buffers in - * memory that could impact your application performance. - * + * Be careful as this class could create potentially very large byte buffers in memory that could + * impact your application performance. + * * @author Thierry Boileau */ public class BufferingRepresentation extends WrapperRepresentation { - /** The cached content as an array of bytes. */ - private volatile byte[] buffer; - - /** Indicates if the wrapped entity has been already cached. */ - private volatile boolean buffered; - - /** - * Constructor. - * - * @param bufferedRepresentation The representation to buffer. - */ - public BufferingRepresentation(Representation bufferedRepresentation) { - super(bufferedRepresentation); - setTransient(false); - } - - /** - * Buffers the content of the wrapped entity. - * - * @throws IOException - */ - private void buffer() throws IOException { - if (!isBuffered()) { - if (getWrappedRepresentation().isAvailable()) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - getWrappedRepresentation().write(baos); - baos.flush(); - setBuffer(baos.toByteArray()); - setBuffered(true); - } - } - } - - @Override - public long getAvailableSize() { - return getSize(); - } - - /** - * Returns the buffered content as an array of bytes. - * - * @return The buffered content as an array of bytes. - */ - protected byte[] getBuffer() { - return buffer; - } - - @Override - public Reader getReader() throws IOException { - return IoUtils.getReader(getStream(), getCharacterSet()); - } - - @Override - public long getSize() { - // Read the content, store it and compute the size. - try { - buffer(); - } catch (IOException e) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to buffer the wrapped representation", e); - } - - return (getBuffer() != null) ? getBuffer().length : -1L; - } - - @Override - public InputStream getStream() throws IOException { - buffer(); - return (getBuffer() != null) ? new ByteArrayInputStream(getBuffer()) : null; - }; - - @Override - public String getText() throws IOException { - buffer(); - - if (getBuffer() != null) { - return (getCharacterSet() != null) ? new String(getBuffer(), getCharacterSet().toCharset()) - : new String(getBuffer()); - } - - return null; - } - - @Override - public boolean isAvailable() { - try { - buffer(); - } catch (IOException e) { - Context.getCurrentLogger().log(Level.FINER, "Unable to buffer the wrapped representation", e); - } - - return isBuffered(); - } - - /** - * Indicates if the wrapped entity has been already buffered. - * - * @return True if the wrapped entity has been already buffered. - */ - protected boolean isBuffered() { - return buffered; - } - - /** - * Sets the buffered content as an array of bytes. - * - * @param buffer The buffered content as an array of bytes. - */ - protected void setBuffer(byte[] buffer) { - this.buffer = buffer; - } - - /** - * Indicates if the wrapped entity has been already buffered. - * - * @param buffered True if the wrapped entity has been already buffered. - */ - protected void setBuffered(boolean buffered) { - this.buffered = buffered; - } - - @Override - public void write(OutputStream outputStream) throws IOException { - buffer(); - - if (getBuffer() != null) { - outputStream.write(getBuffer()); - } - } - - @Override - public void write(Writer writer) throws IOException { - String text = getText(); - - if (text != null) { - writer.write(text); - } - } - + /** The cached content as an array of bytes. */ + private volatile byte[] buffer; + + /** Indicates if the wrapped entity has been already cached. */ + private volatile boolean buffered; + + /** + * Constructor. + * + * @param bufferedRepresentation The representation to buffer. + */ + public BufferingRepresentation(Representation bufferedRepresentation) { + super(bufferedRepresentation); + setTransient(false); + } + + /** + * Buffers the content of the wrapped entity. + * + * @throws IOException + */ + private void buffer() throws IOException { + if (!isBuffered() && getWrappedRepresentation().isAvailable()) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + getWrappedRepresentation().write(baos); + baos.flush(); + setBuffer(baos.toByteArray()); + setBuffered(true); + } + } + + @Override + public long getAvailableSize() { + return getSize(); + } + + /** + * Returns the buffered content as an array of bytes. + * + * @return The buffered content as an array of bytes. + */ + protected byte[] getBuffer() { + return buffer; + } + + @Override + public Reader getReader() throws IOException { + return IoUtils.getReader(getStream(), getCharacterSet()); + } + + @Override + public long getSize() { + // Read the content, store it and compute the size. + try { + buffer(); + } catch (IOException e) { + Context.getCurrentLogger() + .log(Level.WARNING, "Unable to buffer the wrapped representation", e); + } + + return (getBuffer() != null) ? getBuffer().length : -1L; + } + + @Override + public InputStream getStream() throws IOException { + buffer(); + return (getBuffer() != null) ? new ByteArrayInputStream(getBuffer()) : null; + } + + @Override + public String getText() throws IOException { + buffer(); + + if (getBuffer() != null) { + return (getCharacterSet() != null) + ? new String(getBuffer(), getCharacterSet().toCharset()) + : new String(getBuffer()); + } + + return null; + } + + @Override + public boolean isAvailable() { + try { + buffer(); + } catch (IOException e) { + Context.getCurrentLogger() + .log(Level.FINER, "Unable to buffer the wrapped representation", e); + } + + return isBuffered(); + } + + /** + * Indicates if the wrapped entity has been already buffered. + * + * @return True if the wrapped entity has been already buffered. + */ + protected boolean isBuffered() { + return buffered; + } + + /** + * Sets the buffered content as an array of bytes. + * + * @param buffer The buffered content as an array of bytes. + */ + protected void setBuffer(byte[] buffer) { + this.buffer = buffer; + } + + /** + * Indicates if the wrapped entity has been already buffered. + * + * @param buffered True if the wrapped entity has been already buffered. + */ + protected void setBuffered(boolean buffered) { + this.buffered = buffered; + } + + @Override + public void write(OutputStream outputStream) throws IOException { + buffer(); + + if (getBuffer() != null) { + outputStream.write(getBuffer()); + } + } + + @Override + public void write(Writer writer) throws IOException { + String text = getText(); + + if (text != null) { + writer.write(text); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/ByteArrayRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/ByteArrayRepresentation.java index 92df21d687..a7fbdec915 100644 --- a/org.restlet/src/main/java/org/restlet/representation/ByteArrayRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/ByteArrayRepresentation.java @@ -1,85 +1,79 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; -import org.restlet.data.MediaType; - import java.io.ByteArrayInputStream; +import org.restlet.data.MediaType; /** * Representation wrapping a byte array. - * + * * @author Jerome Louvel */ public class ByteArrayRepresentation extends InputRepresentation { - /** - * Constructor. - * - * @param byteArray The byte array to wrap. - */ - public ByteArrayRepresentation(byte[] byteArray) { - super(new ByteArrayInputStream(byteArray)); - } - - /** - * - * @param byteArray The byte array to wrap. - * @param offSet The offset inside the byte array. - * @param length The length to expose inside the byte array. - */ - public ByteArrayRepresentation(byte[] byteArray, int offSet, int length) { - super(new ByteArrayInputStream(byteArray, offSet, length)); - } + /** + * Constructor. + * + * @param byteArray The byte array to wrap. + */ + public ByteArrayRepresentation(byte[] byteArray) { + super(new ByteArrayInputStream(byteArray)); + } - /** - * - * @param byteArray The byte array to wrap. - * @param offSet The offset inside the byte array. - * @param length The length to expose inside the byte array. - * @param mediaType - */ - public ByteArrayRepresentation(byte[] byteArray, int offSet, int length, MediaType mediaType) { - super(new ByteArrayInputStream(byteArray, offSet, length), mediaType); - } + /** + * @param byteArray The byte array to wrap. + * @param offSet The offset inside the byte array. + * @param length The length to expose inside the byte array. + */ + public ByteArrayRepresentation(byte[] byteArray, int offSet, int length) { + super(new ByteArrayInputStream(byteArray, offSet, length)); + } - /** - * - * @param byteArray The byte array to wrap. - * @param offSet The offset inside the byte array. - * @param length The length to expose inside the byte array. - * @param mediaType The media type. - * @param expectedSize - */ - public ByteArrayRepresentation(byte[] byteArray, int offSet, int length, MediaType mediaType, long expectedSize) { - super(new ByteArrayInputStream(byteArray, offSet, length), mediaType, expectedSize); - } + /** + * @param byteArray The byte array to wrap. + * @param offSet The offset inside the byte array. + * @param length The length to expose inside the byte array. + * @param mediaType + */ + public ByteArrayRepresentation(byte[] byteArray, int offSet, int length, MediaType mediaType) { + super(new ByteArrayInputStream(byteArray, offSet, length), mediaType); + } - /** - * Constructor. - * - * @param byteArray The byte array to wrap. - * @param mediaType The media type. - */ - public ByteArrayRepresentation(byte[] byteArray, MediaType mediaType) { - super(new ByteArrayInputStream(byteArray), mediaType); - } + /** + * @param byteArray The byte array to wrap. + * @param offSet The offset inside the byte array. + * @param length The length to expose inside the byte array. + * @param mediaType The media type. + * @param expectedSize + */ + public ByteArrayRepresentation( + byte[] byteArray, int offSet, int length, MediaType mediaType, long expectedSize) { + super(new ByteArrayInputStream(byteArray, offSet, length), mediaType, expectedSize); + } - /** - * - * @param byteArray The byte array to wrap. - * @param mediaType The media type. - * @param expectedSize - */ - public ByteArrayRepresentation(byte[] byteArray, MediaType mediaType, long expectedSize) { - super(new ByteArrayInputStream(byteArray), mediaType, expectedSize); - } + /** + * Constructor. + * + * @param byteArray The byte array to wrap. + * @param mediaType The media type. + */ + public ByteArrayRepresentation(byte[] byteArray, MediaType mediaType) { + super(new ByteArrayInputStream(byteArray), mediaType); + } + /** + * @param byteArray The byte array to wrap. + * @param mediaType The media type. + * @param expectedSize + */ + public ByteArrayRepresentation(byte[] byteArray, MediaType mediaType, long expectedSize) { + super(new ByteArrayInputStream(byteArray), mediaType, expectedSize); + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/CharacterRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/CharacterRepresentation.java index 15c61f254f..fbe663454b 100644 --- a/org.restlet/src/main/java/org/restlet/representation/CharacterRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/CharacterRepresentation.java @@ -1,49 +1,46 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; -import org.restlet.data.CharacterSet; -import org.restlet.data.MediaType; -import org.restlet.engine.io.IoUtils; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; +import org.restlet.data.CharacterSet; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; /** * Representation based on a BIO character stream. - * + * * @author Jerome Louvel */ public abstract class CharacterRepresentation extends Representation { - /** - * Constructor. - * - * @param mediaType The media type. - */ - public CharacterRepresentation(MediaType mediaType) { - super(mediaType); - setCharacterSet(CharacterSet.UTF_8); - } - - @Override - public InputStream getStream() throws IOException { - return IoUtils.getStream(getReader(), getCharacterSet()); - } + /** + * Constructor. + * + * @param mediaType The media type. + */ + protected CharacterRepresentation(MediaType mediaType) { + super(mediaType); + setCharacterSet(CharacterSet.UTF_8); + } - @Override - public void write(OutputStream outputStream) throws IOException { - Writer writer = IoUtils.getWriter(outputStream, getCharacterSet()); - write(writer); - writer.flush(); - } + @Override + public InputStream getStream() throws IOException { + return IoUtils.getStream(getReader(), getCharacterSet()); + } + @Override + public void write(OutputStream outputStream) throws IOException { + Writer writer = IoUtils.getWriter(outputStream, getCharacterSet()); + write(writer); + writer.flush(); + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/DigesterRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/DigesterRepresentation.java index 7b5fb0b4ec..8d3a9d0ef9 100644 --- a/org.restlet/src/main/java/org/restlet/representation/DigesterRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/DigesterRepresentation.java @@ -1,236 +1,234 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; -import org.restlet.Context; -import org.restlet.data.Digest; -import org.restlet.engine.io.IoUtils; -import org.restlet.util.WrapperRepresentation; - -import java.io.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; import java.security.DigestInputStream; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.data.Digest; +import org.restlet.engine.io.IoUtils; +import org.restlet.util.WrapperRepresentation; /** - * Representation capable of computing a digest. It wraps another representation - * and allows to get the computed digest of the wrapped entity after reading or - * writing operations. The final digest value is guaranteed to be correct only - * after the wrapped representation has been entirely exhausted (that is to say - * read or written).
+ * Representation capable of computing a digest. It wraps another representation and allows to get + * the computed digest of the wrapped entity after reading or writing operations. The final digest + * value is guaranteed to be correct only after the wrapped representation has been entirely + * exhausted (meaning read or written).
*
- * This wrapper allows getting the computed digest at the same time the - * representation is read or written. It does not need two separate operations - * which may require specific attention for transient representations. + * This wrapper allows getting the computed digest at the same time the representation is read or + * written. It does not need two separate operations which may require specific attention for + * transient representations. * * @author Jerome Louvel * @see Representation#isTransient() . */ public class DigesterRepresentation extends WrapperRepresentation { - /** The digest algorithm. */ - private final String algorithm; - - /** The computed digest value. */ - private volatile MessageDigest computedDigest; - - /** - * Constructor.
- * By default, the instance relies on the {@link Digest#ALGORITHM_MD5} digest - * algorithm. - * - * @param wrappedRepresentation The wrapped representation. - * @throws NoSuchAlgorithmException - */ - public DigesterRepresentation(Representation wrappedRepresentation) throws NoSuchAlgorithmException { - this(wrappedRepresentation, Digest.ALGORITHM_MD5); - } - - /** - * Constructor.
- * - * @param wrappedRepresentation The wrapped representation. - * @param algorithm The digest algorithm. - * @throws NoSuchAlgorithmException - */ - public DigesterRepresentation(Representation wrappedRepresentation, String algorithm) - throws NoSuchAlgorithmException { - super(wrappedRepresentation); - this.algorithm = algorithm; - this.computedDigest = MessageDigest.getInstance(algorithm); - } - - /** - * Check that the digest computed from the representation content and the digest - * declared by the representation are the same.
- * Since this method relies on the {@link #computeDigest(String)} method, and - * since this method reads entirely the representation's stream, user must take - * care of the content of the representation in case the latter is transient. - * - * @return True if both digests are not null and equals. - */ - public boolean checkDigest() { - Digest digest = getDigest(); - return (digest != null && digest.equals(getComputedDigest())); - } - - /** - * Check that the digest computed from the representation content and the digest - * declared by the representation are the same. It also first checks that the - * algorithms are the same.
- * Since this method relies on the {@link #computeDigest(String)} method, and - * since this method reads entirely the representation's stream, user must take - * care of the content of the representation in case the latter is transient. - * - * @param algorithm The algorithm used to compute the digest to compare with. - * See constant values in {@link org.restlet.data.Digest}. - * @return True if both digests are not null and equals. - */ - public boolean checkDigest(String algorithm) { - boolean result = false; - - if (this.algorithm != null && this.algorithm.equals(algorithm)) { - result = checkDigest(); - } else { - Digest digest = getDigest(); - - if (digest != null) { - if (algorithm.equals(digest.getAlgorithm())) { - result = digest.equals(computeDigest(algorithm)); - } - } - } - - return result; - } - - /** - * Compute the representation digest according to MD5 algorithm.
- * If case this algorithm is the same as the one provided at instantiation, - * the computation operation is made with the current stored computed value and - * does not require to entirely exhaust the representation's stream. - */ - public Digest computeDigest() { - return computeDigest(Digest.ALGORITHM_MD5); - } - - /** - * Compute the representation digest according to the given algorithm.
- * Since this method entirely reads the representation stream, user must take - * care of the content of the representation in case the latter is transient. - * - * @param algorithm The algorithm used to compute the digest. See constant - * values in {@link org.restlet.data.Digest}. - * @return The computed digest or null if the digest cannot be computed. - */ - public Digest computeDigest(String algorithm) { - Digest result = null; - - if (this.algorithm != null && this.algorithm.equals(algorithm)) { - result = getComputedDigest(); - } else if (isAvailable()) { - try { - java.security.MessageDigest md = java.security.MessageDigest.getInstance(algorithm); - try (java.security.DigestInputStream dis = new java.security.DigestInputStream(getStream(), md)) { - org.restlet.engine.io.IoUtils.exhaust(dis); - } - result = new org.restlet.data.Digest(algorithm, md.digest()); - } catch (NoSuchAlgorithmException | IOException e) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to check the digest of the representation.", e); - } - } - - return result; - } - - /** - * Exhausts the content of the representation by reading it and silently - * discarding anything read. - * - * @return The number of bytes consumed or -1 if unknown. - */ - @Override - public long exhaust() throws IOException { - long result = -1L; - - if (isAvailable()) { - result = IoUtils.exhaust(getStream()); - } - - return result; - } - - /** - * Returns the current computed digest value of the representation. User must be - * aware that, if the representation has not been entirely read or written, the - * computed digest value may not be accurate. - * - * @return The current computed digest value. - */ - public Digest getComputedDigest() { - return new Digest(this.algorithm, computedDigest.digest()); - } - - @Override - public Reader getReader() throws IOException { - return IoUtils.getReader(getStream(), getCharacterSet()); - } - - /** - * {@inheritDoc}
- * - * The stream of the underlying representation is wrapped with a new instance of - * the {@link DigestInputStream} class, which allows to compute progressively - * the digest value. - */ - @Override - public InputStream getStream() throws IOException { - return new DigestInputStream(getWrappedRepresentation().getStream(), this.computedDigest); - } - - @Override - public String getText() throws IOException { - String result = null; - - if (isAvailable()) { - if (getSize() == 0) { - result = ""; - } else { - java.io.StringWriter sw = new java.io.StringWriter(); - write(sw); - result = sw.toString(); - } - } - - return result; - } - - /** - * {@inheritDoc}
- * - * The output stream is wrapped with a new instance of the - * {@link DigestOutputStream} class, which allows to compute progressively the - * digest value. - */ - @Override - public void write(OutputStream outputStream) throws IOException { - OutputStream dos = new DigestOutputStream(outputStream, this.computedDigest); - getWrappedRepresentation().write(dos); - dos.flush(); - } - - @Override - public void write(Writer writer) throws IOException { - OutputStream os = IoUtils.getStream(writer, getCharacterSet()); - write(os); - os.flush(); - } + /** The digest algorithm. */ + private final String algorithm; + + /** The computed digest value. */ + private volatile MessageDigest computedDigest; + + /** + * Constructor.
+ * By default, the instance relies on the {@link Digest#ALGORITHM_MD5} digest algorithm. + * + * @param wrappedRepresentation The wrapped representation. + * @throws NoSuchAlgorithmException + */ + public DigesterRepresentation(Representation wrappedRepresentation) + throws NoSuchAlgorithmException { + this(wrappedRepresentation, Digest.ALGORITHM_MD5); + } + + /** + * Constructor.
+ * + * @param wrappedRepresentation The wrapped representation. + * @param algorithm The digest algorithm. + * @throws NoSuchAlgorithmException + */ + public DigesterRepresentation(Representation wrappedRepresentation, String algorithm) + throws NoSuchAlgorithmException { + super(wrappedRepresentation); + this.algorithm = algorithm; + this.computedDigest = MessageDigest.getInstance(algorithm); + } + + /** + * Check that the digest computed from the representation content and the digest declared by the + * representation are the same.
+ * Since this method relies on the {@link #computeDigest(String)} method, and since this method + * entirely reads the representation's stream, user must take care of the content of the + * representation in case the latter is transient. + * + * @return True if both digests are not null and equals. + */ + public boolean checkDigest() { + Digest digest = getDigest(); + return (digest != null && digest.equals(getComputedDigest())); + } + + /** + * Check that the digest computed from the representation content and the digest declared by the + * representation are the same. It also first checks that the algorithms are the same.
+ * Since this method relies on the {@link #computeDigest(String)} method, and since this method + * entirely reads the representation's stream, user must take care of the content of the + * representation in case the latter is transient. + * + * @param algorithm The algorithm used to compute the digest to compare with. See constant + * values in {@link org.restlet.data.Digest}. + * @return True if both digests are not null and equals. + */ + public boolean checkDigest(String algorithm) { + boolean result = false; + + if (this.algorithm != null && this.algorithm.equals(algorithm)) { + result = checkDigest(); + } else { + Digest digest = getDigest(); + + if (digest != null) { + if (algorithm.equals(digest.getAlgorithm())) { + result = digest.equals(computeDigest(algorithm)); + } + } + } + + return result; + } + + /** + * Compute the representation digest according to MD5 algorithm.
+ * If case this algorithm is the same as the one provided at instantiation, the computation + * operation is made with the current stored computed value and does not require to entirely + * exhaust the representation's stream. + */ + public Digest computeDigest() { + return computeDigest(Digest.ALGORITHM_MD5); + } + + /** + * Compute the representation digest according to the given algorithm.
+ * Since this method entirely reads the representation stream, the user must take care of the + * content of the representation in case the latter is transient. + * + * @param algorithm The algorithm used to compute the digest. See constant values in {@link + * org.restlet.data.Digest}. + * @return The computed digest or null if the digest cannot be computed. + */ + public Digest computeDigest(String algorithm) { + Digest result = null; + + if (this.algorithm != null && this.algorithm.equals(algorithm)) { + result = getComputedDigest(); + } else if (isAvailable()) { + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance(algorithm); + try (java.security.DigestInputStream dis = + new java.security.DigestInputStream(getStream(), md)) { + org.restlet.engine.io.IoUtils.exhaust(dis); + } + result = new org.restlet.data.Digest(algorithm, md.digest()); + } catch (NoSuchAlgorithmException | IOException e) { + Context.getCurrentLogger() + .log(Level.WARNING, "Unable to check the digest of the representation.", e); + } + } + + return result; + } + + /** + * Exhausts the content of the representation by reading it and silently discarding anything + * read. + * + * @return The number of bytes consumed or -1 if unknown. + */ + @Override + public long exhaust() throws IOException { + long result = -1L; + + if (isAvailable()) { + result = IoUtils.exhaust(getStream()); + } + + return result; + } + + /** + * Returns the current computed digest value of the representation. User must be aware that, if + * the representation has not been entirely read or written, the computed digest value may not + * be accurate. + * + * @return The current computed digest value. + */ + public Digest getComputedDigest() { + return new Digest(this.algorithm, computedDigest.digest()); + } + + @Override + public Reader getReader() throws IOException { + return IoUtils.getReader(getStream(), getCharacterSet()); + } + + /** + * {@inheritDoc}
+ * The stream of the underlying representation is wrapped with a new instance of the {@link + * DigestInputStream} class, which allows progressively computing the digest value. + */ + @Override + public InputStream getStream() throws IOException { + return new DigestInputStream(getWrappedRepresentation().getStream(), this.computedDigest); + } + + @Override + public String getText() throws IOException { + String result = null; + + if (isAvailable()) { + if (getSize() == 0) { + result = ""; + } else { + java.io.StringWriter sw = new java.io.StringWriter(); + write(sw); + result = sw.toString(); + } + } + + return result; + } + + /** + * {@inheritDoc}
+ * The output stream is wrapped with a new instance of the {@link DigestOutputStream} class, + * which allows progressively computing the digest value. + */ + @Override + public void write(OutputStream outputStream) throws IOException { + OutputStream dos = new DigestOutputStream(outputStream, this.computedDigest); + getWrappedRepresentation().write(dos); + dos.flush(); + } + + @Override + public void write(Writer writer) throws IOException { + OutputStream os = IoUtils.getStream(writer, getCharacterSet()); + write(os); + os.flush(); + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/EmptyRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/EmptyRepresentation.java index 21cf0556b8..6441b8e4dc 100644 --- a/org.restlet/src/main/java/org/restlet/representation/EmptyRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/EmptyRepresentation.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; import java.io.IOException; @@ -15,45 +14,43 @@ import java.io.Reader; /** - * Empty representation with no content. It is always considered available but - * calling the {@link #getText()} method for example will return an empty - * string. It can also have regular metadata available. - * + * Empty representation with no content. It is always considered available but calling the {@link + * #getText()} method for example will return an empty string. It can also have regular metadata + * available. + * * @author Jerome Louvel */ public class EmptyRepresentation extends Representation { - /** - * Constructor. - */ - public EmptyRepresentation() { - setAvailable(false); - setTransient(true); - setSize(0); - } - - @Override - public Reader getReader() throws IOException { - return null; - } - - @Override - public InputStream getStream() throws IOException { - return null; - } - - @Override - public String getText() throws IOException { - return null; - } - - @Override - public void write(java.io.Writer writer) throws IOException { - // Do nothing - } - - @Override - public void write(OutputStream outputStream) throws IOException { - // Do nothing - } + /** Constructor. */ + public EmptyRepresentation() { + setAvailable(false); + setTransient(true); + setSize(0); + } + + @Override + public Reader getReader() throws IOException { + return null; + } + + @Override + public InputStream getStream() throws IOException { + return null; + } + + @Override + public String getText() throws IOException { + return null; + } + + @Override + public void write(java.io.Writer writer) throws IOException { + // Do nothing + } + + @Override + public void write(OutputStream outputStream) throws IOException { + // Do nothing + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/FileRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/FileRepresentation.java index c543e8d1fc..9762b3bbf6 100644 --- a/org.restlet/src/main/java/org/restlet/representation/FileRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/FileRepresentation.java @@ -1,215 +1,209 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.util.Date; import org.restlet.data.Disposition; import org.restlet.data.LocalReference; import org.restlet.data.MediaType; import org.restlet.engine.io.IoUtils; -import java.io.*; -import java.util.Date; - /** - * Representation based on a static file. Note that in order for Web clients to - * display a download box upon reception of a file representation, it needs an - * additional call to {@link Disposition#setType(String)} with a - * {@link Disposition#TYPE_ATTACHMENT} value. - * + * Representation based on a static file. Note that in order for Web clients to display a download + * box upon reception of a file representation, it needs an additional call to {@link + * Disposition#setType(String)} with a {@link Disposition#TYPE_ATTACHMENT} value. + * * @author Jerome Louvel */ public class FileRepresentation extends Representation { - /** - * Creates a new file by detecting if the name is a URI or a simple path name. - * - * @param path The path name or file URI of the represented file (either in - * system format or in 'file:///' format). - * @return The associated File instance. - */ - private static File createFile(String path) { - if (path.startsWith("file://")) { - return new LocalReference(path).getFile(); - } - - return new File(path); - } - - /** - * Indicates if this file should be automatically deleted on release of the - * representation. - */ - private volatile boolean autoDeleting; - - /** The file handle. */ - private volatile File file; - - /** - * Constructor that does not set an expiration date for {@code file} - * - * @param file The represented file. - * @param mediaType The representation's media type. - * @see #FileRepresentation(File, MediaType, int) - */ - public FileRepresentation(File file, MediaType mediaType) { - this(file, mediaType, -1); - } - - /** - * Constructor. If a positive "timeToLive" parameter is given, then the - * expiration date is set accordingly. If "timeToLive" is equal to zero, then - * the expiration date is set to the current date, meaning that it will - * immediately expire on the client. If -1 is given, then no expiration date is - * set. - * - * @param file The represented file. - * @param mediaType The representation's media type. - * @param timeToLive The time to live before it expires (in seconds). - */ - public FileRepresentation(File file, MediaType mediaType, int timeToLive) { - super(mediaType); - this.file = file; - setModificationDate(new Date(file.lastModified())); - - if (timeToLive == 0) { - setExpirationDate(null); - } else if (timeToLive > 0) { - setExpirationDate(new Date(System.currentTimeMillis() + (1000L * timeToLive))); - } - - setMediaType(mediaType); - Disposition disposition = new Disposition(); - disposition.setFilename(file.getName()); - this.setDisposition(disposition); - } - - /** - * Constructor that does not set an expiration date for {@code path} - * - * @param path The path name or file URI of the represented file (either in - * system format or in 'file:///' format). - * @param mediaType The representation's media type. - * @see #FileRepresentation(String, MediaType, int) - */ - public FileRepresentation(String path, MediaType mediaType) { - this(path, mediaType, -1); - } - - /** - * Constructor. - * - * @param path The path name or file URI of the represented file (either - * in system format or in 'file:///' format). - * @param mediaType The representation's media type. - * @param timeToLive The time to live before it expires (in seconds). - * @see java.io.File#File(String) - */ - public FileRepresentation(String path, MediaType mediaType, int timeToLive) { - this(createFile(path), mediaType, timeToLive); - } - - /** - * Returns the file handle. - * - * @return the file handle. - */ - public File getFile() { - return this.file; - } - - @Override - public Reader getReader() throws IOException { - return new FileReader(this.file); - } - - @Override - public long getSize() { - if (super.getSize() != UNKNOWN_SIZE) { - return super.getSize(); - } - - return this.file.length(); - } - - @Override - public FileInputStream getStream() throws IOException { - try { - return new FileInputStream(this.file); - } catch (FileNotFoundException fnfe) { - throw new IOException("Couldn't get the stream. File not found"); - } - } - - /** - * Note that this method relies on {@link #getStream()}. This stream is closed - * once fully read. - */ - @Override - public String getText() throws IOException { - return IoUtils.toString(getStream(), getCharacterSet()); - } - - /** - * Indicates if this file should be automatically deleted on release of the - * representation. - * - * @return True if this file should be automatically deleted on release of the - * representation. - */ - public boolean isAutoDeleting() { - return autoDeleting; - } - - /** - * Releases the file handle. - */ - @Override - public void release() { - if (isAutoDeleting() && getFile() != null) { - try { - IoUtils.delete(getFile(), true); - } catch (Exception e) { - } - } - - setFile(null); - super.release(); - } - - /** - * Indicates if this file should be automatically deleted on release of the - * representation. - * - * @param autoDeleting True if this file should be automatically deleted on - * release of the representation. - */ - public void setAutoDeleting(boolean autoDeleting) { - this.autoDeleting = autoDeleting; - } - - /** - * Sets the file handle. - * - * @param file The file handle. - */ - public void setFile(File file) { - this.file = file; - } - - @Override - public void write(OutputStream outputStream) throws IOException { - IoUtils.copy(getStream(), outputStream); - } - - @Override - public void write(Writer writer) throws IOException { - IoUtils.copy(getReader(), writer); - } - + /** + * Creates a new file by detecting if the name is a URI or a simple path name. + * + * @param path The path name or file URI of the represented file (either in system format or in + * 'file:///' format). + * @return The associated File instance. + */ + private static File createFile(String path) { + if (path.startsWith("file://")) { + return new LocalReference(path).getFile(); + } + + return new File(path); + } + + /** Indicates if this file should be automatically deleted on release of the representation. */ + private volatile boolean autoDeleting; + + /** The file handle. */ + private volatile File file; + + /** + * Constructor that does not set an expiration date for {@code file} + * + * @param file The represented file. + * @param mediaType The representation's media-type. + * @see #FileRepresentation(File, MediaType, int) + */ + public FileRepresentation(File file, MediaType mediaType) { + this(file, mediaType, -1); + } + + /** + * Constructor. If a positive "timeToLive" parameter is given, then the expiration date is set + * accordingly. If "timeToLive" is equal to zero, then the expiration date is set to the current + * date, meaning that it will immediately expire on the client. If -1 is given, then no + * expiration date is set. + * + * @param file The represented file. + * @param mediaType The representation's media-type. + * @param timeToLive The time to live before it expires (in seconds). + */ + public FileRepresentation(File file, MediaType mediaType, int timeToLive) { + super(mediaType); + this.file = file; + setModificationDate(new Date(file.lastModified())); + + if (timeToLive == 0) { + setExpirationDate(null); + } else if (timeToLive > 0) { + setExpirationDate(new Date(System.currentTimeMillis() + (1000L * timeToLive))); + } + + setMediaType(mediaType); + Disposition disposition = new Disposition(); + disposition.setFilename(file.getName()); + this.setDisposition(disposition); + } + + /** + * Constructor that does not set an expiration date for {@code path} + * + * @param path The path name or file URI of the represented file (either in system format or in + * 'file:///' format). + * @param mediaType The representation's media-type. + * @see #FileRepresentation(String, MediaType, int) + */ + public FileRepresentation(String path, MediaType mediaType) { + this(path, mediaType, -1); + } + + /** + * Constructor. + * + * @param path The path name or file URI of the represented file (either in system format or in + * 'file:///' format). + * @param mediaType The representation's media-type. + * @param timeToLive The time to live before it expires (in seconds). + * @see java.io.File#File(String) + */ + public FileRepresentation(String path, MediaType mediaType, int timeToLive) { + this(createFile(path), mediaType, timeToLive); + } + + /** + * Returns the file handle. + * + * @return the file handle. + */ + public File getFile() { + return this.file; + } + + @Override + public Reader getReader() throws IOException { + return new FileReader(this.file); + } + + @Override + public long getSize() { + if (super.getSize() != UNKNOWN_SIZE) { + return super.getSize(); + } + + return this.file.length(); + } + + @Override + public FileInputStream getStream() throws IOException { + try { + return new FileInputStream(this.file); + } catch (FileNotFoundException fnfe) { + throw new IOException("Couldn't get the stream. File not found"); + } + } + + /** + * Note that this method relies on {@link #getStream()}. This stream is closed once fully read. + */ + @Override + public String getText() throws IOException { + return IoUtils.toString(getStream(), getCharacterSet()); + } + + /** + * Indicates if this file should be automatically deleted on release of the representation. + * + * @return True if this file should be automatically deleted on release of the representation. + */ + public boolean isAutoDeleting() { + return autoDeleting; + } + + /** Releases the file handle. */ + @Override + public void release() { + if (isAutoDeleting() && getFile() != null) { + try { + IoUtils.delete(getFile(), true); + } catch (Exception ignored) { + // Ignored + } + } + + setFile(null); + super.release(); + } + + /** + * Indicates if this file should be automatically deleted on release of the representation. + * + * @param autoDeleting True if this file should be automatically deleted on release of the + * representation. + */ + public void setAutoDeleting(boolean autoDeleting) { + this.autoDeleting = autoDeleting; + } + + /** + * Sets the file handle. + * + * @param file The file handle. + */ + public void setFile(File file) { + this.file = file; + } + + @Override + public void write(OutputStream outputStream) throws IOException { + IoUtils.copy(getStream(), outputStream); + } + + @Override + public void write(Writer writer) throws IOException { + IoUtils.copy(getReader(), writer); + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/InputRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/InputRepresentation.java index d23adf96a2..1b251aa3cb 100644 --- a/org.restlet/src/main/java/org/restlet/representation/InputRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/InputRepresentation.java @@ -1,22 +1,20 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; -import org.restlet.Context; -import org.restlet.data.MediaType; -import org.restlet.engine.io.IoUtils; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; /** * Transient representation based on a BIO input stream. @@ -25,89 +23,86 @@ */ public class InputRepresentation extends StreamRepresentation { - /** The representation's stream. */ - private volatile InputStream stream; - - /** - * Constructor. - * - * @param inputStream The representation's stream. - */ - public InputRepresentation(InputStream inputStream) { - this(inputStream, null); - } - - /** - * Constructor. - * - * @param inputStream The representation's stream. - * @param mediaType The representation's media type. - */ - public InputRepresentation(InputStream inputStream, MediaType mediaType) { - this(inputStream, mediaType, UNKNOWN_SIZE); - } - - /** - * Constructor. - * - * @param inputStream The representation's stream. - * @param mediaType The representation's media type. - * @param expectedSize The expected input stream size. - */ - public InputRepresentation(InputStream inputStream, MediaType mediaType, long expectedSize) { - super(mediaType); - setSize(expectedSize); - setTransient(true); - setStream(inputStream); - } - - @Override - public InputStream getStream() throws IOException { - final InputStream result = this.stream; - setStream(null); - return result; - } - - /** - * Note that this method relies on {@link #getStream()}. This stream is closed - * once fully read. - */ - @Override - public String getText() throws IOException { - return IoUtils.toString(getStream(), getCharacterSet()); - } - - /** - * Closes and releases the input stream. - */ - @Override - public void release() { - if (this.stream != null) { - try { - this.stream.close(); - } catch (IOException e) { - Context.getCurrentLogger().log(Level.WARNING, "Error while releasing the representation.", e); - } - - this.stream = null; - } - - super.release(); - } - - /** - * Sets the input stream to use. - * - * @param stream The input stream to use. - */ - public void setStream(InputStream stream) { - this.stream = stream; - setAvailable(stream != null); - } - - @Override - public void write(OutputStream outputStream) throws IOException { - IoUtils.copy(getStream(), outputStream); - } - + /** The representation's stream. */ + private volatile InputStream stream; + + /** + * Constructor. + * + * @param inputStream The representation's stream. + */ + public InputRepresentation(InputStream inputStream) { + this(inputStream, null); + } + + /** + * Constructor. + * + * @param inputStream The representation's stream. + * @param mediaType The representation's media-type. + */ + public InputRepresentation(InputStream inputStream, MediaType mediaType) { + this(inputStream, mediaType, UNKNOWN_SIZE); + } + + /** + * Constructor. + * + * @param inputStream The representation's stream. + * @param mediaType The representation's media-type. + * @param expectedSize The expected input stream size. + */ + public InputRepresentation(InputStream inputStream, MediaType mediaType, long expectedSize) { + super(mediaType); + setSize(expectedSize); + setTransient(true); + setStream(inputStream); + } + + @Override + public InputStream getStream() throws IOException { + final InputStream result = this.stream; + setStream(null); + return result; + } + + /** + * Note that this method relies on {@link #getStream()}. This stream is closed once fully read. + */ + @Override + public String getText() throws IOException { + return IoUtils.toString(getStream(), getCharacterSet()); + } + + /** Closes and releases the input stream. */ + @Override + public void release() { + if (this.stream != null) { + try { + this.stream.close(); + } catch (IOException e) { + Context.getCurrentLogger() + .log(Level.WARNING, "Error while releasing the representation.", e); + } + + this.stream = null; + } + + super.release(); + } + + /** + * Sets the input stream to use. + * + * @param stream The input stream to use. + */ + public void setStream(InputStream stream) { + this.stream = stream; + setAvailable(stream != null); + } + + @Override + public void write(OutputStream outputStream) throws IOException { + IoUtils.copy(getStream(), outputStream); + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/MultiPartRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/MultiPartRepresentation.java index 890b33b1df..782dd2462e 100644 --- a/org.restlet/src/main/java/org/restlet/representation/MultiPartRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/MultiPartRepresentation.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; import java.io.IOException; @@ -15,7 +14,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; - import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.MultiPart; import org.eclipse.jetty.http.MultiPart.Part; @@ -28,41 +26,44 @@ import org.restlet.data.MediaType; /** - * Input representation that can either parse or generate a multipart form data - * representation depending on which constructor is invoked. - * + * Input representation that can either parse or generate a multipart form data representation + * depending on which constructor is invoked. + * * @author Jerome Louvel */ public class MultiPartRepresentation extends InputRepresentation { + private static final String PARAMETER_BOUNDARY = "boundary"; + /** - * Creates a #{@link Part} object based on a {@link Representation} plus - * metadata. - * - * @param name The name of the part. - * @param fileName The client suggests file name for storing the part. + * Creates a #{@link Part} object based on a {@link Representation} plus metadata. + * + * @param name The name of the part. + * @param fileName The client's suggested file name for storing the part. * @param partContent The part content. * @return The Jetty #{@link Part} object created. * @throws IOException */ - public static Part createPart(String name, String fileName, - Representation partContent) throws IOException { - return new MultiPart.ContentSourcePart(name, fileName, HttpFields.EMPTY, + public static Part createPart(String name, String fileName, Representation partContent) + throws IOException { + return new MultiPart.ContentSourcePart( + name, + fileName, + HttpFields.EMPTY, new InputStreamContentSource(partContent.getStream())); } /** - * Returns the value of the first media-type parameter with "boundary" name. - * - * @param mediaType The media type that might contain a "boundary" - * parameter. - * @return The value of the first media-type parameter with "boundary" name. + * Returns the value of the first media-type parameter with the "boundary" name. + * + * @param mediaType The media type that might contain a "boundary" parameter. + * @return The value of the first media-type parameter with the "boundary" name. */ public static String getBoundary(MediaType mediaType) { final String result; if (mediaType != null) { - result = mediaType.getParameters().getFirstValue("boundary"); + result = mediaType.getParameters().getFirstValue(PARAMETER_BOUNDARY); } else { result = null; } @@ -71,41 +72,37 @@ public static String getBoundary(MediaType mediaType) { } /** - * Sets a boundary to an existing media type. If the original mediatype - * already has a "boundary" parameter, it will be erased. * - * + * Sets a boundary to an existing media type. If the original media-type already has a + * "boundary" parameter, it will be erased. * + * * @param mediaType The media type to update. - * @param boundary The boundary to add as a parameter. + * @param boundary The boundary to add as a parameter. * @return The updated media type. */ public static MediaType setBoundary(MediaType mediaType, String boundary) { MediaType result = null; if (mediaType != null) { - if (mediaType.getParameters().getFirst("boundary") != null) { - result = new MediaType(mediaType.getParent(), "boundary", - boundary); + if (mediaType.getParameters().getFirst(PARAMETER_BOUNDARY) != null) { + result = new MediaType(mediaType.getParent(), PARAMETER_BOUNDARY, boundary); } else { - result = new MediaType(mediaType, "boundary", boundary); + result = new MediaType(mediaType, PARAMETER_BOUNDARY, boundary); } } return result; } - /** - * The boundary used to separate each part for the parsed or generated form. - */ + /** The boundary used to separate each part for the parsed or generated form. */ private volatile String boundary; /** The wrapped multipart form data either parsed or to be generated. */ private volatile List parts; /** - * Constructor that wraps multiple parts, set a random boundary, then - * GENERATES the content via {@link #getStream()} as a - * {@link MediaType#MULTIPART_FORM_DATA}. - * + * Constructor that wraps multiple parts, set a random boundary, then GENERATES the content via + * {@link #getStream()} as a {@link MediaType#MULTIPART_FORM_DATA}. + * * @param parts The source parts to use when generating the representation. */ public MultiPartRepresentation(List parts) { @@ -113,27 +110,23 @@ public MultiPartRepresentation(List parts) { } /** - * Constructor that wraps multiple parts, set a media type with a boundary, - * then GENERATES the content via {@link #getStream()} as a - * {@link MediaType#MULTIPART_FORM_DATA}. - * + * Constructor that wraps multiple parts, set a media type with a boundary, then GENERATES the + * content via {@link #getStream()} as a {@link MediaType#MULTIPART_FORM_DATA}. + * * @param mediaType The media type to set. - * @param boundary The boundary to add as a parameter. - * @param parts The source parts to use when generating the - * representation. + * @param boundary The boundary to add as a parameter. + * @param parts The source parts to use when generating the representation. */ - public MultiPartRepresentation(MediaType mediaType, String boundary, - List parts) { + public MultiPartRepresentation(MediaType mediaType, String boundary, List parts) { super(null, setBoundary(mediaType, boundary)); this.boundary = boundary; this.parts = parts; } /** - * Constructor that wraps multiple parts, set a random boundary, then - * GENERATES the content via {@link #getStream()} as a - * {@link MediaType#MULTIPART_FORM_DATA}. - * + * Constructor that wraps multiple parts, set a random boundary, then GENERATES the content via + * {@link #getStream()} as a {@link MediaType#MULTIPART_FORM_DATA}. + * * @param parts The source parts to use when generating the representation. */ public MultiPartRepresentation(Part... parts) { @@ -141,69 +134,58 @@ public MultiPartRepresentation(Part... parts) { } /** - * Constructor that PARSES the content based on a given configuration into - * {@link #getParts()}. - * - * @param multiPartEntity The multipart entity to parse which should have a - * media type based on - * {@link MediaType#MULTIPART_FORM_DATA}, with a - * "boundary" parameter. - * @param config The multipart configuration. + * Constructor that PARSES the content based on a given configuration into {@link #getParts()}. + * + * @param multiPartEntity The multipart entity to parse which should have a media type based on + * {@link MediaType#MULTIPART_FORM_DATA}, with a "boundary" parameter. + * @param config The multipart configuration. * @throws IOException */ - public MultiPartRepresentation(Representation multiPartEntity, - MultiPartConfig config) throws IOException { - this(multiPartEntity.getMediaType(), multiPartEntity.getStream(), - config); + public MultiPartRepresentation(Representation multiPartEntity, MultiPartConfig config) + throws IOException { + this(multiPartEntity.getMediaType(), multiPartEntity.getStream(), config); } /** - * Constructor that PARSES the content based on a given configuration into - * {@link #getParts()}. Uses a default {@link MultiPartConfig}. - * - * @param multiPartEntity The multipart entity to parse which should have a - * media type based on - * {@link MediaType#MULTIPART_FORM_DATA}, with a - * "boundary" parameter. - * @param storageLocation The location where parsed files are stored for - * easier access. + * Constructor that PARSES the content based on a given configuration into {@link #getParts()}. + * Uses a default {@link MultiPartConfig}. + * + * @param multiPartEntity The multipart entity to parse which should have a media type based on + * {@link MediaType#MULTIPART_FORM_DATA}, with a "boundary" parameter. + * @param storageLocation The location where parsed files are stored for easier access. * @throws IOException */ - public MultiPartRepresentation(Representation multiPartEntity, - Path storageLocation) throws IOException { - this(multiPartEntity, new MultiPartConfig.Builder() - .location(storageLocation).build()); + public MultiPartRepresentation(Representation multiPartEntity, Path storageLocation) + throws IOException { + this(multiPartEntity, new MultiPartConfig.Builder().location(storageLocation).build()); } /** - * Constructor that PARSES the content based on a given configuration into - * {@link #getParts()}. - * - * @param mediaType The media type that should be based on - * {@link MediaType#MULTIPART_FORM_DATA}, with a - * "boundary" parameter. + * Constructor that PARSES the content based on a given configuration into {@link #getParts()}. + * + * @param mediaType The media type that should be based on {@link + * MediaType#MULTIPART_FORM_DATA}, with a "boundary" parameter. * @param multiPartEntity The multipart entity to parse. - * @param config The multipart configuration. - * @throws IOException + * @param config The multipart configuration. */ - public MultiPartRepresentation(MediaType mediaType, - InputStream multiPartEntity, MultiPartConfig config) - throws IOException { + public MultiPartRepresentation( + MediaType mediaType, InputStream multiPartEntity, MultiPartConfig config) { super(null, mediaType); if (MediaType.MULTIPART_FORM_DATA.equals(getMediaType(), true)) { - this.boundary = getMediaType().getParameters() - .getFirstValue("boundary"); + this.boundary = getMediaType().getParameters().getFirstValue(PARAMETER_BOUNDARY); if (this.boundary != null) { if (multiPartEntity != null) { - Content.Source contentSource = Content.Source - .from(multiPartEntity); + Content.Source contentSource = Content.Source.from(multiPartEntity); Attributes.Mapped attributes = new Attributes.Mapped(); // Convert the request content into parts. - MultiPartFormData.onParts(contentSource, attributes, - mediaType.toString(), config, + MultiPartFormData.onParts( + contentSource, + attributes, + mediaType.toString(), + config, new Promise.Invocable<>() { @Override public void failed(Throwable failure) { @@ -218,18 +200,18 @@ public InvocationType getInvocationType() { } @Override - public void succeeded( - MultiPartFormData.Parts parts) { + public void succeeded(MultiPartFormData.Parts parts) { // Store the resulting parts MultiPartRepresentation.this.parts = new ArrayList<>(); - parts.iterator().forEachRemaining( - part -> MultiPartRepresentation.this.parts - .add(part)); + parts.iterator() + .forEachRemaining( + part -> + MultiPartRepresentation.this.parts.add( + part)); } }); } else { - throw new IllegalArgumentException( - "The multipart entity can't be null"); + throw new IllegalArgumentException("The multipart entity can't be null"); } } else { throw new IllegalArgumentException( @@ -242,23 +224,20 @@ public void succeeded( } /** - * Constructor that wraps multiple parts, set a boundary, then GENERATES the - * content via {@link #getStream()} as a - * {@link MediaType#MULTIPART_FORM_DATA}. - * + * Constructor that wraps multiple parts, set a boundary, then GENERATES the content via {@link + * #getStream()} as a {@link MediaType#MULTIPART_FORM_DATA}. + * * @param boundary The boundary to add as a parameter. - * @param parts The source parts to use when generating the - * representation. + * @param parts The source parts to use when generating the representation. */ public MultiPartRepresentation(String boundary, List parts) { this(MediaType.MULTIPART_FORM_DATA, boundary, parts); } /** - * Constructor that wraps multiple parts, set a boundary, then GENERATES the - * content via {@link #getStream()} as a - * {@link MediaType#MULTIPART_FORM_DATA}. - * + * Constructor that wraps multiple parts, set a boundary, then GENERATES the content via {@link + * #getStream()} as a {@link MediaType#MULTIPART_FORM_DATA}. + * * @param parts The source parts to use when generating the representation. */ public MultiPartRepresentation(String boundary, Part... parts) { @@ -266,11 +245,9 @@ public MultiPartRepresentation(String boundary, Part... parts) { } /** - * Returns the boundary used to separate each part for the parsed or - * generated form. - * - * @return The boundary used to separate each part for the parsed or - * generated form. + * Returns the boundary used to separate each part for the parsed or generated form. + * + * @return The boundary used to separate each part for the parsed or generated form. */ public String getBoundary() { return boundary; @@ -286,10 +263,9 @@ public List getParts() { } /** - * Returns an input stream that generates the multipart form data - * serialization for the wrapped {@link #getParts()} object. The "boundary" - * must be non-null when invoking this method. - * + * Returns an input stream that generates the multipart form data serialization for the wrapped + * {@link #getParts()} object. The "boundary" must be non-null when invoking this method. + * * @return An input stream that generates the multipart form data. */ @Override @@ -298,8 +274,8 @@ public InputStream getStream() throws IOException { throw new IllegalArgumentException("The boundary can't be null"); } - MultiPartFormData.ContentSource content = new MultiPartFormData.ContentSource( - getBoundary()); + MultiPartFormData.ContentSource content = + new MultiPartFormData.ContentSource(getBoundary()); for (Part part : this.parts) { content.addPart(part); @@ -311,21 +287,19 @@ public InputStream getStream() throws IOException { } /** - * Sets the boundary used to separate each part for the parsed or generated - * form. It will also update the {@link MediaType}'s "boundary" attribute. - * - * @param boundary The boundary used to separate each part for the parsed or - * generated form. + * Sets the boundary used to separate each part for the parsed or generated form. It will also + * update the {@link MediaType}'s "boundary" attribute. + * + * @param boundary The boundary used to separate each part for the parsed or generated form. */ public void setBoundary(String boundary) { this.boundary = boundary; if (getMediaType() == null) { - setMediaType(new MediaType(MediaType.MULTIPART_FORM_DATA, - "boundary", boundary)); + setMediaType( + new MediaType(MediaType.MULTIPART_FORM_DATA, PARAMETER_BOUNDARY, boundary)); } else { setMediaType(setBoundary(getMediaType(), boundary)); } } - } diff --git a/org.restlet/src/main/java/org/restlet/representation/ObjectRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/ObjectRepresentation.java index fa55b389d9..79e7d50512 100644 --- a/org.restlet/src/main/java/org/restlet/representation/ObjectRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/ObjectRepresentation.java @@ -1,249 +1,254 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; import org.restlet.data.MediaType; -import java.io.*; - /** * Representation based on a serializable Java object.
- * It supports binary representations of JavaBeans using the - * {@link ObjectInputStream} and {@link ObjectOutputStream} classes. In this - * case, it handles representations having the following media type: - * {@link MediaType#APPLICATION_JAVA_OBJECT} - * ("application/x-java-serialized-object"). It also supports textual - * representations of JavaBeans using the {@link java.beans.XMLEncoder} and - * {@link java.beans.XMLDecoder} classes. In this case, it handles - * representations having the following media type: - * {@link MediaType#APPLICATION_JAVA_OBJECT_XML} + * It supports binary representations of JavaBeans using the {@link ObjectInputStream} and {@link + * ObjectOutputStream} classes. In this case, it handles representations having the following media + * type: {@link MediaType#APPLICATION_JAVA_OBJECT} ("application/x-java-serialized-object"). It also + * supports textual representations of JavaBeans using the {@link java.beans.XMLEncoder} and {@link + * java.beans.XMLDecoder} classes. In this case, it handles representations having the following + * media type: {@link MediaType#APPLICATION_JAVA_OBJECT_XML} * ("application/x-java-serialized-object+xml").
*
- * SECURITY WARNING: The usage of {@link java.beans.XMLDecoder} when - * deserializing XML presentations from untrusted sources can lead to malicious - * attacks. As pointed here, the {@link java.beans.XMLDecoder} is able to force the JVM to - * execute unwanted Java code described inside the XML file. Thus, the support - * of such format has been disabled by default. You can activate this support by - * turning on the following system property: + * SECURITY WARNING: The usage of {@link java.beans.XMLDecoder} when deserializing XML presentations + * from untrusted sources can lead to malicious attacks. As pointed here, the + * {@link java.beans.XMLDecoder} is able to force the JVM to execute unwanted Java code described + * inside the XML file. Thus, the support of such a format has been disabled by default. You can + * activate this support by turning on the following system property: * org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED.
*
- * SECURITY WARNING: The usage of {@link ObjectInputStream} when deserializing - * binary presentations from untrusted sources can lead to malicious attacks. As - * pointed - * here, the {@link ObjectInputStream} is able to force the JVM to execute - * unwanted Java code. Thus, the support of such format has been disabled by - * default. You can activate this support by turning on the following system - * property: "org.restlet.representation.ObjectRepresentation + * SECURITY WARNING: The usage of {@link ObjectInputStream} when deserializing binary presentations + * from untrusted sources can lead to malicious attacks. As pointed here, the {@link + * ObjectInputStream} is able to force the JVM to execute unwanted Java code. Thus, the support of + * such format has been disabled by default. You can activate this support by turning on the + * following system property: "org.restlet.representation.ObjectRepresentation * .VARIANT_OBJECT_BINARY_SUPPORTED". - * + * * @author Jerome Louvel * @param The class to serialize, see {@link Serializable} */ public class ObjectRepresentation extends OutputRepresentation { - /** Indicates whether the JavaBeans XML deserialization is supported or not. */ - public static boolean VARIANT_OBJECT_XML_SUPPORTED = Boolean - .getBoolean("org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED"); - - /** - * Indicates whether the JavaBeans binary deserialization is supported or not. - */ - public static boolean VARIANT_OBJECT_BINARY_SUPPORTED = Boolean - .getBoolean("org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED"); - - /** The serializable object. */ - private volatile T object; - - /** - * Constructor reading the object from a serialized representation. This - * representation must have the proper media type: - * "application/x-java-serialized-object". - * - * @param serializedRepresentation The serialized representation. - * @throws IOException - * @throws ClassNotFoundException - * @throws IllegalArgumentException - */ - public ObjectRepresentation(Representation serializedRepresentation) - throws IOException, ClassNotFoundException, IllegalArgumentException { - this(serializedRepresentation, null); - } - - /** - * Constructor reading the object from a serialized representation. This - * representation must have the proper media type: - * "application/x-java-serialized-object". - * - * @param serializedRepresentation The serialized representation. - * @param classLoader The class loader used to read the object. - * @throws IOException - * @throws ClassNotFoundException - * @throws IllegalArgumentException - */ - public ObjectRepresentation(Representation serializedRepresentation, final ClassLoader classLoader) - throws IOException, ClassNotFoundException, IllegalArgumentException { - this(serializedRepresentation, classLoader, VARIANT_OBJECT_BINARY_SUPPORTED, VARIANT_OBJECT_XML_SUPPORTED); - } - - /** - * Constructor reading the object from a serialized representation. This - * representation must have the proper media type: - * "application/x-java-serialized-object". - * - * @param serializedRepresentation The serialized representation. - * @param classLoader The class loader used to read the object. - * @param variantObjectBinarySupported Indicates whether the JavaBeans binary - * deserialization is supported or not. - * @param variantObjectXmlSupported Indicates whether the JavaBeans XML - * deserialization is supported or not. - * @throws IOException - * @throws ClassNotFoundException - * @throws IllegalArgumentException - */ - @SuppressWarnings("unchecked") - public ObjectRepresentation(Representation serializedRepresentation, final ClassLoader classLoader, - boolean variantObjectBinarySupported, boolean variantObjectXmlSupported) - throws IOException, ClassNotFoundException, IllegalArgumentException { - super(MediaType.APPLICATION_JAVA_OBJECT); - - if (MediaType.APPLICATION_JAVA_OBJECT.equals(serializedRepresentation.getMediaType())) { - if (!variantObjectBinarySupported) { - throw new IllegalArgumentException("SECURITY WARNING: The usage of ObjectInputStream when " - + "deserializing binary representations from unstrusted " - + "sources can lead to malicious attacks. As pointed " - + "here (https://github.com/restlet/restlet-framework-java/issues/778), " - + "the ObjectInputStream class is able to force the JVM to execute unwanted " - + "Java code. Thus, the support of such format has been disactivated " - + "by default. You can activate this support by turning on the following system property: " - + "org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED."); - } - setMediaType(MediaType.APPLICATION_JAVA_OBJECT); - InputStream is = serializedRepresentation.getStream(); - ObjectInputStream ois = null; - if (classLoader != null) { - ois = new ObjectInputStream(is) { - @Override - protected Class resolveClass(java.io.ObjectStreamClass desc) - throws java.io.IOException, java.lang.ClassNotFoundException { - return Class.forName(desc.getName(), false, classLoader); - } - }; - } else { - ois = new ObjectInputStream(is); - } - - this.object = (T) ois.readObject(); - - if (is.read() != -1) { - throw new IOException("The input stream has not been fully read."); - } - - ois.close(); - } else if (MediaType.APPLICATION_JAVA_OBJECT_XML.equals(serializedRepresentation.getMediaType())) { - if (!variantObjectXmlSupported) { - throw new IllegalArgumentException("SECURITY WARNING: The usage of XMLDecoder when " - + "deserializing XML representations from unstrusted " - + "sources can lead to malicious attacks. As pointed " - + "here (http://blog.diniscruz.com/2013/08/using-xmldecoder-to-execute-server-side.html), " - + "the XMLDecoder class is able to force the JVM to " - + "execute unwanted Java code described inside the XML " - + "file. Thus, the support of such format has been " - + "disabled by default. You can activate this " - + "support by turning on the following system property: " - + "org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED."); - } - setMediaType(MediaType.APPLICATION_JAVA_OBJECT_XML); - InputStream is = serializedRepresentation.getStream(); - java.beans.XMLDecoder decoder = new java.beans.XMLDecoder(is); - this.object = (T) decoder.readObject(); - - if (is.read() != -1) { - decoder.close(); - throw new IOException("The input stream has not been fully read."); - } - - decoder.close(); - } else { - throw new IllegalArgumentException("The serialized representation must have this media type: " - + MediaType.APPLICATION_JAVA_OBJECT.toString() + " or this one: " - + MediaType.APPLICATION_JAVA_OBJECT_XML.toString()); - } - } - - /** - * Constructor for the {@link MediaType#APPLICATION_JAVA_OBJECT} type. - * - * @param object The serializable object. - */ - public ObjectRepresentation(T object) { - super(MediaType.APPLICATION_JAVA_OBJECT); - this.object = object; - } - - /** - * Constructor for either the {@link MediaType#APPLICATION_JAVA_OBJECT} type or - * the {@link MediaType#APPLICATION_XML} type. In the first case, the Java - * Object Serialization mechanism is used, based on {@link ObjectOutputStream}. - * In the latter case, the JavaBeans XML serialization is used, based on - * {@link java.beans.XMLEncoder}. - * - * @param object The serializable object. - * @param mediaType The media type. - */ - public ObjectRepresentation(T object, MediaType mediaType) { - super((mediaType == null) ? MediaType.APPLICATION_JAVA_OBJECT : mediaType); - this.object = object; - } - - /** - * Returns the represented object. - * - * @return The represented object. - * @throws IOException - */ - public T getObject() throws IOException { - return this.object; - } - - /** - * Releases the represented object. - */ - @Override - public void release() { - setObject(null); - super.release(); - } - - /** - * Sets the represented object. - * - * @param object The represented object. - */ - public void setObject(T object) { - this.object = object; - } - - @Override - public void write(OutputStream outputStream) throws IOException { - if (MediaType.APPLICATION_JAVA_OBJECT.isCompatible(getMediaType())) { - ObjectOutputStream oos = new ObjectOutputStream(outputStream); - oos.writeObject(getObject()); - oos.flush(); - } else if (MediaType.APPLICATION_JAVA_OBJECT_XML.isCompatible(getMediaType())) { - java.beans.XMLEncoder encoder = new java.beans.XMLEncoder(outputStream); - encoder.writeObject(getObject()); - encoder.close(); - } - } - + /** Indicates whether the JavaBeans XML deserialization is supported or not. */ + public static boolean VARIANT_OBJECT_XML_SUPPORTED = + Boolean.getBoolean( + "org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED"); + + /** Indicates whether the JavaBeans binary deserialization is supported or not. */ + public static boolean VARIANT_OBJECT_BINARY_SUPPORTED = + Boolean.getBoolean( + "org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED"); + + /** The serializable object. */ + private volatile T object; + + /** + * Constructor reading the object from a serialized representation. This representation must + * have the proper media type: "application/x-java-serialized-object". + * + * @param serializedRepresentation The serialized representation. + * @throws IOException + * @throws ClassNotFoundException + * @throws IllegalArgumentException + */ + public ObjectRepresentation(Representation serializedRepresentation) + throws IOException, ClassNotFoundException, IllegalArgumentException { + this(serializedRepresentation, null); + } + + /** + * Constructor reading the object from a serialized representation. This representation must + * have the proper media type: "application/x-java-serialized-object". + * + * @param serializedRepresentation The serialized representation. + * @param classLoader The class loader used to read the object. + * @throws IOException + * @throws ClassNotFoundException + * @throws IllegalArgumentException + */ + public ObjectRepresentation( + Representation serializedRepresentation, final ClassLoader classLoader) + throws IOException, ClassNotFoundException, IllegalArgumentException { + this( + serializedRepresentation, + classLoader, + VARIANT_OBJECT_BINARY_SUPPORTED, + VARIANT_OBJECT_XML_SUPPORTED); + } + + /** + * Constructor reading the object from a serialized representation. This representation must + * have the proper media type: "application/x-java-serialized-object". + * + * @param serializedRepresentation The serialized representation. + * @param classLoader The class loader used to read the object. + * @param variantObjectBinarySupported Indicates whether the JavaBeans binary deserialization is + * supported or not. + * @param variantObjectXmlSupported Indicates whether the JavaBeans XML deserialization is + * supported or not. + * @throws IOException + * @throws ClassNotFoundException + * @throws IllegalArgumentException + */ + @SuppressWarnings("unchecked") + public ObjectRepresentation( + Representation serializedRepresentation, + final ClassLoader classLoader, + boolean variantObjectBinarySupported, + boolean variantObjectXmlSupported) + throws IOException, ClassNotFoundException, IllegalArgumentException { + super(MediaType.APPLICATION_JAVA_OBJECT); + + if (MediaType.APPLICATION_JAVA_OBJECT.equals(serializedRepresentation.getMediaType())) { + if (!variantObjectBinarySupported) { + throw new IllegalArgumentException( + "SECURITY WARNING: The usage of ObjectInputStream when " + + "deserializing binary representations from unstrusted " + + "sources can lead to malicious attacks. As pointed " + + "here (https://github.com/restlet/restlet-framework-java/issues/778), " + + "the ObjectInputStream class is able to force the JVM to execute unwanted " + + "Java code. Thus, the support of such format has been disactivated " + + "by default. You can activate this support by turning on the following system property: " + + "org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED."); + } + setMediaType(MediaType.APPLICATION_JAVA_OBJECT); + + final InputStream is = serializedRepresentation.getStream(); + + final ObjectInputStream ois; + if (classLoader != null) { + ois = + new ObjectInputStream(is) { + @Override + protected Class resolveClass(java.io.ObjectStreamClass desc) + throws java.io.IOException, java.lang.ClassNotFoundException { + return Class.forName(desc.getName(), false, classLoader); + } + }; + } else { + ois = new ObjectInputStream(is); + } + + this.object = (T) ois.readObject(); + + if (is.read() != -1) { + throw new IOException("The input stream has not been fully read."); + } + + ois.close(); + } else if (MediaType.APPLICATION_JAVA_OBJECT_XML.equals( + serializedRepresentation.getMediaType())) { + if (!variantObjectXmlSupported) { + throw new IllegalArgumentException( + "SECURITY WARNING: The usage of XMLDecoder when " + + "deserializing XML representations from unstrusted " + + "sources can lead to malicious attacks. As pointed " + + "here (http://blog.diniscruz.com/2013/08/using-xmldecoder-to-execute-server-side.html), " + + "the XMLDecoder class is able to force the JVM to " + + "execute unwanted Java code described inside the XML " + + "file. Thus, the support of such format has been " + + "disabled by default. You can activate this " + + "support by turning on the following system property: " + + "org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED."); + } + setMediaType(MediaType.APPLICATION_JAVA_OBJECT_XML); + InputStream is = serializedRepresentation.getStream(); + java.beans.XMLDecoder decoder = new java.beans.XMLDecoder(is); + this.object = (T) decoder.readObject(); + + if (is.read() != -1) { + decoder.close(); + throw new IOException("The input stream has not been fully read."); + } + + decoder.close(); + } else { + throw new IllegalArgumentException( + "The serialized representation must have this media type: " + + MediaType.APPLICATION_JAVA_OBJECT.toString() + + " or this one: " + + MediaType.APPLICATION_JAVA_OBJECT_XML.toString()); + } + } + + /** + * Constructor for the {@link MediaType#APPLICATION_JAVA_OBJECT} type. + * + * @param object The serializable object. + */ + public ObjectRepresentation(T object) { + super(MediaType.APPLICATION_JAVA_OBJECT); + this.object = object; + } + + /** + * Constructor for either the {@link MediaType#APPLICATION_JAVA_OBJECT} type or the {@link + * MediaType#APPLICATION_XML} type. In the first case, the Java Object Serialization mechanism + * is used, based on {@link ObjectOutputStream}. In the latter case, the JavaBeans XML + * serialization is used, based on {@link java.beans.XMLEncoder}. + * + * @param object The serializable object. + * @param mediaType The media type. + */ + public ObjectRepresentation(T object, MediaType mediaType) { + super((mediaType == null) ? MediaType.APPLICATION_JAVA_OBJECT : mediaType); + this.object = object; + } + + /** + * Returns the represented object. + * + * @return The represented object. + * @throws IOException + */ + public T getObject() throws IOException { + return this.object; + } + + /** Releases the represented object. */ + @Override + public void release() { + setObject(null); + super.release(); + } + + /** + * Sets the represented object. + * + * @param object The represented object. + */ + public void setObject(T object) { + this.object = object; + } + + @Override + public void write(OutputStream outputStream) throws IOException { + if (MediaType.APPLICATION_JAVA_OBJECT.isCompatible(getMediaType())) { + ObjectOutputStream oos = new ObjectOutputStream(outputStream); + oos.writeObject(getObject()); + oos.flush(); + } else if (MediaType.APPLICATION_JAVA_OBJECT_XML.isCompatible(getMediaType())) { + java.beans.XMLEncoder encoder = new java.beans.XMLEncoder(outputStream); + encoder.writeObject(getObject()); + encoder.close(); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/OutputRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/OutputRepresentation.java index 3e2f18e8b1..ec3d5b82c3 100644 --- a/org.restlet/src/main/java/org/restlet/representation/OutputRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/OutputRepresentation.java @@ -1,61 +1,57 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; -import org.restlet.data.MediaType; -import org.restlet.engine.io.IoUtils; - import java.io.IOException; import java.io.InputStream; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; /** - * Representation based on a BIO output stream. This class is a good basis to - * write your own representations, especially for the dynamic and large - * ones.
+ * Representation based on a BIO output stream. This class is a good basis to write your own + * representations, especially for the dynamic and large ones.
*
* For this you just need to create a subclass and override the abstract - * Representation.write(OutputStream) method. This method will later be called - * back by the connectors when the actual representation's content is needed. - * + * Representation.write(OutputStream) method. This method will later be called back by the + * connectors when the actual representation's content is needed. + * * @author Jerome Louvel */ public abstract class OutputRepresentation extends StreamRepresentation { - /** - * Constructor. - * - * @param mediaType The representation's mediaType. - */ - public OutputRepresentation(MediaType mediaType) { - super(mediaType); - } - - /** - * Constructor. - * - * @param mediaType The representation's mediaType. - * @param expectedSize The expected input stream size. - */ - public OutputRepresentation(MediaType mediaType, long expectedSize) { - super(mediaType); - setSize(expectedSize); - } + /** + * Constructor. + * + * @param mediaType The representation's mediaType. + */ + protected OutputRepresentation(MediaType mediaType) { + super(mediaType); + } - /** - * Returns a stream with the representation's content. Internally, it uses a - * writer thread and a pipe stream. - * - * @return A stream with the representation's content. - */ - @Override - public InputStream getStream() throws IOException { - return IoUtils.getStream(this); - } + /** + * Constructor. + * + * @param mediaType The representation's mediaType. + * @param expectedSize The expected input stream size. + */ + protected OutputRepresentation(MediaType mediaType, long expectedSize) { + super(mediaType); + setSize(expectedSize); + } + /** + * Returns a stream with the representation's content. Internally, it uses a writer thread and a + * pipe stream. + * + * @return A stream with the representation's content. + */ + @Override + public InputStream getStream() throws IOException { + return IoUtils.getStream(this); + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/ReaderRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/ReaderRepresentation.java index 89029f5a4d..ce55314d3f 100644 --- a/org.restlet/src/main/java/org/restlet/representation/ReaderRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/ReaderRepresentation.java @@ -1,112 +1,108 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; -import org.restlet.Context; -import org.restlet.data.MediaType; -import org.restlet.engine.io.IoUtils; - import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; /** * Transient representation based on a BIO characters reader. - * + * * @author Jerome Louvel */ public class ReaderRepresentation extends CharacterRepresentation { - /** The representation's reader. */ - private volatile Reader reader; + /** The representation's reader. */ + private volatile Reader reader; - /** - * Constructor. - * - * @param reader The representation's stream. - */ - public ReaderRepresentation(Reader reader) { - this(reader, null); - } + /** + * Constructor. + * + * @param reader The representation's stream. + */ + public ReaderRepresentation(Reader reader) { + this(reader, null); + } - /** - * Constructor. - * - * @param reader The representation's stream. - * @param mediaType The representation's media type. - */ - public ReaderRepresentation(Reader reader, MediaType mediaType) { - this(reader, mediaType, UNKNOWN_SIZE); - } + /** + * Constructor. + * + * @param reader The representation's stream. + * @param mediaType The representation's media-type. + */ + public ReaderRepresentation(Reader reader, MediaType mediaType) { + this(reader, mediaType, UNKNOWN_SIZE); + } - /** - * Constructor. - * - * @param reader The representation's stream. - * @param mediaType The representation's media type. - * @param expectedSize The expected reader size in bytes. - */ - public ReaderRepresentation(Reader reader, MediaType mediaType, long expectedSize) { - super(mediaType); - setSize(expectedSize); - setTransient(true); - setReader(reader); - } + /** + * Constructor. + * + * @param reader The representation's stream. + * @param mediaType The representation's media-type. + * @param expectedSize The expected reader size in bytes. + */ + public ReaderRepresentation(Reader reader, MediaType mediaType, long expectedSize) { + super(mediaType); + setSize(expectedSize); + setTransient(true); + setReader(reader); + } - @Override - public Reader getReader() throws IOException { - final Reader result = this.reader; - setReader(null); - return result; - } + @Override + public Reader getReader() throws IOException { + final Reader result = this.reader; + setReader(null); + return result; + } - /** - * Note that this method relies on {@link #getStream()}. This stream is closed - * once fully read. - */ - @Override - public String getText() throws IOException { - return IoUtils.toString(getStream(), getCharacterSet()); - } + /** + * Note that this method relies on {@link #getStream()}. This stream is closed once fully read. + */ + @Override + public String getText() throws IOException { + return IoUtils.toString(getStream(), getCharacterSet()); + } - /** - * Closes and releases the input stream. - */ - @Override - public void release() { - if (this.reader != null) { - try { - this.reader.close(); - } catch (IOException e) { - Context.getCurrentLogger().log(Level.WARNING, "Error while releasing the representation.", e); - } + /** Closes and releases the input stream. */ + @Override + public void release() { + if (this.reader != null) { + try { + this.reader.close(); + } catch (IOException e) { + Context.getCurrentLogger() + .log(Level.WARNING, "Error while releasing the representation.", e); + } - this.reader = null; - } + this.reader = null; + } - super.release(); - } + super.release(); + } - /** - * Sets the reader to use. - * - * @param reader The reader to use. - */ - public void setReader(Reader reader) { - this.reader = reader; - setAvailable(reader != null); - } + /** + * Sets the reader to use. + * + * @param reader The reader to use. + */ + public void setReader(Reader reader) { + this.reader = reader; + setAvailable(reader != null); + } - @Override - public void write(Writer writer) throws IOException { - IoUtils.copy(getReader(), writer); - } + @Override + public void write(Writer writer) throws IOException { + IoUtils.copy(getReader(), writer); + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/Representation.java b/org.restlet/src/main/java/org/restlet/representation/Representation.java index 73fbfc818e..cd579c34ff 100644 --- a/org.restlet/src/main/java/org/restlet/representation/Representation.java +++ b/org.restlet/src/main/java/org/restlet/representation/Representation.java @@ -1,14 +1,18 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.util.Date; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.Disposition; @@ -18,496 +22,461 @@ import org.restlet.engine.io.IoUtils; import org.restlet.engine.util.DateUtils; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.util.Date; - /** - * Current or intended state of a resource. The content of a representation can - * be retrieved several times if there is a stable and accessible source, like a - * local file or a string. When the representation is obtained via a temporary - * source like a network socket, its content can only be retrieved once. The - * "transient" and "available" properties are available to help you figure out + * Current or intended state of a resource. The content of a representation can be retrieved several + * times if there is a stable and accessible source, like a local file or a string. When the + * representation is obtained via a temporary source like a network socket, its content can only be + * retrieved once. The "transient" and "available" properties are available to help you figure out * those aspects at runtime.
*
- * For performance purpose, it is essential that a minimal overhead occurs upon - * initialization. The main overhead must only occur during invocation of - * content processing methods (write, getStream, getChannel and toString).
+ * For performance purpose, it is essential that a minimal overhead occurs upon initialization. The + * main overhead must only occur during invocation of content processing methods (write, getStream, + * getChannel, and toString).
*
- * "REST components perform actions on a resource by using a representation to - * capture the current or intended state of that resource and transferring that - * representation between components. A representation is a sequence of bytes, - * plus representation metadata to describe those bytes. Other commonly used but - * less precise names for a representation include: document, file, and HTTP + * "REST components perform actions on a resource by using a representation to capture the current + * or intended state of that resource and transferring that representation between components. A + * representation is a sequence of bytes, plus representation metadata to describe those bytes. + * Other commonly used but less precise names for a representation include: document, file, and HTTP * message entity, instance, or variant." Roy T. Fielding - * - * @see Source dissertation + * + * @see Source + * dissertation * @author Jerome Louvel */ public abstract class Representation extends RepresentationInfo { - /** - * Indicates that the size of the representation can't be known in advance. - */ - public static final long UNKNOWN_SIZE = -1L; - - /** Indicates if the representation's content is potentially available. */ - private volatile boolean available; - - /** - * The representation's digest, if any. - */ - private volatile org.restlet.data.Digest digest; - - /** The disposition characteristics of the representation. */ - private volatile Disposition disposition; - - /** The expiration date. */ - private volatile Date expirationDate; - - /** Indicates if the representation's content is transient. */ - private volatile boolean isTransient; - - /** - * Indicates where in the full content the partial content available should be - * applied. - */ - private volatile Range range; - - /** - * The expected size. Dynamic representations can have any size, but sometimes - * we can know in advance the expected size. If this expected size is specified - * by the user, it has a higher priority than any size that can be guessed by - * the representation (like a file size). - */ - private volatile long size; - - /** - * Default constructor. - */ - public Representation() { - this(null); - } - - /** - * Constructor. - * - * @param mediaType The media type. - */ - public Representation(MediaType mediaType) { - super(mediaType); - this.available = true; - this.disposition = null; - this.isTransient = false; - this.size = UNKNOWN_SIZE; - this.expirationDate = null; - this.digest = null; - this.range = null; - } - - /** - * Constructor. - * - * @param mediaType The media type. - * @param modificationDate The modification date. - */ - public Representation(MediaType mediaType, Date modificationDate) { - this(mediaType, modificationDate, null); - } - - /** - * Constructor. - * - * @param mediaType The media type. - * @param modificationDate The modification date. - * @param tag The tag. - */ - public Representation(MediaType mediaType, Date modificationDate, Tag tag) { - super(mediaType, modificationDate, tag); - } - - /** - * Constructor. - * - * @param mediaType The media type. - * @param tag The tag. - */ - public Representation(MediaType mediaType, Tag tag) { - this(mediaType, null, tag); - } - - /** - * Constructor from a variant. - * - * @param variant The variant to copy. - * @param modificationDate The modification date. - */ - public Representation(Variant variant, Date modificationDate) { - this(variant, modificationDate, null); - } - - /** - * Constructor from a variant. - * - * @param variant The variant to copy. - * @param modificationDate The modification date. - * @param tag The tag. - */ - public Representation(Variant variant, Date modificationDate, Tag tag) { - setCharacterSet(variant.getCharacterSet()); - setEncodings(variant.getEncodings()); - setLocationRef(variant.getLocationRef()); - setLanguages(variant.getLanguages()); - setMediaType(variant.getMediaType()); - setModificationDate(modificationDate); - setTag(tag); - } - - /** - * Constructor from a variant. - * - * @param variant The variant to copy. - * @param tag The tag. - */ - public Representation(Variant variant, Tag tag) { - this(variant, null, tag); - } - - /** - * Appends the representation to an appendable sequence of characters. This - * method is ensured to write the full content for each invocation unless it is - * a transient representation, in which case an exception is thrown.
- *
- * Note that {@link #getText()} is used by the default implementation. - * - * @param appendable The appendable sequence of characters. - * @throws IOException - */ - public void append(Appendable appendable) throws IOException { - appendable.append(getText()); - } - - /** - * Exhaust the content of the representation by reading it and silently - * discarding anything read. By default, it relies on {@link #getStream()} and - * closes the retrieved stream in the end. - * - * @return The number of bytes consumed or -1 if unknown. - * @throws IOException - */ - public long exhaust() throws IOException { - long result = -1L; - - if (isAvailable()) { - InputStream is = getStream(); - result = IoUtils.exhaust(is); - is.close(); - } - - return result; - } - - /** - * Returns the size effectively available. This returns the same value as - * {@link #getSize()} if no range is defined, otherwise it returns the size of - * the range using {@link Range#getSize()}. - * - * @return The available size. - */ - public long getAvailableSize() { - return IoUtils.getAvailableSize(this); - } - - /** - * Returns the representation digest if any.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-MD5" header. - * - * @return The representation digest or null. - */ - public org.restlet.data.Digest getDigest() { - return this.digest; - } - - /** - * Returns the disposition characteristics of the representation. - * - * @return The disposition characteristics of the representation. - */ - public Disposition getDisposition() { - return disposition; - } - - /** - * Returns the future date when this representation expire. If this information - * is not known, returns null.
- *
- * Note that when used with HTTP connectors, this property maps to the "Expires" - * header. - * - * @return The expiration date. - */ - public Date getExpirationDate() { - return this.expirationDate; - } - - /** - * Returns the range where in the full content the partial content available - * should be applied.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Range" header. - * - * @return The content range or null if the full content is available. - */ - public Range getRange() { - return this.range; - } - - /** - * Returns a character reader with the representation's content. This method is - * ensured to return a fresh reader for each invocation unless it is a transient - * representation, in which case null is returned. If the representation has no - * character set defined, the system's default one will be used. - * - * @return A reader with the representation's content. - * @throws IOException - */ - public abstract Reader getReader() throws IOException; - - /** - * Returns the total size in bytes if known, UNKNOWN_SIZE (-1) otherwise. When - * ranges are used, this might not be the actual size available. For this - * purpose, you can use the {@link #getAvailableSize()} method.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Length" header. - * - * @return The size in bytes if known, UNKNOWN_SIZE (-1) otherwise. - * @see #isEmpty() - */ - public long getSize() { - return this.size; - } - - /** - * Returns a stream with the representation's content. This method is ensured to - * return a fresh stream for each invocation unless it is a transient - * representation, in which case null is returned. - * - * @return A stream with the representation's content. - * @throws IOException - */ - public abstract InputStream getStream() throws IOException; - - /** - * Converts the representation to a string value. Be careful when using this - * method as the conversion of large content to a string fully stored in memory - * can result in OutOfMemoryErrors being thrown. - * - * @return The representation as a string value. - * @throws IOException - */ - public String getText() throws IOException { - String result = null; - - if (isEmpty()) { - result = ""; - } else if (isAvailable()) { - java.io.StringWriter sw = new java.io.StringWriter(); - write(sw); - sw.flush(); - result = sw.toString(); - } - - return result; - } - - /** - * Indicates if the size of representation is known. It basically means that its - * size 0 or superior. - * - * @return True if the representation has content. - */ - public boolean hasKnownSize() { - return getSize() >= 0; - } - - /** - * Indicates if some fresh content is potentially available, without having to - * actually call one of the content manipulation method like getStream() that - * would actually consume it. Note that when the size of a representation is 0 - * is a not considered available. However, sometimes the size isn't known until - * a read attempt is made, so availability doesn't guarantee a non empty - * content.
- *
- * This is especially useful for transient representation whose content can only - * be accessed once and also when the size of the representation is not known in - * advance. - * - * @return True if some fresh content is available. - */ - public boolean isAvailable() { - return this.available && (getSize() != 0); - } - - /** - * Indicates if the representation is empty. It basically means that its size is - * 0. - * - * @return True if the representation has no content. - */ - public boolean isEmpty() { - return getSize() == 0; - } - - /** - * Indicates if the representation's content is transient, which means that it - * can be obtained only once. This is often the case with representations - * transmitted via network sockets for example. In such case, if you need to - * read the content several times, you need to cache it first, for example into - * memory or into a file. - * - * @return True if the representation's content is transient. - */ - public boolean isTransient() { - return this.isTransient; - } - - /** - * Releases the representation and all associated objects like streams, channels - * or files which are used to produce its content, transient or not. This method - * must be systematically called when the representation is no longer intended - * to be used. The framework automatically calls back this method via its - * connectors on the server-side when sending responses with an entity and on - * the client-side when sending a request with an entity. By default, it calls - * the {@link #setAvailable(boolean)} method with "false" as a value.
- *
- * Note that for transient socket-bound representations, calling this method - * after consuming the whole content shouldn't prevent the reuse of underlying - * socket via persistent connections for example. However, if the content hasn't - * been read, or has been partially read, the impact should be to discard the - * remaining content and to close the underlying connections.
- *
- * Therefore, if you are not interested in the content, or in the remaining - * content, you should first call the {@link #exhaust()} method or if this could - * be too costly, you should instead explicitly abort the parent request and the - * underlying connections using the {@link Request#abort()} method or a shortcut - * one like {@link org.restlet.resource.ServerResource#abort()} or - * {@link Response#abort()}. - */ - public void release() { - setAvailable(false); - } - - /** - * Indicates if some fresh content is available. - * - * @param available True if some fresh content is available. - */ - public void setAvailable(boolean available) { - this.available = available; - } - - /** - * Sets the representation digest.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-MD5" header. - * - * @param digest The representation digest. - */ - public void setDigest(org.restlet.data.Digest digest) { - this.digest = digest; - } - - /** - * Sets the disposition characteristics of the representation. - * - * @param disposition The disposition characteristics of the representation. - */ - public void setDisposition(Disposition disposition) { - this.disposition = disposition; - } - - /** - * Sets the future date when this representation expire. If this information is - * not known, pass null.
- *
- * Note that when used with HTTP connectors, this property maps to the "Expires" - * header. - * - * @param expirationDate The expiration date. - */ - public void setExpirationDate(Date expirationDate) { - this.expirationDate = DateUtils.unmodifiable(expirationDate); - } - - /** - * Sets the range where in the full content the partial content available should - * be applied.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Range" header. - * - * @param range The content range. - */ - public void setRange(Range range) { - this.range = range; - } - - /** - * Sets the expected size in bytes if known, -1 otherwise. For this purpose, you - * can use the {@link #getAvailableSize()} method.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Length" header. - * - * @param expectedSize The expected size in bytes if known, -1 otherwise. - */ - public void setSize(long expectedSize) { - this.size = expectedSize; - } - - /** - * Indicates if the representation's content is transient. - * - * @param isTransient True if the representation's content is transient. - */ - public void setTransient(boolean isTransient) { - this.isTransient = isTransient; - } - - /** - * Writes the representation to a characters writer. This method is ensured to - * write the full content for each invocation unless it is a transient - * representation, in which case an exception is thrown.
- *
- * Note that the class implementing this method shouldn't flush or close the - * given {@link java.io.Writer} after writing to it as this will be handled by - * the Restlet connectors automatically. - * - * @param writer The characters writer. - * @throws IOException - */ - public abstract void write(java.io.Writer writer) throws IOException; - - /** - * Writes the representation to a byte stream. This method is ensured to write - * the full content for each invocation unless it is a transient representation, - * in which case an exception is thrown.
- *
- * Note that the class implementing this method shouldn't flush or close the - * given {@link OutputStream} after writing to it as this will be handled by the - * Restlet connectors automatically. - * - * @param outputStream The output stream. - * @throws IOException - */ - public abstract void write(OutputStream outputStream) throws IOException; - + /** Indicates that the size of the representation can't be known in advance. */ + public static final long UNKNOWN_SIZE = -1L; + + /** Indicates if the representation's content is potentially available. */ + private volatile boolean available; + + /** The representation's digest, if any. */ + private volatile org.restlet.data.Digest digest; + + /** The disposition characteristics of the representation. */ + private volatile Disposition disposition; + + /** The expiration date. */ + private volatile Date expirationDate; + + /** Indicates if the representation's content is transient. */ + private volatile boolean isTransient; + + /** Indicates where in the full content the partial content available should be applied. */ + private volatile Range range; + + /** + * The expected size. Dynamic representations can have any size, but sometimes we can know in + * advance the expected size. If the user specifies this expected size, it has a higher priority + * than any size that can be guessed by the representation (like a file size). + */ + private volatile long size; + + /** Default constructor. */ + protected Representation() { + this(null); + } + + /** + * Constructor. + * + * @param mediaType The media type. + */ + protected Representation(MediaType mediaType) { + super(mediaType); + this.available = true; + this.disposition = null; + this.isTransient = false; + this.size = UNKNOWN_SIZE; + this.expirationDate = null; + this.digest = null; + this.range = null; + } + + /** + * Constructor. + * + * @param mediaType The media type. + * @param modificationDate The modification date. + */ + protected Representation(MediaType mediaType, Date modificationDate) { + this(mediaType, modificationDate, null); + } + + /** + * Constructor. + * + * @param mediaType The media type. + * @param modificationDate The modification date. + * @param tag The tag. + */ + protected Representation(MediaType mediaType, Date modificationDate, Tag tag) { + super(mediaType, modificationDate, tag); + } + + /** + * Constructor. + * + * @param mediaType The media type. + * @param tag The tag. + */ + protected Representation(MediaType mediaType, Tag tag) { + this(mediaType, null, tag); + } + + /** + * Constructor from a variant. + * + * @param variant The variant to copy. + * @param modificationDate The modification date. + */ + protected Representation(Variant variant, Date modificationDate) { + this(variant, modificationDate, null); + } + + /** + * Constructor from a variant. + * + * @param variant The variant to copy. + * @param modificationDate The modification date. + * @param tag The tag. + */ + protected Representation(Variant variant, Date modificationDate, Tag tag) { + setCharacterSet(variant.getCharacterSet()); + setEncodings(variant.getEncodings()); + setLocationRef(variant.getLocationRef()); + setLanguages(variant.getLanguages()); + setMediaType(variant.getMediaType()); + setModificationDate(modificationDate); + setTag(tag); + } + + /** + * Constructor from a variant. + * + * @param variant The variant to copy. + * @param tag The tag. + */ + protected Representation(Variant variant, Tag tag) { + this(variant, null, tag); + } + + /** + * Appends the representation to an appendable sequence of characters. This method is ensured to + * write the full content for each invocation unless it is a transient representation, in which + * case an exception is thrown.
+ *
+ * Note that {@link #getText()} is used by the default implementation. + * + * @param appendable The appendable sequence of characters. + * @throws IOException + */ + public void append(Appendable appendable) throws IOException { + appendable.append(getText()); + } + + /** + * Exhaust the content of the representation by reading it and silently discarding anything + * read. By default, it relies on {@link #getStream()} and closes the retrieved stream in the + * end. + * + * @return The number of bytes consumed or -1 if unknown. + * @throws IOException + */ + public long exhaust() throws IOException { + long result = -1L; + + if (isAvailable()) { + InputStream is = getStream(); + result = IoUtils.exhaust(is); + is.close(); + } + + return result; + } + + /** + * Returns the size effectively available. This returns the same value as {@link #getSize()} if + * no range is defined, otherwise it returns the size of the range using {@link + * Range#getSize()}. + * + * @return The available size. + */ + public long getAvailableSize() { + return IoUtils.getAvailableSize(this); + } + + /** + * Returns the representation digest if any.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-MD5" header. + * + * @return The representation's digest or null. + */ + public org.restlet.data.Digest getDigest() { + return this.digest; + } + + /** + * Returns the disposition characteristics of the representation. + * + * @return The disposition characteristics of the representation. + */ + public Disposition getDisposition() { + return disposition; + } + + /** + * Returns the future date when this representation expires. If this information is not known, + * returns null.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Expires" header. + * + * @return The expiration date. + */ + public Date getExpirationDate() { + return this.expirationDate; + } + + /** + * Returns the range where in the full content the partial content available should be applied. + *
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Range" header. + * + * @return The content range or null if the full content is available. + */ + public Range getRange() { + return this.range; + } + + /** + * Returns a character reader with the representation's content. This method is ensured to + * return a fresh reader for each invocation unless it is a transient representation, in which + * case null is returned. If the representation has no character set defined, the system's + * default one will be used. + * + * @return A reader with the representation's content. + * @throws IOException + */ + public abstract Reader getReader() throws IOException; + + /** + * Returns the total size in bytes if known, UNKNOWN_SIZE (-1) otherwise. When ranges are used, + * this might not be the actual size available. For this purpose, you can use the {@link + * #getAvailableSize()} method.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Length" header. + * + * @return The size in bytes if known, UNKNOWN_SIZE (-1) otherwise. + * @see #isEmpty() + */ + public long getSize() { + return this.size; + } + + /** + * Returns a stream with the representation's content. This method is ensured to return a fresh + * stream for each invocation unless it is a transient representation, in which case null is + * returned. + * + * @return A stream with the representation's content. + * @throws IOException + */ + public abstract InputStream getStream() throws IOException; + + /** + * Converts the representation to a string value. Be careful when using this method as the + * conversion of large content to a string fully stored in memory can result in + * OutOfMemoryErrors being thrown. + * + * @return The representation as a string value. + * @throws IOException + */ + public String getText() throws IOException { + String result = null; + + if (isEmpty()) { + result = ""; + } else if (isAvailable()) { + java.io.StringWriter sw = new java.io.StringWriter(); + write(sw); + sw.flush(); + result = sw.toString(); + } + + return result; + } + + /** + * Indicates if the size of representation is known. It basically means that its size 0 or + * superior. + * + * @return True if the representation has content. + */ + public boolean hasKnownSize() { + return getSize() >= 0; + } + + /** + * Indicates if some fresh content is potentially available, without having to actually call one + * of the content manipulation methods like getStream() that would actually consume it. Note + * that when the size of a representation is 0, it is not considered as available. However, + * sometimes the size isn't known until a read attempt is made, so availability doesn't + * guarantee a non-empty content.
+ *
+ * This is especially useful for transient representation whose content can only be accessed + * once and also when the size of the representation is not known in advance. + * + * @return True if some fresh content is available. + */ + public boolean isAvailable() { + return this.available && (getSize() != 0); + } + + /** + * Indicates if the representation is empty. It basically means that its size is 0. + * + * @return True if the representation has no content. + */ + public boolean isEmpty() { + return getSize() == 0; + } + + /** + * Indicates if the representation's content is transient, which means that it can be obtained + * only once. This is often the case with representations transmitted via network sockets, for + * example. In such a case, if you need to read the content several times, you need to cache it + * first, for example, into memory or into a file. + * + * @return True if the representation's content is transient. + */ + public boolean isTransient() { + return this.isTransient; + } + + /** + * Releases the representation and all associated objects like streams, channels, or files that + * are used to produce its content, transient or not. This method must be systematically called + * when the representation is no longer intended to be used. The framework automatically calls + * back this method via its connectors on the server-side when sending responses with an entity + * and on the client-side when sending a request with an entity. By default, it calls the {@link + * #setAvailable(boolean)} method with "false" as a value.
+ *
+ * Note that for transient socket-bound representations, calling this method after consuming the + * whole content shouldn't prevent the reuse of the underlying socket via persistent + * connections, for example. However, if the content hasn't been read or has been partially + * read, the impact should be to discard the remaining content and to close the underlying + * connections.
+ *
+ * Therefore, if you are not interested in the content, or in the remaining content, you should + * first call the {@link #exhaust()} method or if this could be too costly, you should instead + * explicitly abort the parent request and the underlying connections using the {@link + * Request#abort()} method or a shortcut one like {@link + * org.restlet.resource.ServerResource#abort()} or {@link Response#abort()}. + */ + public void release() { + setAvailable(false); + } + + /** + * Indicates if some fresh content is available. + * + * @param available True if some fresh content is available. + */ + public void setAvailable(boolean available) { + this.available = available; + } + + /** + * Sets the representation digest.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-MD5" header. + * + * @param digest The representation digest. + */ + public void setDigest(org.restlet.data.Digest digest) { + this.digest = digest; + } + + /** + * Sets the disposition characteristics of the representation. + * + * @param disposition The disposition characteristics of the representation. + */ + public void setDisposition(Disposition disposition) { + this.disposition = disposition; + } + + /** + * Sets the future date when this representation expires. If this information is not known, pass + * null.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Expires" header. + * + * @param expirationDate The expiration date. + */ + public void setExpirationDate(Date expirationDate) { + this.expirationDate = DateUtils.unmodifiable(expirationDate); + } + + /** + * Sets the range where in the full content the partial content available should be applied.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Range" header. + * + * @param range The content range. + */ + public void setRange(Range range) { + this.range = range; + } + + /** + * Sets the expected size in bytes if known, -1 otherwise. For this purpose, you can use the + * {@link #getAvailableSize()} method.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Length" header. + * + * @param expectedSize The expected size in bytes if known, -1 otherwise. + */ + public void setSize(long expectedSize) { + this.size = expectedSize; + } + + /** + * Indicates if the representation's content is transient. + * + * @param isTransient True if the representation's content is transient. + */ + public void setTransient(boolean isTransient) { + this.isTransient = isTransient; + } + + /** + * Writes the representation to a characters' writer. This method is ensured to write the full + * content for each invocation unless it is a transient representation, in which case an + * exception is thrown.
+ *
+ * Note that the class implementing this method shouldn't flush or close the given {@link + * java.io.Writer} after writing to it as this will be handled by the Restlet connectors + * automatically. + * + * @param writer The characters' writer. + * @throws IOException + */ + public abstract void write(java.io.Writer writer) throws IOException; + + /** + * Writes the representation to a byte stream. This method is ensured to write the full content + * for each invocation unless it is a transient representation, in which case an exception is + * thrown.
+ *
+ * Note that the class implementing this method shouldn't flush or close the given {@link + * OutputStream} after writing to it as this will be handled by the Restlet connectors + * automatically. + * + * @param outputStream The output stream. + * @throws IOException + */ + public abstract void write(OutputStream outputStream) throws IOException; } diff --git a/org.restlet/src/main/java/org/restlet/representation/RepresentationInfo.java b/org.restlet/src/main/java/org/restlet/representation/RepresentationInfo.java index 7210106e77..4e5dfd5ef2 100644 --- a/org.restlet/src/main/java/org/restlet/representation/RepresentationInfo.java +++ b/org.restlet/src/main/java/org/restlet/representation/RepresentationInfo.java @@ -1,173 +1,162 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; +import java.util.Date; import org.restlet.data.MediaType; import org.restlet.data.Tag; import org.restlet.engine.util.DateUtils; -import java.util.Date; - /** - * Information about a representation. Those metadata don't belong to the parent - * {@link Variant} class, however they are important for conditional method - * processing. The advantage over the complete {@link Representation} class is - * that it is much lighter to create. - * - * @see Source dissertation + * Information about a representation. Those metadata don't belong to the parent {@link Variant} + * class, however they are important for conditional method processing. The advantage over the + * complete {@link Representation} class is that it is much lighter to create. + * + * @see Source + * dissertation * @author Jerome Louvel */ public class RepresentationInfo extends Variant { - /** The modification date. */ - private volatile Date modificationDate; - - /** The tag. */ - private volatile Tag tag; - - /** - * Default constructor. - */ - public RepresentationInfo() { - this(null); - } - - /** - * Constructor. - * - * @param mediaType The media type. - */ - public RepresentationInfo(MediaType mediaType) { - this(mediaType, null, null); - } - - /** - * Constructor. - * - * @param mediaType The media type. - * @param modificationDate The modification date. - */ - public RepresentationInfo(MediaType mediaType, Date modificationDate) { - this(mediaType, modificationDate, null); - } - - /** - * Constructor. - * - * @param mediaType The media type. - * @param modificationDate The modification date. - * @param tag The tag. - */ - public RepresentationInfo(MediaType mediaType, Date modificationDate, Tag tag) { - super(mediaType); - this.modificationDate = modificationDate; - this.tag = tag; - } - - /** - * Constructor. - * - * @param mediaType The media type. - * @param tag The tag. - */ - public RepresentationInfo(MediaType mediaType, Tag tag) { - this(mediaType, null, tag); - } - - /** - * Constructor from a variant. - * - * @param variant The variant to copy. - * @param modificationDate The modification date. - */ - public RepresentationInfo(Variant variant, Date modificationDate) { - this(variant, modificationDate, null); - } - - /** - * Constructor from a variant. - * - * @param variant The variant to copy. - * @param modificationDate The modification date. - * @param tag The tag. - */ - public RepresentationInfo(Variant variant, Date modificationDate, Tag tag) { - setCharacterSet(variant.getCharacterSet()); - setEncodings(variant.getEncodings()); - setLocationRef(variant.getLocationRef()); - setLanguages(variant.getLanguages()); - setMediaType(variant.getMediaType()); - setModificationDate(modificationDate); - setTag(tag); - } - - /** - * Constructor from a variant. - * - * @param variant The variant to copy. - * @param tag The tag. - */ - public RepresentationInfo(Variant variant, Tag tag) { - this(variant, null, tag); - } - - /** - * Returns the last date when this representation was modified. If this - * information is not known, returns null.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Last-Modified" header. - * - * @return The modification date. - */ - public Date getModificationDate() { - return this.modificationDate; - } - - /** - * Returns the tag.
- *
- * Note that when used with HTTP connectors, this property maps to the "ETag" - * header. - * - * @return The tag. - */ - public Tag getTag() { - return this.tag; - } - - /** - * Sets the last date when this representation was modified. If this information - * is not known, pass null.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Last-Modified" header. - * - * @param modificationDate The modification date. - */ - public void setModificationDate(Date modificationDate) { - this.modificationDate = DateUtils.unmodifiable(modificationDate); - } - - /** - * Sets the tag.
- *
- * Note that when used with HTTP connectors, this property maps to the "ETag" - * header. - * - * @param tag The tag. - */ - public void setTag(Tag tag) { - this.tag = tag; - } - + /** The modification date. */ + private volatile Date modificationDate; + + /** The tag. */ + private volatile Tag tag; + + /** Default constructor. */ + public RepresentationInfo() { + this(null); + } + + /** + * Constructor. + * + * @param mediaType The media type. + */ + public RepresentationInfo(MediaType mediaType) { + this(mediaType, null, null); + } + + /** + * Constructor. + * + * @param mediaType The media type. + * @param modificationDate The modification date. + */ + public RepresentationInfo(MediaType mediaType, Date modificationDate) { + this(mediaType, modificationDate, null); + } + + /** + * Constructor. + * + * @param mediaType The media type. + * @param modificationDate The modification date. + * @param tag The tag. + */ + public RepresentationInfo(MediaType mediaType, Date modificationDate, Tag tag) { + super(mediaType); + this.modificationDate = modificationDate; + this.tag = tag; + } + + /** + * Constructor. + * + * @param mediaType The media type. + * @param tag The tag. + */ + public RepresentationInfo(MediaType mediaType, Tag tag) { + this(mediaType, null, tag); + } + + /** + * Constructor from a variant. + * + * @param variant The variant to copy. + * @param modificationDate The modification date. + */ + public RepresentationInfo(Variant variant, Date modificationDate) { + this(variant, modificationDate, null); + } + + /** + * Constructor from a variant. + * + * @param variant The variant to copy. + * @param modificationDate The modification date. + * @param tag The tag. + */ + public RepresentationInfo(Variant variant, Date modificationDate, Tag tag) { + setCharacterSet(variant.getCharacterSet()); + setEncodings(variant.getEncodings()); + setLocationRef(variant.getLocationRef()); + setLanguages(variant.getLanguages()); + setMediaType(variant.getMediaType()); + setModificationDate(modificationDate); + setTag(tag); + } + + /** + * Constructor from a variant. + * + * @param variant The variant to copy. + * @param tag The tag. + */ + public RepresentationInfo(Variant variant, Tag tag) { + this(variant, null, tag); + } + + /** + * Returns the last date when this representation was modified. If this information is not + * known, returns null.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Last-Modified" header. + * + * @return The modification date. + */ + public Date getModificationDate() { + return this.modificationDate; + } + + /** + * Returns the tag.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "ETag" header. + * + * @return The tag. + */ + public Tag getTag() { + return this.tag; + } + + /** + * Sets the last date when this representation was modified. If this information is not known, + * pass null.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Last-Modified" header. + * + * @param modificationDate The modification date. + */ + public void setModificationDate(Date modificationDate) { + this.modificationDate = DateUtils.unmodifiable(modificationDate); + } + + /** + * Sets the tag.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "ETag" header. + * + * @param tag The tag. + */ + public void setTag(Tag tag) { + this.tag = tag; + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/StreamRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/StreamRepresentation.java index 462209e7d7..1ada9c7fbe 100644 --- a/org.restlet/src/main/java/org/restlet/representation/StreamRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/StreamRepresentation.java @@ -1,47 +1,44 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; -import org.restlet.data.MediaType; -import org.restlet.engine.io.IoUtils; - import java.io.IOException; import java.io.OutputStream; import java.io.Reader; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; /** * Representation based on a BIO stream. - * + * * @author Jerome Louvel */ public abstract class StreamRepresentation extends Representation { - /** - * Constructor. - * - * @param mediaType The media type. - */ - public StreamRepresentation(MediaType mediaType) { - super(mediaType); - } - - @Override - public Reader getReader() throws IOException { - return IoUtils.getReader(getStream(), getCharacterSet()); - } - - @Override - public void write(java.io.Writer writer) throws IOException { - OutputStream os = IoUtils.getStream(writer, getCharacterSet()); - write(os); - os.flush(); - } - + /** + * Constructor. + * + * @param mediaType The media type. + */ + protected StreamRepresentation(MediaType mediaType) { + super(mediaType); + } + + @Override + public Reader getReader() throws IOException { + return IoUtils.getReader(getStream(), getCharacterSet()); + } + + @Override + public void write(java.io.Writer writer) throws IOException { + OutputStream os = IoUtils.getStream(writer, getCharacterSet()); + write(os); + os.flush(); + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/StringRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/StringRepresentation.java index 07d7e4bef1..75011e5010 100644 --- a/org.restlet/src/main/java/org/restlet/representation/StringRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/StringRepresentation.java @@ -1,191 +1,190 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.data.CharacterSet; import org.restlet.data.Language; import org.restlet.data.MediaType; -import java.io.*; -import java.util.logging.Level; - /** - * Represents an Unicode string that can be converted to any character set - * supported by Java. - * + * Represents a Unicode string that can be converted to any character set supported by Java. + * * @author Jerome Louvel */ public class StringRepresentation extends CharacterRepresentation { - /** The string value. */ - private volatile CharSequence text; - - /** - * Constructor. The following metadata are used by default: "text/plain" media - * type, no language and the UTF-8 character set. - * - * @param chars The characters array. - */ - public StringRepresentation(char[] chars) { - this(new String(chars), MediaType.TEXT_PLAIN); - } - - /** - * Constructor. The following metadata are used by default: "text/plain" media - * type, no language and the UTF-8 character set. - * - * @param text The string value. - */ - public StringRepresentation(CharSequence text) { - this(text, MediaType.TEXT_PLAIN); - } - - /** - * Constructor. The following metadata are used by default: "text/plain" media - * type, no language and the UTF-8 character set. - * - * @param text The string value. - * @param language The language. - */ - public StringRepresentation(CharSequence text, Language language) { - this(text, MediaType.TEXT_PLAIN, language); - } - - /** - * Constructor. The following metadata are used by default: no language and the - * UTF-8 character set. - * - * @param text The string value. - * @param mediaType The media type. - */ - public StringRepresentation(CharSequence text, MediaType mediaType) { - this(text, mediaType, null); - } - - /** - * Constructor. The following metadata are used by default: UTF-8 character set. - * - * @param text The string value. - * @param mediaType The media type. - * @param language The language. - */ - public StringRepresentation(CharSequence text, MediaType mediaType, Language language) { - this(text, mediaType, language, CharacterSet.UTF_8); - } - - /** - * Constructor. - * - * @param text The string value. - * @param mediaType The media type. - * @param language The language. - * @param characterSet The character set. - */ - public StringRepresentation(CharSequence text, MediaType mediaType, Language language, CharacterSet characterSet) { - super(mediaType); - setMediaType(mediaType); - if (language != null) { - getLanguages().add(language); - } - - setCharacterSet(characterSet); - setText(text); - } - - @Override - public Reader getReader() throws IOException { - if (getText() != null) { - return new StringReader(getText()); - } - - return null; - } - - @Override - public InputStream getStream() throws IOException { - CharacterSet charset = getCharacterSet() == null ? CharacterSet.ISO_8859_1 : getCharacterSet(); - ByteArrayInputStream result = new ByteArrayInputStream(getText().getBytes(charset.getName())); - return result; - } - - @Override - public String getText() { - return (this.text == null) ? null : this.text.toString(); - } - - /** - * Closes and releases the input stream. - */ - @Override - public void release() { - setText(null); - super.release(); - } - - @Override - public void setCharacterSet(CharacterSet characterSet) { - super.setCharacterSet(characterSet); - updateSize(); - } - - /** - * Sets the string value. - * - * @param text The string value. - */ - public void setText(CharSequence text) { - this.text = text; - updateSize(); - } - - /** - * Sets the string value. - * - * @param text The string value. - */ - public void setText(String text) { - setText((CharSequence) text); - } - - @Override - public String toString() { - return getText(); - } - - /** - * Updates the expected size according to the current string value. - */ - protected void updateSize() { - if (getText() != null) { - try { - if (getCharacterSet() != null) { - setSize(getText().getBytes(getCharacterSet().getName()).length); - } else { - setSize(getText().getBytes().length); - } - } catch (UnsupportedEncodingException e) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to update size", e); - setSize(UNKNOWN_SIZE); - } - } else { - setSize(UNKNOWN_SIZE); - } - } - - @Override - public void write(Writer writer) throws IOException { - if (getText() != null) { - writer.write(getText()); - writer.flush(); - } - } - + /** The string value. */ + private volatile CharSequence text; + + /** + * Constructor. The following metadata is used by default: "text/plain" media type, no language, + * and the UTF-8 character set. + * + * @param chars The characters array. + */ + public StringRepresentation(char[] chars) { + this(new String(chars), MediaType.TEXT_PLAIN); + } + + /** + * Constructor. The following metadata is used by default: "text/plain" media type, no language, + * and the UTF-8 character set. + * + * @param text The string value. + */ + public StringRepresentation(CharSequence text) { + this(text, MediaType.TEXT_PLAIN); + } + + /** + * Constructor. The following metadata is used by default: "text/plain" media type, no language, + * and the UTF-8 character set. + * + * @param text The string value. + * @param language The language. + */ + public StringRepresentation(CharSequence text, Language language) { + this(text, MediaType.TEXT_PLAIN, language); + } + + /** + * Constructor. The following metadata is used by default: no language and the UTF-8 character + * set. + * + * @param text The string value. + * @param mediaType The media type. + */ + public StringRepresentation(CharSequence text, MediaType mediaType) { + this(text, mediaType, null); + } + + /** + * Constructor. The following metadata is used by default: UTF-8 character set. + * + * @param text The string value. + * @param mediaType The media type. + * @param language The language. + */ + public StringRepresentation(CharSequence text, MediaType mediaType, Language language) { + this(text, mediaType, language, CharacterSet.UTF_8); + } + + /** + * Constructor. + * + * @param text The string value. + * @param mediaType The media type. + * @param language The language. + * @param characterSet The character set. + */ + public StringRepresentation( + CharSequence text, MediaType mediaType, Language language, CharacterSet characterSet) { + super(mediaType); + setMediaType(mediaType); + if (language != null) { + getLanguages().add(language); + } + + setCharacterSet(characterSet); + setText(text); + } + + @Override + public Reader getReader() throws IOException { + if (getText() != null) { + return new StringReader(getText()); + } + + return null; + } + + @Override + public InputStream getStream() throws IOException { + CharacterSet charset = + getCharacterSet() == null ? CharacterSet.ISO_8859_1 : getCharacterSet(); + return new ByteArrayInputStream(getText().getBytes(charset.getName())); + } + + @Override + public String getText() { + return (this.text == null) ? null : this.text.toString(); + } + + /** Closes and releases the input stream. */ + @Override + public void release() { + setText(null); + super.release(); + } + + @Override + public void setCharacterSet(CharacterSet characterSet) { + super.setCharacterSet(characterSet); + updateSize(); + } + + /** + * Sets the string value. + * + * @param text The string value. + */ + public void setText(CharSequence text) { + this.text = text; + updateSize(); + } + + /** + * Sets the string value. + * + * @param text The string value. + */ + public void setText(String text) { + setText((CharSequence) text); + } + + @Override + public String toString() { + return getText(); + } + + /** Updates the expected size according to the current string value. */ + protected void updateSize() { + if (getText() != null) { + try { + if (getCharacterSet() != null) { + setSize(getText().getBytes(getCharacterSet().getName()).length); + } else { + setSize(getText().getBytes().length); + } + } catch (UnsupportedEncodingException e) { + Context.getCurrentLogger().log(Level.WARNING, "Unable to update size", e); + setSize(UNKNOWN_SIZE); + } + } else { + setSize(UNKNOWN_SIZE); + } + } + + @Override + public void write(Writer writer) throws IOException { + if (getText() != null) { + writer.write(getText()); + writer.flush(); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/Variant.java b/org.restlet/src/main/java/org/restlet/representation/Variant.java index ca352585ca..d6d14a780f 100644 --- a/org.restlet/src/main/java/org/restlet/representation/Variant.java +++ b/org.restlet/src/main/java/org/restlet/representation/Variant.java @@ -1,476 +1,369 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; -import org.restlet.data.*; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import org.restlet.data.CharacterSet; +import org.restlet.data.ClientInfo; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Preference; +import org.restlet.data.Reference; import org.restlet.engine.util.SystemUtils; -import org.restlet.util.WrapperList; - -import java.util.*; +import org.restlet.util.NonNullItemsList; /** - * Descriptor for available representations of a resource. It contains all the - * important metadata about a representation but is not able to actually serve - * the representation's content itself.
+ * Descriptor for available representations of a resource. It contains all the important metadata + * about a representation but is not able to actually serve the representation's content itself.
*
- * For this, you need to use on of the {@link Representation} subclasses. - * + * For this, you need to use one of the {@link Representation} subclasses. + * * @author Jerome Louvel */ public class Variant { - /** The character set or null if not applicable. */ - private volatile CharacterSet characterSet; - - /** The additional content codings applied to the entity-body. */ - private volatile List encodings; - - /** The location reference. */ - private volatile Reference locationRef; - - /** The natural language(s) of the intended audience for this variant. */ - private volatile List languages; - - /** The media type. */ - private volatile MediaType mediaType; - - /** - * Default constructor. - */ - public Variant() { - this(null); - } - - /** - * Constructor. - * - * @param mediaType The media type. - */ - public Variant(MediaType mediaType) { - this(mediaType, null); - } - - /** - * Constructor. - * - * @param mediaType The media type. - * @param language The language. - */ - public Variant(MediaType mediaType, Language language) { - this.characterSet = null; - this.encodings = null; - - if (language != null) { - getLanguages().add(language); - } else { - this.languages = null; - } - - this.mediaType = mediaType; - this.locationRef = null; - } - - /** - * Creates a {@link ClientInfo} instance with preferences matching exactly the - * current variant. - * - * @return The new {@link ClientInfo} instance. - */ - public ClientInfo createClientInfo() { - ClientInfo result = new ClientInfo(); - - if (getCharacterSet() != null) { - result.getAcceptedCharacterSets().add(new Preference(getCharacterSet())); - } - - if (getEncodings() != null) { - for (Encoding encoding : getEncodings()) { - result.getAcceptedEncodings().add(new Preference(encoding)); - } - } - - if (getLanguages() != null) { - for (Language language : getLanguages()) { - result.getAcceptedLanguages().add(new Preference(language)); - } - } - - if (getMediaType() != null) { - result.getAcceptedMediaTypes().add(new Preference(getMediaType())); - } - - return result; - } - - /** - * Indicates if the current variant is equal to the given variant. - * - * @param other The other variant. - * @return True if the current variant includes the other. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof Variant)) { - return false; - } - - Variant that = (Variant) other; - - return Objects.equals(getCharacterSet(), that.getCharacterSet()) - && Objects.equals(getMediaType(), that.getMediaType()) && getLanguages().equals(that.getLanguages()) - && getEncodings().equals(that.getEncodings()) - && Objects.equals(getLocationRef(), that.getLocationRef()); - } - - /** - * Returns the character set or null if not applicable. Note that when used with - * HTTP connectors, this property maps to the "Content-Type" header. - * - * @return The character set or null if not applicable. - */ - public CharacterSet getCharacterSet() { - return this.characterSet; - } - - /** - * Returns the modifiable list of encodings applied to the entity-body. Creates - * a new instance if no one has been set. An "IllegalArgumentException" - * exception is thrown when adding a null encoding to this list.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Encoding" header. - * - * @return The list of encodings applied to the entity-body. - */ - public List getEncodings() { - if (this.encodings == null) { - this.encodings = new WrapperList() { - - @Override - public boolean add(Encoding element) { - if (element == null) { - throw new IllegalArgumentException("Cannot add a null encoding."); - } - - return super.add(element); - } - - @Override - public void add(int index, Encoding element) { - if (element == null) { - throw new IllegalArgumentException("Cannot add a null encoding."); - } - - super.add(index, element); - } - - @Override - public boolean addAll(Collection elements) { - boolean addNull = (elements == null); - if (!addNull) { - for (final Iterator iterator = elements.iterator(); !addNull - && iterator.hasNext();) { - addNull = (iterator.next() == null); - } - } - if (addNull) { - throw new IllegalArgumentException("Cannot add a null encoding."); - } - - return super.addAll(elements); - } - - @Override - public boolean addAll(int index, Collection elements) { - boolean addNull = (elements == null); - if (!addNull) { - for (final Iterator iterator = elements.iterator(); !addNull - && iterator.hasNext();) { - addNull = (iterator.next() == null); - } - } - if (addNull) { - throw new IllegalArgumentException("Cannot add a null encoding."); - } - - return super.addAll(index, elements); - } - }; - } - - return this.encodings; - } - - /** - * Returns the modifiable list of languages. Creates a new instance if no one - * has been set. An "IllegalArgumentException" exception is thrown when adding a - * null language to this list.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Language" header. - * - * @return The list of languages. - */ - public List getLanguages() { - if (this.languages == null) { - this.languages = new WrapperList() { - - @Override - public void add(int index, Language element) { - if (element == null) { - throw new IllegalArgumentException("Cannot add a null language."); - } - - super.add(index, element); - } - - @Override - public boolean add(Language element) { - if (element == null) { - throw new IllegalArgumentException("Cannot add a null language."); - } - - return super.add(element); - } - - @Override - public boolean addAll(Collection elements) { - boolean addNull = (elements == null); - if (!addNull) { - for (final Iterator iterator = elements.iterator(); !addNull - && iterator.hasNext();) { - addNull = (iterator.next() == null); - } - } - if (addNull) { - throw new IllegalArgumentException("Cannot add a null language."); - } - - return super.addAll(elements); - } - - @Override - public boolean addAll(int index, Collection elements) { - boolean addNull = (elements == null); - if (!addNull) { - for (final Iterator iterator = elements.iterator(); !addNull - && iterator.hasNext();) { - addNull = (iterator.next() == null); - } - } - if (addNull) { - throw new IllegalArgumentException("Cannot add a null language."); - } - - return super.addAll(index, elements); - } - - }; - } - return this.languages; - } - - /** - * Returns an optional location reference. This is useful when the - * representation is accessible from a location separate from the - * representation's resource URI, for example when content negotiation - * occurs.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Location" header. - * - * @return The identifier. - */ - public Reference getLocationRef() { - return this.locationRef; - } - - /** - * Returns the media type.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Type" header. - * - * @return The media type. - */ - public MediaType getMediaType() { - return this.mediaType; - } - - @Override - public int hashCode() { - return SystemUtils.hashCode(super.hashCode(), characterSet, encodings, locationRef, languages, mediaType); - } - - /** - * Indicates if the current variant includes the given variant. - * - * @param other The other variant. - * @return True if the current variant includes the other. - */ - public boolean includes(Variant other) { - boolean result = other != null; - - // Compare the character set - if (result) { - result = (getCharacterSet() == null) || getCharacterSet().includes(other.getCharacterSet()); - } - - // Compare the media type - if (result) { - result = (getMediaType() == null) || getMediaType().includes(other.getMediaType()); - } - - // Compare the languages - if (result) { - result = (getLanguages().isEmpty()) || getLanguages().contains(Language.ALL) - || new HashSet<>(getLanguages()).containsAll(other.getLanguages()); - } - - // Compare the encodings - if (result) { - result = (getEncodings().isEmpty()) || getEncodings().contains(Encoding.ALL) - || new HashSet<>(getEncodings()).containsAll(other.getEncodings()); - } - - return result; - } - - /** - * Indicates if the current variant is compatible with the given variant. - * - * @param other The other variant. - * @return True if the current variant is compatible with the other. - */ - public boolean isCompatible(Variant other) { - return (other != null) && (includes(other) || other.includes(this)); - } - - /** - * Sets the character set or null if not applicable.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Type" header. - * - * @param characterSet The character set or null if not applicable. - */ - public void setCharacterSet(CharacterSet characterSet) { - this.characterSet = characterSet; - } - - /** - * Sets the list of encodings applied to the entity-body.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Encoding" header. - * - * @param encodings The list of encodings applied to the entity-body. - */ - public void setEncodings(List encodings) { - this.encodings = encodings; - } - - /** - * Sets the list of languages.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Language" header. - * - * @param languages The list of languages. - */ - public void setLanguages(List languages) { - this.languages = languages; - } - - /** - * Sets the optional identifier. This is useful when the representation is - * accessible from a location separate from the representation's resource URI, - * for example when content negotiation occurs.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Location" header. - * - * @param location The location reference. - */ - public void setLocationRef(Reference location) { - this.locationRef = location; - } - - /** - * Sets the identifier from a URI string.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Location" header. - * - * @param locationUri The location URI to parse. - */ - public void setLocationRef(String locationUri) { - setLocationRef(new Reference(locationUri)); - } - - /** - * Sets the media type.
- *
- * Note that when used with HTTP connectors, this property maps to the - * "Content-Type" header. - * - * @param mediaType The media type. - */ - public void setMediaType(MediaType mediaType) { - this.mediaType = mediaType; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("["); - boolean first = true; - - if (getMediaType() != null) { - first = false; - sb.append(getMediaType()); - } - - if (getCharacterSet() != null) { - if (!first) { - sb.append(","); - } else { - first = false; - } - - sb.append(getCharacterSet()); - } - - if (!getLanguages().isEmpty()) { - if (!first) { - sb.append(","); - } else { - first = false; - } - - sb.append(getLanguages()); - } - - if (!getEncodings().isEmpty()) { - if (!first) { - sb.append(","); - } else { - first = false; - } - - sb.append(getEncodings()); - } - - sb.append("]"); - return sb.toString(); - } + /** The character set or null if not applicable. */ + private volatile CharacterSet characterSet; + + /** The additional content codings applied to the entity-body. */ + private volatile List encodings; + + /** The location reference. */ + private volatile Reference locationRef; + + /** The natural language(s) of the intended audience for this variant. */ + private volatile List languages; + + /** The media type. */ + private volatile MediaType mediaType; + + /** Default constructor. */ + public Variant() { + this(null); + } + + /** + * Constructor. + * + * @param mediaType The media type. + */ + public Variant(MediaType mediaType) { + this(mediaType, null); + } + + /** + * Constructor. + * + * @param mediaType The media type. + * @param language The language. + */ + public Variant(MediaType mediaType, Language language) { + this.characterSet = null; + this.encodings = null; + + if (language != null) { + getLanguages().add(language); + } else { + this.languages = null; + } + + this.mediaType = mediaType; + this.locationRef = null; + } + + /** + * Creates a {@link ClientInfo} instance with preferences matching exactly the current variant. + * + * @return The new {@link ClientInfo} instance. + */ + public ClientInfo createClientInfo() { + ClientInfo result = new ClientInfo(); + + if (getCharacterSet() != null) { + result.getAcceptedCharacterSets().add(new Preference<>(getCharacterSet())); + } + + if (getEncodings() != null) { + for (Encoding encoding : getEncodings()) { + result.getAcceptedEncodings().add(new Preference<>(encoding)); + } + } + + if (getLanguages() != null) { + for (Language language : getLanguages()) { + result.getAcceptedLanguages().add(new Preference<>(language)); + } + } + + if (getMediaType() != null) { + result.getAcceptedMediaTypes().add(new Preference<>(getMediaType())); + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof final Variant that)) { + return false; + } + + return Objects.equals(getCharacterSet(), that.getCharacterSet()) + && Objects.equals(getMediaType(), that.getMediaType()) + && getLanguages().equals(that.getLanguages()) + && getEncodings().equals(that.getEncodings()) + && Objects.equals(getLocationRef(), that.getLocationRef()); + } + + /** + * Returns the character set or null if not applicable. Note that when used with HTTP + * connectors, this property maps to the "Content-Type" header. + * + * @return The character set or null if not applicable. + */ + public CharacterSet getCharacterSet() { + return this.characterSet; + } + + /** + * Returns the modifiable list of encodings applied to the entity-body. Creates a new instance + * if no one has been set. An "IllegalArgumentException" exception is thrown when adding a null + * encoding to this list.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Encoding" + * header. + * + * @return The list of encodings applied to the entity-body. + */ + public List getEncodings() { + if (this.encodings == null) { + this.encodings = new NonNullItemsList<>("Cannot add a null encoding"); + } + + return this.encodings; + } + + /** + * Returns the modifiable list of languages. Creates a new instance if no one has been set. An + * "IllegalArgumentException" exception is thrown when adding a null language to this list.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Language" + * header. + * + * @return The list of languages. + */ + public List getLanguages() { + if (this.languages == null) { + this.languages = new NonNullItemsList<>("Cannot add a null language"); + } + return this.languages; + } + + /** + * Returns an optional location reference. This is useful when the representation is accessible + * from a location separate from the representation's resource URI, for example, when content + * negotiation occurs.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Location" + * header. + * + * @return The identifier. + */ + public Reference getLocationRef() { + return this.locationRef; + } + + /** + * Returns the media type.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Type" header. + * + * @return The media type. + */ + public MediaType getMediaType() { + return this.mediaType; + } + + @Override + public int hashCode() { + return SystemUtils.hashCode( + super.hashCode(), characterSet, encodings, locationRef, languages, mediaType); + } + + /** + * Indicates if the current variant includes the given variant. + * + * @param other The other variant. + * @return True if the current variant includes the other. + */ + public boolean includes(Variant other) { + boolean result = other != null; + + // Compare the character set + if (result) { + result = + (getCharacterSet() == null) + || getCharacterSet().includes(other.getCharacterSet()); + } + + // Compare the media type + if (result) { + result = (getMediaType() == null) || getMediaType().includes(other.getMediaType()); + } + + // Compare the languages + if (result) { + result = + (getLanguages().isEmpty()) + || getLanguages().contains(Language.ALL) + || new HashSet<>(getLanguages()).containsAll(other.getLanguages()); + } + + // Compare the encodings + if (result) { + result = + (getEncodings().isEmpty()) + || getEncodings().contains(Encoding.ALL) + || new HashSet<>(getEncodings()).containsAll(other.getEncodings()); + } + + return result; + } + + /** + * Indicates if the current variant is compatible with the given variant. + * + * @param other The other variant. + * @return True if the current variant is compatible with the other. + */ + public boolean isCompatible(Variant other) { + return (other != null) && (includes(other) || other.includes(this)); + } + + /** + * Sets the character set or null if not applicable.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Type" header. + * + * @param characterSet The character set or null if not applicable. + */ + public void setCharacterSet(CharacterSet characterSet) { + this.characterSet = characterSet; + } + + /** + * Sets the list of encodings applied to the entity-body.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Encoding" + * header. + * + * @param encodings The list of encodings applied to the entity-body. + */ + public void setEncodings(List encodings) { + this.encodings = encodings; + } + + /** + * Sets the list of languages.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Language" + * header. + * + * @param languages The list of languages. + */ + public void setLanguages(List languages) { + this.languages = languages; + } + + /** + * Sets the optional identifier. This is useful when the representation is accessible from a + * location separate from the representation's resource URI, for example, when content + * negotiation occurs.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Location" + * header. + * + * @param location The location reference. + */ + public void setLocationRef(Reference location) { + this.locationRef = location; + } + + /** + * Sets the identifier from a URI string.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Location" + * header. + * + * @param locationUri The location URI to parse. + */ + public void setLocationRef(String locationUri) { + setLocationRef(new Reference(locationUri)); + } + + /** + * Sets the media type.
+ *
+ * Note that when used with HTTP connectors, this property maps to the "Content-Type" header. + * + * @param mediaType The media type. + */ + public void setMediaType(MediaType mediaType) { + this.mediaType = mediaType; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("["); + boolean first = true; + + if (getMediaType() != null) { + first = false; + sb.append(getMediaType()); + } + + if (getCharacterSet() != null) { + if (!first) { + sb.append(","); + } else { + first = false; + } + + sb.append(getCharacterSet()); + } + + if (!getLanguages().isEmpty()) { + if (!first) { + sb.append(","); + } else { + first = false; + } + + sb.append(getLanguages()); + } + + if (!getEncodings().isEmpty()) { + if (!first) { + sb.append(","); + } + + sb.append(getEncodings()); + } + + sb.append("]"); + return sb.toString(); + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/WriterRepresentation.java b/org.restlet/src/main/java/org/restlet/representation/WriterRepresentation.java index 0c8ddd8216..75743f1459 100644 --- a/org.restlet/src/main/java/org/restlet/representation/WriterRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/representation/WriterRepresentation.java @@ -1,56 +1,52 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; -import org.restlet.data.MediaType; -import org.restlet.engine.io.IoUtils; - import java.io.IOException; import java.io.Reader; +import org.restlet.data.MediaType; +import org.restlet.engine.io.IoUtils; /** - * Representation based on a BIO characters writer. This class is a good basis - * to write your own representations, especially for the dynamic and large ones. - *
+ * Representation based on a BIO characters writer. This class is a good basis to write your own + * representations, especially for the dynamic and large ones.
*
- * For this you just need to create a subclass and override the abstract - * Representation.write(Writer) method. This method will later be called back by - * the connectors when the actual representation's content is needed. - * + * For this you need to create a subclass and override the abstract Representation.write(Writer) + * method. This method will later be called back by the connectors when the actual representation's + * content is needed. + * * @author Jerome Louvel */ public abstract class WriterRepresentation extends CharacterRepresentation { - /** - * Constructor. - * - * @param mediaType The representation's mediaType. - */ - public WriterRepresentation(MediaType mediaType) { - super(mediaType); - } - - /** - * Constructor. - * - * @param mediaType The representation's mediaType. - * @param expectedSize The expected writer size in bytes. - */ - public WriterRepresentation(MediaType mediaType, long expectedSize) { - super(mediaType); - setSize(expectedSize); - } - - @Override - public Reader getReader() throws IOException { - return IoUtils.getReader(this); - } - + /** + * Constructor. + * + * @param mediaType The representation's mediaType. + */ + protected WriterRepresentation(MediaType mediaType) { + super(mediaType); + } + + /** + * Constructor. + * + * @param mediaType The representation's mediaType. + * @param expectedSize The expected writer size in bytes. + */ + protected WriterRepresentation(MediaType mediaType, long expectedSize) { + super(mediaType); + setSize(expectedSize); + } + + @Override + public Reader getReader() throws IOException { + return IoUtils.getReader(this); + } } diff --git a/org.restlet/src/main/java/org/restlet/representation/package-info.java b/org.restlet/src/main/java/org/restlet/representation/package-info.java new file mode 100644 index 0000000000..4252937bcf --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/representation/package-info.java @@ -0,0 +1,8 @@ +/** + * Common representation data elements. + * + * @since Restlet 2.0 + * @see User + * Guide - Representation package + */ +package org.restlet.representation; diff --git a/org.restlet/src/main/java/org/restlet/representation/package.html b/org.restlet/src/main/java/org/restlet/representation/package.html deleted file mode 100644 index 390bb26997..0000000000 --- a/org.restlet/src/main/java/org/restlet/representation/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - - Common representation data elements. -

- @since Restlet 2.0 - @see User Guide - Representation package - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/resource/ClientProxy.java b/org.restlet/src/main/java/org/restlet/resource/ClientProxy.java index f6d3a253b6..0fbb71931c 100644 --- a/org.restlet/src/main/java/org/restlet/resource/ClientProxy.java +++ b/org.restlet/src/main/java/org/restlet/resource/ClientProxy.java @@ -1,29 +1,27 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** - * Marker interface for RESTful resource proxies. This allows you to retrieve - * and manipulate the underlying {@link ClientResource} of a dynamic client - * proxy generated by the {@link ClientResource#create(String, Class)} method - * for example, or by {@link ClientResource#getChild(String, Class)}. - * + * Marker interface for RESTful resource proxies. This allows you to retrieve and manipulate the + * underlying {@link ClientResource} of a dynamic client proxy generated by the {@link + * ClientResource#create(String, Class)} method for example, or by {@link + * ClientResource#getChild(String, Class)}. + * * @author Jerome Louvel */ public interface ClientProxy { - /** - * Returns the wrapped client resource. - * - * @return The wrapped client resource. - */ - ClientResource getClientResource(); - + /** + * Returns the wrapped client resource. + * + * @return The wrapped client resource. + */ + ClientResource getClientResource(); } diff --git a/org.restlet/src/main/java/org/restlet/resource/ClientResource.java b/org.restlet/src/main/java/org/restlet/resource/ClientResource.java index 6dc199624b..1d6f84c848 100644 --- a/org.restlet/src/main/java/org/restlet/resource/ClientResource.java +++ b/org.restlet/src/main/java/org/restlet/resource/ClientResource.java @@ -1,1852 +1,1846 @@ /** * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; -import org.restlet.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import org.restlet.Client; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.Uniform; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.ClientInfo; +import org.restlet.data.Conditions; +import org.restlet.data.Cookie; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; +import org.restlet.data.Method; +import org.restlet.data.Parameter; +import org.restlet.data.Protocol; +import org.restlet.data.Range; +import org.restlet.data.Reference; import org.restlet.data.Status; -import org.restlet.data.*; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.representation.Variant; import org.restlet.util.Series; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; - /** * Client-side resource. Acts like a proxy of a target resource.
- * This class changes the semantics of the {@link Resource#getRequest()} and - * {@link Resource#getResponse()} methods. Since a clientResource may receive - * severals responses for a single request (in case of interim response), the - * {@link #getResponse()} method returns the last received response object. The - * Request object returned by the {@link #getRequest()} is actually a prototype - * which is cloned (except the representation) just before the {@link #handle()} - * method is called.
- * Users must be aware that by most representations can only be read or written - * once. Some others, such as {@link StringRepresentation} stored the entity in - * memory which can be read several times but has the drawback to consume - * memory.
- * Concurrency note: instances of the class are not designed to be shared among - * several threads. If thread-safety is necessary, consider using the - * lower-level {@link Client} class instead. - * + * This class changes the semantics of the {@link Resource#getRequest()} and {@link + * Resource#getResponse()} methods. Since a clientResource may receive severals responses for a + * single request (in case of interim response), the {@link #getResponse()} method returns the last + * received response object. The Request object returned by the {@link #getRequest()} is actually a + * prototype which is cloned (except the representation) just before the {@link #handle()} method is + * called.
+ * Users must be aware that by most representations can only be read or written once. Some others, + * such as {@link StringRepresentation} stored the entity in memory which can be read several times + * but has the drawback to consume memory.
+ * Concurrency note: instances of the class are not designed to be shared among several threads. If + * thread-safety is necessary, consider using the lower-level {@link Client} class instead. + * * @author Jerome Louvel */ public class ClientResource extends Resource { - /** - * Creates a client resource that proxy calls to the given Java interface into - * Restlet method calls. It basically creates a new instance of - * {@link ClientResource} and invokes the {@link #wrap(Class)} method. - * - * @param The proxified interface. - * @param context The context. - * @param reference The target reference. - * @param resourceInterface The annotated resource interface class to proxy. - * @return The proxy instance. - */ - public static T create(Context context, Reference reference, Class resourceInterface) { - ClientResource clientResource = new ClientResource(context, reference); - return clientResource.wrap(resourceInterface); - } - - /** - * Creates a client resource that proxy calls to the given Java interface into - * Restlet method calls. It basically creates a new instance of - * {@link ClientResource} and invokes the {@link #wrap(Class)} method. - * - * @param The proxified interface. - * @param reference The target reference. - * @param resourceInterface The annotated resource interface class to proxy. - * @return The proxy instance. - */ - public static T create(Reference reference, Class resourceInterface) { - return create(null, reference, resourceInterface); - } - - /** - * Creates a client resource that proxy calls to the given Java interface into - * Restlet method calls. It basically creates a new instance of - * {@link ClientResource} and invokes the {@link #wrap(Class)} method. - * - * @param The proxified interface. - * @param uri The target URI. - * @param resourceInterface The annotated resource interface class to proxy. - * @return The proxy instance. - */ - public static T create(String uri, Class resourceInterface) { - return create(null, new Reference(uri), resourceInterface); - } - - /** Indicates if redirections should be automatically followed. */ - private volatile boolean followingRedirects; - - /** - * Indicates the maximum number of redirections that can be automatically - * followed for a single call. - */ - private volatile int maxRedirects; - - /** The next Restlet. */ - private volatile Uniform next; - - /** Indicates if the next Restlet has been created. */ - private volatile boolean nextCreated; - - /** - * Indicates if transient or unknown size request entities should be buffered - * before being sent. - */ - private volatile boolean requestEntityBuffering; - - /** - * Indicates if transient or unknown size response entities should be buffered - * after being received. - */ - private volatile boolean responseEntityBuffering; - - /** Number of retry attempts before reporting an error. */ - private volatile int retryAttempts; - - /** Delay in milliseconds between two retry attempts. */ - private volatile long retryDelay; - - /** Indicates if idempotent requests should be retried on error. */ - private volatile boolean retryOnError; - - /** - * Empty constructor. - */ - protected ClientResource() { - } - - /** - * Constructor. - * - * @param resource The client resource to copy. - */ - public ClientResource(ClientResource resource) { - Request request = new Request(resource.getRequest()); - Response response = new Response(request); - this.next = resource.getNext(); - this.maxRedirects = resource.getMaxRedirects(); - this.retryOnError = resource.isRetryOnError(); - this.retryDelay = resource.getRetryDelay(); - this.retryAttempts = resource.getRetryAttempts(); - - this.followingRedirects = resource.isFollowingRedirects(); - this.requestEntityBuffering = resource.isRequestEntityBuffering(); - this.responseEntityBuffering = resource.isResponseEntityBuffering(); - setApplication(resource.getApplication()); - - init(resource.getContext(), request, response); - } - - /** - * Constructor. - * - * @param context The context. - * @param uri The target URI. - */ - public ClientResource(Context context, java.net.URI uri) { - this(context, Method.GET, uri); - } - - /** - * Constructor. - * - * @param context The context. - * @param method The method to call. - * @param uri The target URI. - */ - public ClientResource(Context context, Method method, java.net.URI uri) { - this(context, method, new Reference(uri)); - } - - /** - * Constructor. - * - * @param context The context. - * @param method The method to call. - * @param reference The target reference. - */ - public ClientResource(Context context, Method method, Reference reference) { - this(context, new Request(method, reference), new Response(null)); - } - - /** - * Constructor. - * - * @param context The context. - * @param method The method to call. - * @param uri The target URI. - */ - public ClientResource(Context context, Method method, String uri) { - this(context, method, new Reference(uri)); - } - - /** - * Constructor. - * - * @param context The context. - * @param reference The target reference. - */ - public ClientResource(Context context, Reference reference) { - this(context, Method.GET, reference); - } - - /** - * Constructor. - * - * @param context The current context. - * @param request The handled request. - */ - public ClientResource(Context context, Request request) { - this(context, request, null); - } - - /** - * Constructor. - * - * @param context The current context. - * @param request The handled request. - * @param response The handled response. - */ - public ClientResource(Context context, Request request, Response response) { - if (context == null) { - context = Context.getCurrent(); - } - - // Don't remove this line. - // See other constructor ClientResource(Context, Method, Reference) - response.setRequest(request); - - this.maxRedirects = 10; - this.retryOnError = true; - this.retryDelay = 2000L; - this.retryAttempts = 2; - this.followingRedirects = true; - this.requestEntityBuffering = false; - this.responseEntityBuffering = false; - init(context, request, response); - } - - /** - * Constructor. - * - * @param context The context. - * @param uri The target URI. - */ - public ClientResource(Context context, String uri) { - this(context, Method.GET, uri); - } - - /** - * Constructor. - * - * @param uri The target URI. - */ - public ClientResource(java.net.URI uri) { - this(Context.getCurrent(), null, uri); - } - - /** - * Constructor. - * - * @param method The method to call. - * @param uri The target URI. - */ - public ClientResource(Method method, java.net.URI uri) { - this(Context.getCurrent(), method, uri); - } - - /** - * Constructor. - * - * @param method The method to call. - * @param reference The target reference. - */ - public ClientResource(Method method, Reference reference) { - this(Context.getCurrent(), method, reference); - } - - /** - * Constructor. - * - * @param method The method to call. - * @param uri The target URI. - */ - public ClientResource(Method method, String uri) { - this(Context.getCurrent(), method, uri); - } - - /** - * Constructor. - * - * @param reference The target reference. - */ - public ClientResource(Reference reference) { - this(Context.getCurrent(), null, reference); - } - - /** - * Constructor. - * - * @param request The handled request. - */ - public ClientResource(Request request) { - this(request, new Response(request)); - } - - /** - * Constructor. - * - * @param request The handled request. - * @param response The handled response. - */ - public ClientResource(Request request, Response response) { - this(Context.getCurrent(), request, response); - } - - /** - * Constructor. - * - * @param uri The target URI. - */ - public ClientResource(String uri) { - this(Context.getCurrent(), Method.GET, uri); - } - - /** - * Updates the client preferences to accept the given metadata (media types, - * character sets, etc.) with a 1.0 quality in addition to existing ones. - * - * @param metadata The metadata to accept. - * @see ClientInfo#accept(Metadata...) - */ - public void accept(Metadata... metadata) { - getClientInfo().accept(metadata); - } - - /** - * Updates the client preferences to accept the given metadata (media types, - * character sets, etc.) with a given quality in addition to existing ones. - * - * @param metadata The metadata to accept. - * @param quality The quality to set. - * @see ClientInfo#accept(Metadata, float) - */ - public void accept(Metadata metadata, float quality) { - getClientInfo().accept(metadata, quality); - } - - /** - * Adds a parameter to the query component. The name and value are automatically - * encoded if necessary. - * - * @param parameter The parameter to add. - * @return The updated reference. - * @see Reference#addQueryParameter(Parameter) - */ - public Reference addQueryParameter(Parameter parameter) { - return getReference().addQueryParameter(parameter); - } - - /** - * Adds a parameter to the query component. The name and value are automatically - * encoded if necessary. - * - * @param name The parameter name. - * @param value The optional parameter value. - * @return The updated reference. - * @see Reference#addQueryParameter(String, String) - */ - public Reference addQueryParameter(String name, String value) { - return getReference().addQueryParameter(name, value); - } - - /** - * Adds several parameters to the query component. The name and value are - * automatically encoded if necessary. - * - * @param parameters The parameters to add. - * @return The updated reference. - * @see Reference#addQueryParameters(Iterable) - */ - public Reference addQueryParameters(Iterable parameters) { - return getReference().addQueryParameters(parameters); - } - - /** - * Adds a segment at the end of the path. If the current path doesn't end with a - * slash character, one is inserted before the new segment value. The value is - * automatically encoded if necessary. - * - * @param value The segment value to add. - * @return The updated reference. - * @see Reference#addSegment(String) - */ - public Reference addSegment(String value) { - return getReference().addSegment(value); - } - - /** - * Creates a next Restlet is no one is set. By default, it creates a new - * {@link Client} based on the protocol of the resource's URI reference. - * - * @return The created next Restlet or null. - */ - protected Uniform createNext() { - Uniform result = null; - - // Prefer the outbound root - result = getApplication().getOutboundRoot(); - - if ((result == null) && (getContext() != null)) { - // Try using directly the client dispatcher - result = getContext().getClientDispatcher(); - } - - if (result == null) { - // As a final option, try creating a client connector - Protocol rProtocol = getProtocol(); - Reference rReference = getReference(); - Protocol protocol = (rProtocol != null) ? rProtocol - : (rReference != null) ? rReference.getSchemeProtocol() : null; - - if (protocol != null) { - org.restlet.engine.util.TemplateDispatcher dispatcher = new org.restlet.engine.util.TemplateDispatcher(); - dispatcher.setContext(getContext()); - dispatcher.setNext(new Client(protocol)); - result = dispatcher; - } - } - - return result; - } - - /** - * Creates a new request by cloning the one wrapped by this class. - * - * @return The new response. - * @see #getRequest() - */ - public Request createRequest() { - return new Request(getRequest()); - } - - /** - * Creates a new response for the given request. - * - * @param request The associated request. - * @return The new response. - */ - protected Response createResponse(Request request) { - return new Response(request); - } - - /** - * Deletes the target resource and all its representations. If a success status - * is not returned, then a resource exception is thrown. - * - * @return The optional response entity. - * @see HTTP - * DELETE method - */ - public Representation delete() throws ResourceException { - return handle(Method.DELETE); - } - - /** - * Deletes the target resource and all its representations. If a success status - * is not returned, then a resource exception is thrown. - * - * @param The expected type for the response entity. - * @param resultClass The expected class for the response entity object. - * @return The response entity object. - * @see HTTP - * DELETE method - */ - public T delete(Class resultClass) throws ResourceException { - return handle(Method.DELETE, resultClass); - } - - /** - * Deletes the target resource and all its representations. If a success status - * is not returned, then a resource exception is thrown. - * - * @param mediaType The media type of the representation to retrieve. - * @return The representation matching the given media type. - * @throws ResourceException - * @see HTTP - * DELETE method - */ - public Representation delete(MediaType mediaType) throws ResourceException { - return handle(Method.DELETE, mediaType); - } - - /** - * By default, it throws a new resource exception. Call - * {@link #doError(org.restlet.data.Status, org.restlet.Request, org.restlet.Response)}. - * - * @param request The associated request. - * @param response The associated response. - */ - public void doError(Request request, Response response) { - doError(response.getStatus(), request, response); - } - - /** - * By default, it throws a new resource exception. Call - * {@link #doError(org.restlet.data.Status, org.restlet.Request, org.restlet.Response)}. - * - * @param errorStatus The error status received. - */ - @Override - public void doError(Status errorStatus) { - doError(errorStatus, getRequest(), getResponse()); - } - - /** - * By default, it throws a new resource exception. This can be overridden to - * provide a different behavior. - * - * @param errorStatus The error status received. - * @param request The associated request. - * @param response The associated response. - */ - public void doError(Status errorStatus, Request request, Response response) { - throw new ResourceException(errorStatus, request, response); - } - - /** - * Releases the resource by stopping any connector automatically created and - * associated to the "next" property (see {@link #getNext()} method. - */ - @Override - protected void doRelease() throws ResourceException { - if ((getNext() != null) && this.nextCreated) { - if (getNext() instanceof Restlet) { - try { - ((Restlet) getNext()).stop(); - } catch (Exception e) { - throw new ResourceException(e); - } - } - - setNext(null); - } - } - - /** - * Attempts to {@link #release()} the resource. - */ - @Override - protected void finalize() throws Throwable { - release(); - super.finalize(); - } - - /** - * Represents the resource using content negotiation to select the best variant - * based on the client preferences. Note that the client preferences will be - * automatically adjusted, but only for this request. If you want to change them - * once for all, you can use the {@link #getClientInfo()} method.
- *
- * If a success status is not returned, then a resource exception is thrown. - * - * @return The best representation. - * @throws ResourceException - * @see HTTP GET - * method - */ - public Representation get() throws ResourceException { - return handle(Method.GET); - } - - /** - * Represents the resource in the given object class. Note that the client - * preferences will be automatically adjusted, but only for this request. If you - * want to change them once for all, you can use the {@link #getClientInfo()} - * method.
- *
- * If a success status is not returned, then a resource exception is thrown. - * - * @param The expected type for the response entity. - * @param resultClass The expected class for the response entity object. - * @return The response entity object. - * @throws ResourceException - * @see HTTP GET - * method - */ - public T get(Class resultClass) throws ResourceException { - return handle(Method.GET, resultClass); - } - - /** - * Represents the resource using a given media type. Note that the client - * preferences will be automatically adjusted, but only for this request. If you - * want to change them once for all, you can use the {@link #getClientInfo()} - * method.
- *
- * If a success status is not returned, then a resource exception is thrown. - * - * @param mediaType The media type of the representation to retrieve. - * @return The representation matching the given media type. - * @throws ResourceException - * @see HTTP GET - * method - */ - public Representation get(MediaType mediaType) throws ResourceException { - return handle(Method.GET, mediaType); - } - - /** - * Returns the attribute value by looking up the given name in the response - * attributes maps. The toString() method is then invoked on the attribute - * value. - * - * @param name The attribute name. - * @return The response attribute value. - */ - public String getAttribute(String name) { - Object value = getResponseAttributes().get(name); - return (value == null) ? null : value.toString(); - } - - /** - * Returns the child resource defined by its URI relatively to the current - * resource. The child resource is defined in the sense of hierarchical URIs. If - * the resource URI is not hierarchical, then an exception is thrown. - * - * @param relativeRef The URI reference of the child resource relatively to the - * current resource seen as the parent resource. - * @return The child resource. - * @throws ResourceException - */ - public ClientResource getChild(Reference relativeRef) throws ResourceException { - ClientResource result = null; - - if ((relativeRef != null) && relativeRef.isRelative()) { - result = new ClientResource(this); - result.setReference(new Reference(getReference().getTargetRef(), relativeRef).getTargetRef()); - } else { - doError(Status.CLIENT_ERROR_BAD_REQUEST, "The child URI is not relative."); - } - - return result; - } - - /** - * Wraps the child client resource to proxy calls to the given Java interface - * into Restlet method calls. The child resource is defined in the sense of - * hierarchical URIs. If the resource URI is not hierarchical, then an exception - * is thrown. - * - * @param The proxified interface. - * @param relativeRef The URI reference of the child resource relatively - * to the current resource seen as the parent resource. - * @param resourceInterface The annotated resource interface class to proxy. - * @return The proxy instance. - */ - public T getChild(Reference relativeRef, Class resourceInterface) throws ResourceException { - T result = null; - ClientResource childResource = getChild(relativeRef); - - if (childResource != null) { - result = childResource.wrap(resourceInterface); - } - - return result; - } - - /** - * Returns the child resource defined by its URI relatively to the current - * resource. The child resource is defined in the sense of hierarchical URIs. If - * the resource URI is not hierarchical, then an exception is thrown. - * - * @param relativeUri The URI of the child resource relatively to the current - * resource seen as the parent resource. - * @return The child resource. - * @throws ResourceException - */ - public ClientResource getChild(String relativeUri) throws ResourceException { - return getChild(new Reference(relativeUri)); - } - - /** - * Wraps the child client resource to proxy calls to the given Java interface - * into Restlet method calls. The child resource is defined in the sense of - * hierarchical URIs. If the resource URI is not hierarchical, then an exception - * is thrown. - * - * @param The proxified interface. - * @param relativeUri The URI of the child resource relatively to the - * current resource seen as the parent resource. - * @param resourceInterface The annotated resource interface class to proxy. - * @return The proxy instance. - */ - public T getChild(String relativeUri, Class resourceInterface) throws ResourceException { - return getChild(new Reference(relativeUri), resourceInterface); - } - - /** - * Returns the maximum number of redirections that can be automatically followed - * for a single call. Default value is 10. - * - * @return The maximum number of redirections that can be automatically followed - * for a single call. - */ - public int getMaxRedirects() { - return maxRedirects; - } - - /** - * Returns the next Restlet. By default, it is the client dispatcher if a - * context is available. - * - * @return The next Restlet or null. - */ - public Uniform getNext() { - Uniform result = this.next; - - if (result == null) { - synchronized (this) { - if (result == null) { - result = createNext(); - - if (result != null) { - setNext(result); - this.nextCreated = true; - } - } - } - } - - return result; - } - - /** - * Returns the callback invoked on response reception. If the value is not null, - * then the associated request will be executed asynchronously. - * - * @return The callback invoked on response reception. - */ - public Uniform getOnResponse() { - return getRequest().getOnResponse(); - } - - /** - * Returns the callback invoked after sending the request. - * - * @return The callback invoked after sending the request. - */ - public Uniform getOnSent() { - return getRequest().getOnSent(); - } - - /** - * Returns the parent resource. The parent resource is defined in the sense of - * hierarchical URIs. If the resource URI is not hierarchical, then an exception - * is thrown. - * - * @return The parent resource. - */ - public ClientResource getParent() throws ResourceException { - ClientResource result = null; - - if (getReference().isHierarchical()) { - result = new ClientResource(this); - result.setReference(getReference().getParentRef()); - } else { - doError(Status.CLIENT_ERROR_BAD_REQUEST, "The resource URI is not hierarchical."); - } - - return result; - } - - /** - * Wraps the parent client resource to proxy calls to the given Java interface - * into Restlet method calls. The parent resource is defined in the sense of - * hierarchical URIs. If the resource URI is not hierarchical, then an exception - * is thrown. - * - * @param The proxified interface. - * @param resourceInterface The annotated resource interface class to proxy. - * @return The proxy instance. - */ - public T getParent(Class resourceInterface) throws ResourceException { - T result = null; - - ClientResource parentResource = getParent(); - if (parentResource != null) { - result = parentResource.wrap(resourceInterface); - } - - return result; - } - - /** - * Returns the number of retry attempts before reporting an error. Default value - * is 2. - * - * @return The number of retry attempts before reporting an error. - */ - public int getRetryAttempts() { - return retryAttempts; - } - - /** - * Returns the delay in milliseconds between two retry attempts. Default value - * is 2 seconds. - * - * @return The delay in milliseconds between two retry attempts. - */ - public long getRetryDelay() { - return retryDelay; - } - - /** - * Handles the call by invoking the next handler. The prototype request is - * retrieved via {@link #getRequest()} and cloned and the response is set as the - * latest with {@link #setResponse(Response)}. If necessary the - * {@link #setNext(Uniform)} method is called as well with a {@link Client} - * instance matching the request protocol. - * - * @return The optional response entity. - * @see #getNext() - */ - @Override - public Representation handle() { - Response response = handleOutbound(createRequest()); - return (response == null) ? null : response.getEntity(); - } - - /** - * Handles the call by cloning the prototype request, setting the method and - * entity. - * - * @param method The request method to use. - * @return The optional response entity. - */ - protected Representation handle(Method method) { - return handle(method, (Representation) null); - } - - /** - * Handles the call by cloning the prototype request, setting the method and - * entity. - * - * @param The expected type for the response entity. - * @param method The request method to use. - * @param resultClass The expected class for the response entity object. - * @return The response entity object. - * @throws ResourceException - */ - protected T handle(Method method, Class resultClass) throws ResourceException { - return handle(method, null, resultClass); - } - - /** - * Handles the call by cloning the prototype request, setting the method and - * entity. - * - * @param method The request method to use. - * @param mediaType The preferred result media type. - * @return The optional response entity. - */ - protected Representation handle(Method method, MediaType mediaType) { - return handle(method, null, mediaType); - } - - /** - * Handles an object entity. Automatically serializes the object using the - * {@link org.restlet.service.ConverterService}. - * - * @param The expected type for the response entity. - * @param method The request method to use. - * @param entity The object entity to post. - * @param resultClass The class of the response entity. - * @return The response object entity. - * @throws ResourceException - */ - protected T handle(Method method, Object entity, Class resultClass) throws ResourceException { - org.restlet.service.ConverterService cs = getConverterService(); - ClientInfo clientInfo = getClientInfo(); - - if (clientInfo.getAcceptedMediaTypes().isEmpty()) { - cs.updatePreferences(clientInfo.getAcceptedMediaTypes(), resultClass); - } - - // Prepare the request by cloning the prototype request - Request request = createRequest(); - request.setMethod(method); - request.setClientInfo(clientInfo); - - if (entity != null) { - List entityVariants; - try { - entityVariants = cs.getVariants(entity.getClass(), null); - request.setEntity(toRepresentation(entity, - getConnegService().getPreferredVariant(entityVariants, request, getMetadataService()))); - } catch (IOException e) { - throw new ResourceException(e); - } - } else { - request.setEntity(null); - } - - // Actually handle the call - Response response = handleOutbound(request); - Representation responseEntity = handleInbound(response); - return toObject(responseEntity, resultClass); - } - - /** - * Handles the call by cloning the prototype request, setting the method and - * entity. - * - * @param method The request method to use. - * @param entity The request entity to set. - * @return The optional response entity. - */ - protected Representation handle(Method method, Representation entity) { - return handle(method, entity, getClientInfo()); - } - - /** - * Handles the call by cloning the prototype request, setting the method and - * entity. - * - * @param method The request method to use. - * @param entity The request entity to set. - * @param clientInfo The client preferences. - * @return The optional response entity. - */ - protected Representation handle(Method method, Representation entity, ClientInfo clientInfo) { - // Prepare the request by cloning the prototype request - Request request = createRequest(); - request.setMethod(method); - request.setEntity(entity); - request.setClientInfo(clientInfo); - - // Actually handle the call - Response response = handleOutbound(request); - return handleInbound(response); - } - - /** - * Handles the call by cloning the prototype request, setting the method and - * entity. - * - * @param method The request method to use. - * @param entity The request entity to set. - * @param mediaType The preferred result media type. - * @return The optional response entity. - */ - protected Representation handle(Method method, Representation entity, MediaType mediaType) { - return handle(method, entity, new ClientInfo(mediaType)); - } - - /** - * Handle the call and follow redirection for safe methods. - * - * @param request The request to send. - * @param response The response to update. - * @param references The references that caused a redirection to prevent - * infinite loops. - * @param retryAttempt The number of remaining attempts. - * @param next The next handler handling the call. - */ - protected void handle(Request request, Response response, List references, int retryAttempt, - Uniform next) { - if (next != null) { - // Check if request entity buffering must be done - if (isRequestEntityBuffering()) { - request.bufferEntity(); - } - - // Actually handle the call - next.handle(request, response); - - if (isRetryOnError() && response.getStatus().isRecoverableError() && request.getMethod().isIdempotent() - && (retryAttempt < getRetryAttempts()) - && ((request.getEntity() == null) || request.getEntity().isAvailable())) { - retry(request, response, references, retryAttempt, next); - } else if (isFollowingRedirects() && response.getStatus().isRedirection() - && (response.getLocationRef() != null)) { - boolean doRedirection = false; - - if (request.getMethod().isSafe()) { - doRedirection = true; - } else { - if (Status.REDIRECTION_SEE_OTHER.equals(response.getStatus())) { - // The user agent is redirected using the GET method - request.setMethod(Method.GET); - request.setEntity(null); - doRedirection = true; - } else if (Status.REDIRECTION_USE_PROXY.equals(response.getStatus())) { - doRedirection = true; - } - } - - if (doRedirection) { - redirect(request, response, references, retryAttempt, next); - } else { - getLogger().fine("Unable to redirect the client call after a response" + response); - } - } - - // Check if response entity buffering must be done - if (isResponseEntityBuffering()) { - response.bufferEntity(); - } - } else { - getLogger().log(Level.WARNING, "Request ignored as no next Restlet is available"); - } - } - - /** - * Handles the inbound call. Note that only synchronous calls are processed. - * - * @param response - * @return The response's entity, if any. - */ - public Representation handleInbound(Response response) { - if (response == null) { - return null; - } - - // Verify that the request was synchronous - if (response.getRequest().isSynchronous()) { - if (response.getStatus().isError()) { - doError(response.getStatus()); - return null; - } - return response.getEntity(); - } - - return null; - } - - /** - * Handles the outbound call by invoking the next handler. - * - * @param request The request to handle. - * @return The response created. - * @see #getNext() - */ - public Response handleOutbound(Request request) { - Response response = createResponse(request); - Uniform next = getNext(); - - if (next != null) { - // Effectively handle the call - handle(request, response, null, 0, next); - - // Update the last received response. - setResponse(response); - } else { - getLogger().warning("Unable to process the call for a client resource. No next Restlet has been provided."); - } - - return response; - } - - /** - * Indicates if there is a next Restlet. - * - * @return True if there is a next Restlet. - */ - public boolean hasNext() { - return getNext() != null; - } - - /** - * Represents the resource using content negotiation to select the best variant - * based on the client preferences. This method is identical to {@link #get()} - * but doesn't return the actual content of the representation, only its - * metadata.
- *
- * Note that the client preferences will be automatically adjusted, but only for - * this request. If you want to change them once for all, you can use the - * {@link #getClientInfo()} method.
- *
- * If a success status is not returned, then a resource exception is thrown. - * - * @return The best representation. - * @throws ResourceException - * @see HTTP HEAD - * method - */ - public Representation head() throws ResourceException { - return handle(Method.HEAD); - } - - /** - * Represents the resource using a given media type. This method is identical to - * {@link #get(MediaType)} but doesn't return the actual content of the - * representation, only its metadata.
- *
- * Note that the client preferences will be automatically adjusted, but only for - * this request. If you want to change them once for all, you can use the - * {@link #getClientInfo()} method.
- *
- * If a success status is not returned, then a resource exception is thrown. - * - * @param mediaType The media type of the representation to retrieve. - * @return The representation matching the given media type. - * @throws ResourceException - * @see HTTP HEAD - * method - */ - public Representation head(MediaType mediaType) throws ResourceException { - return handle(Method.HEAD, mediaType); - } - - /** - * Indicates if redirections are followed. - * - * @return True if redirections are followed. - */ - public boolean isFollowingRedirects() { - return followingRedirects; - } - - /** - * Indicates if transient or unknown size response entities should be buffered - * after being received. This is useful to increase the chance of being able to - * resubmit a failed request due to network error, or to prevent chunked - * encoding from being used an HTTP connector. - * - * @return True if transient response entities should be buffered after being - * received. - */ - public boolean isRequestEntityBuffering() { - return requestEntityBuffering; - } - - /** - * Indicates if transient or unknown size response entities should be buffered - * after being received. This is useful to be able to systematically reuse and - * process a response entity several times after retrieval. - * - * @return True if transient response entities should be buffered after being - * received. - */ - public boolean isResponseEntityBuffering() { - return responseEntityBuffering; - } - - /** - * Indicates if idempotent requests should be retried on error. Default value is - * true. - * - * @return True if idempotent requests should be retried on error. - */ - public boolean isRetryOnError() { - return retryOnError; - } - - /** - * Describes the resource using content negotiation to select the best variant - * based on the client preferences. If a success status is not returned, then a - * resource exception is thrown. - * - * @return The best description. - * @throws ResourceException - * @see HTTP - * OPTIONS method - */ - public Representation options() throws ResourceException { - return handle(Method.OPTIONS); - } - - /** - * Describes the resource using a given media type. If a success status is not - * returned, then a resource exception is thrown. - * - * @param The expected type for the response entity. - * @param resultClass The expected class for the response entity object. - * @return The response entity object. - * @throws ResourceException - * @see HTTP - * OPTIONS method - */ - public T options(Class resultClass) throws ResourceException { - return handle(Method.OPTIONS, resultClass); - } - - /** - * Describes the resource using a given media type. If a success status is not - * returned, then a resource exception is thrown. - * - * @param mediaType The media type of the representation to retrieve. - * @return The matched description or null. - * @throws ResourceException - * @see HTTP - * OPTIONS method - */ - public Representation options(MediaType mediaType) throws ResourceException { - return handle(Method.OPTIONS, mediaType); - } - - /** - * Patches a resource with the given object as delta state. Automatically - * serializes the object using the {@link org.restlet.service.ConverterService}. - * - * @param entity The object entity containing the patch. - * @return The optional result entity. - * @throws ResourceException - * @see HTTP PATCH method - */ - public Representation patch(Object entity) throws ResourceException { - try { - return patch(toRepresentation(entity)); - } catch (IOException e) { - throw new ResourceException(e); - } - } - - /** - * Patches a resource with the given object as delta state. Automatically - * serializes the object using the {@link org.restlet.service.ConverterService}. - * - * @param The expected type for the response entity. - * @param entity The object entity containing the patch. - * @param resultClass The class of the response entity. - * @return The response object entity. - * @throws ResourceException - * @see HTTP PATCH method - */ - public T patch(Object entity, Class resultClass) throws ResourceException { - return handle(Method.PATCH, entity, resultClass); - } - - /** - * Patches a resource with the given object as delta state. Automatically - * serializes the object using the {@link org.restlet.service.ConverterService}. - * - * @param entity The object entity containing the patch. - * @param mediaType The media type of the representation to retrieve. - * @return The response object entity. - * @throws ResourceException - * @see HTTP PATCH method - */ - public Representation patch(Object entity, MediaType mediaType) throws ResourceException { - try { - return handle(Method.PATCH, toRepresentation(entity), mediaType); - } catch (IOException e) { - throw new ResourceException(e); - } - } - - /** - * Patches a resource with the given representation as delta state. If a success - * status is not returned, then a resource exception is thrown. - * - * @param entity The request entity containing the patch. - * @return The optional result entity. - * @throws ResourceException - * @see HTTP PATCH method - */ - public Representation patch(Representation entity) throws ResourceException { - return handle(Method.PATCH, entity); - } - - /** - * Posts an object entity. Automatically serializes the object using the - * {@link org.restlet.service.ConverterService}. - * - * @param entity The object entity to post. - * @return The optional result entity. - * @throws ResourceException - * @see HTTP POST - * method - */ - public Representation post(Object entity) throws ResourceException { - try { - return post(toRepresentation(entity)); - } catch (IOException e) { - throw new ResourceException(e); - } - } - - /** - * Posts an object entity. Automatically serializes the object using the - * {@link org.restlet.service.ConverterService}. - * - * @param The expected type for the response entity. - * @param entity The object entity to post. - * @param resultClass The class of the response entity. - * @return The response object entity. - * @throws ResourceException - * @see HTTP POST - * method - */ - public T post(Object entity, Class resultClass) throws ResourceException { - return handle(Method.POST, entity, resultClass); - } - - /** - * Posts an object entity. Automatically serializes the object using the - * {@link org.restlet.service.ConverterService}. - * - * @param entity The object entity to post. - * @param mediaType The media type of the representation to retrieve. - * @return The response object entity. - * @throws ResourceException - * @see HTTP POST - * method - */ - public Representation post(Object entity, MediaType mediaType) throws ResourceException { - try { - return handle(Method.POST, toRepresentation(entity), mediaType); - } catch (IOException e) { - throw new ResourceException(e); - } - } - - /** - * Posts a representation. If a success status is not returned, then a resource - * exception is thrown. - * - * @param entity The posted entity. - * @return The optional result entity. - * @throws ResourceException - * @see HTTP POST - * method - */ - public Representation post(Representation entity) throws ResourceException { - return handle(Method.POST, entity); - } - - /** - * Puts an object entity. Automatically serializes the object using the - * {@link org.restlet.service.ConverterService}. - * - * @param entity The object entity to put. - * @return The optional result entity. - * @throws ResourceException - * @see HTTP PUT - * method - */ - public Representation put(Object entity) throws ResourceException { - try { - return put(toRepresentation(entity)); - } catch (IOException e) { - throw new ResourceException(e); - } - } - - /** - * Puts an object entity. Automatically serializes the object using the - * {@link org.restlet.service.ConverterService}. - * - * @param The expected type for the response entity. - * @param entity The object entity to put. - * @param resultClass The class of the response entity. - * @return The response object entity. - * @throws ResourceException - * @see HTTP PUT - * method - */ - public T put(Object entity, Class resultClass) throws ResourceException { - return handle(Method.PUT, entity, resultClass); - } - - /** - * Puts an object entity. Automatically serializes the object using the - * {@link org.restlet.service.ConverterService}. - * - * @param entity The object entity to post. - * @param mediaType The media type of the representation to retrieve. - * @return The response object entity. - * @throws ResourceException - * @see HTTP PUT - * method - */ - public Representation put(Object entity, MediaType mediaType) throws ResourceException { - try { - return handle(Method.PUT, toRepresentation(entity), mediaType); - } catch (IOException e) { - throw new ResourceException(e); - } - } - - /** - * Creates or updates a resource with the given representation as new state to - * be stored. If a success status is not returned, then a resource exception is - * thrown. - * - * @param entity The request entity to store. - * @return The optional result entity. - * @throws ResourceException - * @see HTTP PUT - * method - */ - public Representation put(Representation entity) throws ResourceException { - return handle(Method.PUT, entity); - } - - /** - * Effectively redirects a client call. By default, it checks for infinite loops - * and unavailable entities, the references list is updated and the - * {@link #handle(Request, Response, List, int, Uniform)} method invoked. - * - * @param request The request to send. - * @param response The response to update. - * @param references The references that caused a redirection to prevent - * infinite loops. - * @param retryAttempt The number of remaining attempts. - * @param next The next handler handling the call. - */ - protected void redirect(Request request, Response response, List references, int retryAttempt, - Uniform next) { - Reference newTargetRef = response.getLocationRef(); - - if ((references != null) && references.contains(newTargetRef)) { - getLogger().warning("Infinite redirection loop detected with URI: " + newTargetRef); - } else if (request.getEntity() != null && !request.isEntityAvailable()) { - getLogger().warning("Unable to follow the redirection because the request entity isn't available anymore."); - } else { - if (references == null) { - references = new ArrayList(); - } - - if (references.size() >= getMaxRedirects()) { - getLogger().warning( - "Unable to follow the redirection because the request the maximum number of redirections for a single call has been reached."); - } else { - // Add to the list of redirection reference - // to prevent infinite loops - references.add(request.getResourceRef()); - request.setResourceRef(newTargetRef); - handle(request, response, references, 0, next); - } - } - } - - /** - * Effectively retries a failed client call. By default, it sleeps before the - * retry attempt and increments the number of retries. - * - * @param request The request to send. - * @param response The response to update. - * @param references The references that caused a redirection to prevent - * infinite loops. - * @param retryAttempt The number of remaining attempts. - * @param next The next handler handling the call. - */ - protected void retry(Request request, Response response, List references, int retryAttempt, - Uniform next) { - getLogger().log(Level.INFO, "A recoverable error was detected (" + response.getStatus().getCode() - + "), attempting again in " + getRetryDelay() + " ms."); - - // Wait before attempting again - if (getRetryDelay() > 0) { - try { - Thread.sleep(getRetryDelay()); - } catch (InterruptedException e) { - getLogger().log(Level.FINE, "Retry delay sleep was interrupted", e); - // MITRE, CWE-391 - Unchecked Error Condition - Thread.currentThread().interrupt(); - } - } - - // Retry the call - handle(request, response, references, ++retryAttempt, next); - } - - /** - * Sets the request attribute value. - * - * @param name The attribute name. - * @param value The attribute to set. - */ - public void setAttribute(String name, Object value) { - getRequestAttributes().put(name, value); - } - - /** - * Sets the authentication response sent by a client to an origin server. - * - * @param challengeResponse The authentication response sent by a client to an - * origin server. - * @see Request#setChallengeResponse(ChallengeResponse) - */ - public void setChallengeResponse(ChallengeResponse challengeResponse) { - getRequest().setChallengeResponse(challengeResponse); - } - - /** - * Sets the authentication response sent by a client to an origin server given a - * scheme, identifier and secret. - * - * @param scheme The challenge scheme. - * @param identifier The user identifier, such as a login name or an access key. - * @param secret The user secret, such as a password or a secret key. - */ - public void setChallengeResponse(ChallengeScheme scheme, final String identifier, String secret) { - setChallengeResponse(new ChallengeResponse(scheme, identifier, secret)); - } - - /** - * Sets the client-specific information. - * - * @param clientInfo The client-specific information. - * @see Request#setClientInfo(ClientInfo) - */ - public void setClientInfo(ClientInfo clientInfo) { - getRequest().setClientInfo(clientInfo); - } - - /** - * Sets the conditions applying to this request. - * - * @param conditions The conditions applying to this request. - * @see Request#setConditions(Conditions) - */ - public void setConditions(Conditions conditions) { - getRequest().setConditions(conditions); - } - - /** - * Sets the cookies provided by the client. - * - * @param cookies The cookies provided by the client. - * @see Request#setCookies(Series) - */ - public void setCookies(Series cookies) { - getRequest().setCookies(cookies); - } - - /** - * Indicates if transient entities should be buffered after being received or - * before being sent. - * - * @param entityBuffering True if transient entities should be buffered. - * @see ClientResource#setRequestEntityBuffering(boolean) - * @see #setResponseEntityBuffering(boolean) - */ - public void setEntityBuffering(boolean entityBuffering) { - setRequestEntityBuffering(entityBuffering); - setResponseEntityBuffering(entityBuffering); - } - - /** - * Indicates if redirections are followed. - * - * @param followingRedirects True if redirections are followed. - */ - public void setFollowingRedirects(boolean followingRedirects) { - this.followingRedirects = followingRedirects; - } - - /** - * Sets the host reference. - * - * @param hostRef The host reference. - * @see Request#setHostRef(Reference) - */ - public void setHostRef(Reference hostRef) { - getRequest().setHostRef(hostRef); - } - - /** - * Sets the host reference using an URI string. - * - * @param hostUri The host URI. - * @see Request#setHostRef(String) - */ - public void setHostRef(String hostUri) { - getRequest().setHostRef(hostUri); - } - - /** - * Indicates if the call is loggable - * - * @param loggable True if the call is loggable - */ - public void setLoggable(boolean loggable) { - getRequest().setLoggable(loggable); - } - - /** - * Sets the maximum number of redirections that can be automatically followed - * for a single call. - * - * @param maxRedirects The maximum number of redirections that can be - * automatically followed for a single call. - */ - public void setMaxRedirects(int maxRedirects) { - this.maxRedirects = maxRedirects; - } - - /** - * Sets the method called. - * - * @param method The method called. - * @see Request#setMethod(Method) - */ - public void setMethod(Method method) { - getRequest().setMethod(method); - } - - /** - * Sets the next handler such as a Restlet or a Filter. - * - * In addition, this method will set the context of the next Restlet if it is - * null by passing a reference to its own context. - * - * @param next The next handler. - */ - public void setNext(org.restlet.Uniform next) { - if (next instanceof Restlet) { - Restlet nextRestlet = (Restlet) next; - - if (nextRestlet.getContext() == null) { - nextRestlet.setContext(getContext()); - } - } - - this.next = next; - - // If true, it must be updated after calling this method - this.nextCreated = false; - } - - /** - * Sets the callback invoked on response reception. If the value is not null, - * then the associated request will be executed asynchronously. - * - * @param onResponseCallback The callback invoked on response reception. - */ - public void setOnResponse(Uniform onResponseCallback) { - getRequest().setOnResponse(onResponseCallback); - } - - /** - * Sets the callback invoked after sending the request. - * - * @param onSentCallback The callback invoked after sending the request. - */ - public void setOnSent(Uniform onSentCallback) { - getRequest().setOnSent(onSentCallback); - } - - /** - * Sets the original reference requested by the client. - * - * @param originalRef The original reference. - * @see Request#setOriginalRef(Reference) - */ - public void setOriginalRef(Reference originalRef) { - getRequest().setOriginalRef(originalRef); - } - - /** - * Sets the protocol used or to be used. - * - * @param protocol The protocol used or to be used. - */ - public void setProtocol(Protocol protocol) { - getRequest().setProtocol(protocol); - } - - /** - * Sets the proxy authentication response sent by a client to an origin server. - * - * @param challengeResponse The proxy authentication response sent by a client - * to an origin server. - * @see Request#setProxyChallengeResponse(ChallengeResponse) - */ - public void setProxyChallengeResponse(ChallengeResponse challengeResponse) { - getRequest().setProxyChallengeResponse(challengeResponse); - } - - /** - * Sets the proxy authentication response sent by a client to an origin server - * given a scheme, identifier and secret. - * - * @param scheme The challenge scheme. - * @param identifier The user identifier, such as a login name or an access key. - * @param secret The user secret, such as a password or a secret key. - */ - public void setProxyChallengeResponse(ChallengeScheme scheme, final String identifier, String secret) { - setProxyChallengeResponse(new ChallengeResponse(scheme, identifier, secret)); - } - - /** - * Sets the ranges to return from the target resource's representation. - * - * @param ranges The ranges. - * @see Request#setRanges(List) - */ - public void setRanges(List ranges) { - getRequest().setRanges(ranges); - } - - /** - * Sets the resource's reference. If the reference is relative, it will be - * resolved as an absolute reference. Also, the context's base reference will be - * reset. Finally, the reference will be normalized to ensure a consistent - * handling of the call. - * - * @param reference The resource reference. - * @see Request#setResourceRef(Reference) - */ - public void setReference(Reference reference) { - getRequest().setResourceRef(reference); - } - - /** - * Sets the resource's reference using an URI string. Note that the URI can be - * either absolute or relative to the context's base reference. - * - * @param uri The resource URI. - * @see Request#setResourceRef(String) - */ - public void setReference(String uri) { - getRequest().setResourceRef(uri); - } - - /** - * Sets the referrer reference if available. - * - * @param referrerRef The referrer reference. - * @see Request#setReferrerRef(Reference) - */ - public void setReferrerRef(Reference referrerRef) { - getRequest().setReferrerRef(referrerRef); - } - - /** - * Sets the referrer reference if available using an URI string. - * - * @param referrerUri The referrer URI. - * @see Request#setReferrerRef(String) - */ - public void setReferrerRef(String referrerUri) { - getRequest().setReferrerRef(referrerUri); - } - - /** - * Indicates if transient or unknown size response entities should be buffered - * after being received. This is useful to increase the chance of being able to - * resubmit a failed request due to network error, or to prevent chunked - * encoding from being used an HTTP connector. - * - * @param requestEntityBuffering True if transient request entities should be - * buffered after being received. - */ - public void setRequestEntityBuffering(boolean requestEntityBuffering) { - this.requestEntityBuffering = requestEntityBuffering; - } - - /** - * Indicates if transient or unknown size response entities should be buffered - * after being received. This is useful to be able to systematically reuse and - * process a response entity several times after retrieval. - * - * @param responseEntityBuffering True if transient response entities should be - * buffered after being received. - */ - public void setResponseEntityBuffering(boolean responseEntityBuffering) { - this.responseEntityBuffering = responseEntityBuffering; - } - - /** - * Sets the number of retry attempts before reporting an error. - * - * @param retryAttempts The number of retry attempts before reporting an error. - */ - public void setRetryAttempts(int retryAttempts) { - this.retryAttempts = retryAttempts; - } - - /** - * Sets the delay in milliseconds between two retry attempts. The default value - * is two seconds. - * - * @param retryDelay The delay in milliseconds between two retry attempts. - */ - public void setRetryDelay(long retryDelay) { - this.retryDelay = retryDelay; - } - - /** - * Indicates if idempotent requests should be retried on error. - * - * @param retryOnError True if idempotent requests should be retried on error. - */ - public void setRetryOnError(boolean retryOnError) { - this.retryOnError = retryOnError; - } - - /** - * Wraps the client resource to proxy calls to the given Java interface into - * Restlet method calls. Use the {@link org.restlet.engine.Engine} classloader - * in order to generate the proxy. - * - * @param - * @param resourceInterface The annotated resource interface class to proxy. - * @return The proxy instance. - */ - public T wrap(Class resourceInterface) { - return wrap(resourceInterface, org.restlet.engine.Engine.getInstance().getClassLoader()); - } - - /** - * Wraps the client resource to proxy calls to the given Java interface into - * Restlet method calls. - * - * @param - * @param resourceInterface The annotated resource interface class to proxy. - * @param classLoader The classloader used to instantiate the dynamic - * proxy. - * @return The proxy instance. - */ - @SuppressWarnings("unchecked") - public T wrap(Class resourceInterface, ClassLoader classLoader) { - T result = null; - - // Create the client resource proxy - java.lang.reflect.InvocationHandler h = new org.restlet.engine.resource.ClientInvocationHandler(this, - resourceInterface); - - // Instantiate our dynamic proxy - result = (T) java.lang.reflect.Proxy.newProxyInstance(classLoader, - new Class[] { ClientProxy.class, resourceInterface }, h); - - return result; - } + /** + * Creates a client resource that proxy calls to the given Java interface into Restlet method + * calls. It basically creates a new instance of {@link ClientResource} and invokes the {@link + * #wrap(Class)} method. + * + * @param The proxied interface. + * @param context The context. + * @param reference The target reference. + * @param resourceInterface The annotated resource interface class to proxy. + * @return The proxy instance. + */ + public static T create( + Context context, Reference reference, Class resourceInterface) { + ClientResource clientResource = new ClientResource(context, reference); + return clientResource.wrap(resourceInterface); + } + + /** + * Creates a client resource that proxy calls to the given Java interface into Restlet method + * calls. It basically creates a new instance of {@link ClientResource} and invokes the {@link + * #wrap(Class)} method. + * + * @param The proxied interface. + * @param reference The target reference. + * @param resourceInterface The annotated resource interface class to proxy. + * @return The proxy instance. + */ + public static T create(Reference reference, Class resourceInterface) { + return create(null, reference, resourceInterface); + } + + /** + * Creates a client resource that proxy calls to the given Java interface into Restlet method + * calls. It basically creates a new instance of {@link ClientResource} and invokes the {@link + * #wrap(Class)} method. + * + * @param The proxied interface. + * @param uri The target URI. + * @param resourceInterface The annotated resource interface class to proxy. + * @return The proxy instance. + */ + public static T create(String uri, Class resourceInterface) { + return create(null, new Reference(uri), resourceInterface); + } + + /** Indicates if redirections should be automatically followed. */ + private volatile boolean followingRedirects; + + /** + * Indicates the maximum number of redirections that can be automatically followed for a single + * call. + */ + private volatile int maxRedirects; + + /** The next Restlet. */ + private volatile Uniform next; + + /** Indicates if the next Restlet has been created. */ + private volatile boolean nextCreated; + + /** + * Indicates if transient or unknown size request entities should be buffered before being sent. + */ + private volatile boolean requestEntityBuffering; + + /** + * Indicates if transient or unknown size response entities should be buffered after being + * received. + */ + private volatile boolean responseEntityBuffering; + + /** Number of retry attempts before reporting an error. */ + private volatile int retryAttempts; + + /** Delay in milliseconds between two retry attempts. */ + private volatile long retryDelay; + + /** Indicates if idempotent requests should be retried on error. */ + private volatile boolean retryOnError; + + /** Empty constructor. */ + protected ClientResource() {} + + /** + * Constructor. + * + * @param resource The client resource to copy. + */ + public ClientResource(ClientResource resource) { + Request request = new Request(resource.getRequest()); + Response response = new Response(request); + this.next = resource.getNext(); + this.maxRedirects = resource.getMaxRedirects(); + this.retryOnError = resource.isRetryOnError(); + this.retryDelay = resource.getRetryDelay(); + this.retryAttempts = resource.getRetryAttempts(); + + this.followingRedirects = resource.isFollowingRedirects(); + this.requestEntityBuffering = resource.isRequestEntityBuffering(); + this.responseEntityBuffering = resource.isResponseEntityBuffering(); + setApplication(resource.getApplication()); + + init(resource.getContext(), request, response); + } + + /** + * Constructor. + * + * @param context The context. + * @param uri The target URI. + */ + public ClientResource(Context context, java.net.URI uri) { + this(context, Method.GET, uri); + } + + /** + * Constructor. + * + * @param context The context. + * @param method The method to call. + * @param uri The target URI. + */ + public ClientResource(Context context, Method method, java.net.URI uri) { + this(context, method, new Reference(uri)); + } + + /** + * Constructor. + * + * @param context The context. + * @param method The method to call. + * @param reference The target reference. + */ + public ClientResource(Context context, Method method, Reference reference) { + this(context, new Request(method, reference), new Response(null)); + } + + /** + * Constructor. + * + * @param context The context. + * @param method The method to call. + * @param uri The target URI. + */ + public ClientResource(Context context, Method method, String uri) { + this(context, method, new Reference(uri)); + } + + /** + * Constructor. + * + * @param context The context. + * @param reference The target reference. + */ + public ClientResource(Context context, Reference reference) { + this(context, Method.GET, reference); + } + + /** + * Constructor. + * + * @param context The current context. + * @param request The handled request. + */ + public ClientResource(Context context, Request request) { + this(context, request, null); + } + + /** + * Constructor. + * + * @param context The current context. + * @param request The handled request. + * @param response The handled response. + */ + public ClientResource(Context context, Request request, Response response) { + if (context == null) { + context = Context.getCurrent(); + } + + // Don't remove this line. + // See the constructor ClientResource(Context, Method, Reference) + response.setRequest(request); + + this.maxRedirects = 10; + this.retryOnError = true; + this.retryDelay = 2000L; + this.retryAttempts = 2; + this.followingRedirects = true; + this.requestEntityBuffering = false; + this.responseEntityBuffering = false; + init(context, request, response); + } + + /** + * Constructor. + * + * @param context The context. + * @param uri The target URI. + */ + public ClientResource(Context context, String uri) { + this(context, Method.GET, uri); + } + + /** + * Constructor. + * + * @param uri The target URI. + */ + public ClientResource(java.net.URI uri) { + this(Context.getCurrent(), null, uri); + } + + /** + * Constructor. + * + * @param method The method to call. + * @param uri The target URI. + */ + public ClientResource(Method method, java.net.URI uri) { + this(Context.getCurrent(), method, uri); + } + + /** + * Constructor. + * + * @param method The method to call. + * @param reference The target reference. + */ + public ClientResource(Method method, Reference reference) { + this(Context.getCurrent(), method, reference); + } + + /** + * Constructor. + * + * @param method The method to call. + * @param uri The target URI. + */ + public ClientResource(Method method, String uri) { + this(Context.getCurrent(), method, uri); + } + + /** + * Constructor. + * + * @param reference The target reference. + */ + public ClientResource(Reference reference) { + this(Context.getCurrent(), null, reference); + } + + /** + * Constructor. + * + * @param request The handled request. + */ + public ClientResource(Request request) { + this(request, new Response(request)); + } + + /** + * Constructor. + * + * @param request The handled request. + * @param response The handled response. + */ + public ClientResource(Request request, Response response) { + this(Context.getCurrent(), request, response); + } + + /** + * Constructor. + * + * @param uri The target URI. + */ + public ClientResource(String uri) { + this(Context.getCurrent(), Method.GET, uri); + } + + /** + * Updates the client preferences to accept the given metadata (media types, character sets, + * etc.) with a 1.0 quality in addition to existing ones. + * + * @param metadata The metadata to accept. + * @see ClientInfo#accept(Metadata...) + */ + public void accept(Metadata... metadata) { + getClientInfo().accept(metadata); + } + + /** + * Updates the client preferences to accept the given metadata (media types, character sets, + * etc.) with a given quality in addition to existing ones. + * + * @param metadata The metadata to accept. + * @param quality The quality to set. + * @see ClientInfo#accept(Metadata, float) + */ + public void accept(Metadata metadata, float quality) { + getClientInfo().accept(metadata, quality); + } + + /** + * Adds a parameter to the query component. The name and value are automatically encoded if + * necessary. + * + * @param parameter The parameter to add. + * @return The updated reference. + * @see Reference#addQueryParameter(Parameter) + */ + public Reference addQueryParameter(Parameter parameter) { + return getReference().addQueryParameter(parameter); + } + + /** + * Adds a parameter to the query component. The name and value are automatically encoded if + * necessary. + * + * @param name The parameter name. + * @param value The optional parameter value. + * @return The updated reference. + * @see Reference#addQueryParameter(String, String) + */ + public Reference addQueryParameter(String name, String value) { + return getReference().addQueryParameter(name, value); + } + + /** + * Adds several parameters to the query component. The name and value are automatically encoded + * if necessary. + * + * @param parameters The parameters to add. + * @return The updated reference. + * @see Reference#addQueryParameters(Iterable) + */ + public Reference addQueryParameters(Iterable parameters) { + return getReference().addQueryParameters(parameters); + } + + /** + * Adds a segment at the end of the path. If the current path doesn't end with a slash + * character, one is inserted before the new segment value. The value is automatically encoded + * if necessary. + * + * @param value The segment value to add. + * @return The updated reference. + * @see Reference#addSegment(String) + */ + public Reference addSegment(String value) { + return getReference().addSegment(value); + } + + /** + * Creates a next Restlet is no one is set. By default, it creates a new {@link Client} based on + * the protocol of the resource's URI reference. + * + * @return The created next Restlet or null. + */ + protected Uniform createNext() { + Uniform result = null; + + // Prefer the outbound root + result = getApplication().getOutboundRoot(); + + if ((result == null) && (getContext() != null)) { + // Try using directly the client dispatcher + result = getContext().getClientDispatcher(); + } + + if (result == null) { + // As a final option, try creating a client connector + Protocol rProtocol = getProtocol(); + Reference rReference = getReference(); + Protocol protocol = + (rProtocol != null) + ? rProtocol + : (rReference != null) ? rReference.getSchemeProtocol() : null; + + if (protocol != null) { + org.restlet.engine.util.TemplateDispatcher dispatcher = + new org.restlet.engine.util.TemplateDispatcher(); + dispatcher.setContext(getContext()); + dispatcher.setNext(new Client(protocol)); + result = dispatcher; + } + } + + return result; + } + + /** + * Creates a new request by cloning the one wrapped by this class. + * + * @return The new response. + * @see #getRequest() + */ + public Request createRequest() { + return new Request(getRequest()); + } + + /** + * Creates a new response for the given request. + * + * @param request The associated request. + * @return The new response. + */ + protected Response createResponse(Request request) { + return new Response(request); + } + + /** + * Deletes the target resource and all its representations. If a success status is not returned, + * then a resource exception is thrown. + * + * @return The optional response entity. + * @see HTTP DELETE + * method + */ + public Representation delete() throws ResourceException { + return handle(Method.DELETE); + } + + /** + * Deletes the target resource and all its representations. If a success status is not returned, + * then a resource exception is thrown. + * + * @param The expected type for the response entity. + * @param resultClass The expected class for the response entity object. + * @return The response entity object. + * @see HTTP DELETE + * method + */ + public T delete(Class resultClass) throws ResourceException { + return handle(Method.DELETE, resultClass); + } + + /** + * Deletes the target resource and all its representations. If a success status is not returned, + * then a resource exception is thrown. + * + * @param mediaType The media type of the representation to retrieve. + * @return The representation matching the given media type. + * @throws ResourceException + * @see HTTP DELETE + * method + */ + public Representation delete(MediaType mediaType) throws ResourceException { + return handle(Method.DELETE, mediaType); + } + + /** + * By default, it throws a new resource exception. Call {@link #doError(org.restlet.data.Status, + * org.restlet.Request, org.restlet.Response)}. + * + * @param request The associated request. + * @param response The associated response. + */ + public void doError(Request request, Response response) { + doError(response.getStatus(), request, response); + } + + /** + * By default, it throws a new resource exception. Call {@link #doError(org.restlet.data.Status, + * org.restlet.Request, org.restlet.Response)}. + * + * @param errorStatus The error status received. + */ + @Override + public void doError(Status errorStatus) { + doError(errorStatus, getRequest(), getResponse()); + } + + /** + * By default, it throws a new resource exception. This can be overridden to provide a different + * behavior. + * + * @param errorStatus The error status received. + * @param request The associated request. + * @param response The associated response. + */ + public void doError(Status errorStatus, Request request, Response response) { + throw new ResourceException(errorStatus, request, response); + } + + /** + * Releases the resource by stopping any connector automatically created and associated with the + * "next" property (see {@link #getNext()} method. + */ + @Override + protected void doRelease() throws ResourceException { + if ((getNext() != null) && this.nextCreated) { + if (getNext() instanceof Restlet restlet) { + try { + restlet.stop(); + } catch (Exception e) { + throw new ResourceException(e); + } + } + + setNext(null); + } + } + + /** Attempts to {@link #release()} the resource. */ + @Override + protected void finalize() throws Throwable { + release(); + super.finalize(); + } + + /** + * Represents the resource using content negotiation to select the best variant based on the + * client preferences. Note that the client preferences will be automatically adjusted, but only + * for this request. If you want to change them once for all, you can use the {@link + * #getClientInfo()} method.
+ *
+ * If a success status is not returned, then a resource exception is thrown. + * + * @return The best representation. + * @throws ResourceException + * @see HTTP GET + * method + */ + public Representation get() throws ResourceException { + return handle(Method.GET); + } + + /** + * Represents the resource in the given object class. Note that the client preferences will be + * automatically adjusted, but only for this request. If you want to change them once for all, + * you can use the {@link #getClientInfo()} method.
+ *
+ * If a success status is not returned, then a resource exception is thrown. + * + * @param The expected type for the response entity. + * @param resultClass The expected class for the response entity object. + * @return The response entity object. + * @throws ResourceException + * @see HTTP GET + * method + */ + public T get(Class resultClass) throws ResourceException { + return handle(Method.GET, resultClass); + } + + /** + * Represents the resource using a given media type. Note that the client preferences will be + * automatically adjusted, but only for this request. If you want to change them once for all, + * you can use the {@link #getClientInfo()} method.
+ *
+ * If a success status is not returned, then a resource exception is thrown. + * + * @param mediaType The media type of the representation to retrieve. + * @return The representation matching the given media type. + * @throws ResourceException + * @see HTTP GET + * method + */ + public Representation get(MediaType mediaType) throws ResourceException { + return handle(Method.GET, mediaType); + } + + /** + * Returns the attribute value by looking up the given name in the response attributes maps. The + * toString() method is then invoked on the attribute value. + * + * @param name The attribute name. + * @return The response attribute value. + */ + public String getAttribute(String name) { + Object value = getResponseAttributes().get(name); + return (value == null) ? null : value.toString(); + } + + /** + * Returns the child resource defined by its URI relatively to the current resource. The child + * resource is defined in the sense of hierarchical URIs. If the resource URI is not + * hierarchical, then an exception is thrown. + * + * @param relativeRef The URI reference of the child resource relatively to the current resource + * seen as the parent resource. + * @return The child resource. + * @throws ResourceException + */ + public ClientResource getChild(Reference relativeRef) throws ResourceException { + ClientResource result = null; + + if ((relativeRef != null) && relativeRef.isRelative()) { + result = new ClientResource(this); + result.setReference( + new Reference(getReference().getTargetRef(), relativeRef).getTargetRef()); + } else { + doError(Status.CLIENT_ERROR_BAD_REQUEST, "The child URI is not relative."); + } + + return result; + } + + /** + * Wraps the child client resource to proxy call to the given Java interface into Restlet method + * calls. The child resource is defined in the sense of hierarchical URIs. If the resource URI + * is not hierarchical, then an exception is thrown. + * + * @param The proxied interface. + * @param relativeRef The URI reference of the child resource relatively to the current resource + * seen as the parent resource. + * @param resourceInterface The annotated resource interface class to proxy. + * @return The proxy instance. + */ + public T getChild(Reference relativeRef, Class resourceInterface) + throws ResourceException { + T result = null; + ClientResource childResource = getChild(relativeRef); + + if (childResource != null) { + result = childResource.wrap(resourceInterface); + } + + return result; + } + + /** + * Returns the child resource defined by its URI relatively to the current resource. The child + * resource is defined in the sense of hierarchical URIs. If the resource URI is not + * hierarchical, then an exception is thrown. + * + * @param relativeUri The URI of the child resource relatively to the current resource seen as + * the parent resource. + * @return The child resource. + * @throws ResourceException + */ + public ClientResource getChild(String relativeUri) throws ResourceException { + return getChild(new Reference(relativeUri)); + } + + /** + * Wraps the child client resource to proxy call to the given Java interface into Restlet method + * calls. The child resource is defined in the sense of hierarchical URIs. If the resource URI + * is not hierarchical, then an exception is thrown. + * + * @param The proxied interface. + * @param relativeUri The URI of the child resource relatively to the current resource seen as + * the parent resource. + * @param resourceInterface The annotated resource interface class to proxy. + * @return The proxy instance. + */ + public T getChild(String relativeUri, Class resourceInterface) + throws ResourceException { + return getChild(new Reference(relativeUri), resourceInterface); + } + + /** + * Returns the maximum number of redirections that can be automatically followed for a single + * call. The default value is 10. + * + * @return The maximum number of redirections that can be automatically followed for a single + * call. + */ + public int getMaxRedirects() { + return maxRedirects; + } + + /** + * Returns the next Restlet. By default, it is the client dispatcher if a context is available. + * + * @return The next Restlet or null. + */ + public Uniform getNext() { + Uniform result = this.next; + + if (result == null) { + synchronized (this) { + if (result == null) { + result = createNext(); + + if (result != null) { + setNext(result); + this.nextCreated = true; + } + } + } + } + + return result; + } + + /** + * Returns the callback invoked on response reception. If the value is not null, then the + * associated request will be executed asynchronously. + * + * @return The callback invoked on response reception. + */ + public Uniform getOnResponse() { + return getRequest().getOnResponse(); + } + + /** + * Returns the callback invoked after sending the request. + * + * @return The callback invoked after sending the request. + */ + public Uniform getOnSent() { + return getRequest().getOnSent(); + } + + /** + * Returns the parent resource. The parent resource is defined in the sense of hierarchical + * URIs. If the resource URI is not hierarchical, then an exception is thrown. + * + * @return The parent resource. + */ + public ClientResource getParent() throws ResourceException { + ClientResource result = null; + + if (getReference().isHierarchical()) { + result = new ClientResource(this); + result.setReference(getReference().getParentRef()); + } else { + doError(Status.CLIENT_ERROR_BAD_REQUEST, "The resource URI is not hierarchical."); + } + + return result; + } + + /** + * Wraps the parent client resource to proxy call to the given Java interface into Restlet + * method calls. The parent resource is defined in the sense of hierarchical URIs. If the + * resource URI is not hierarchical, then an exception is thrown. + * + * @param The proxied interface. + * @param resourceInterface The annotated resource interface class to proxy. + * @return The proxy instance. + */ + public T getParent(Class resourceInterface) throws ResourceException { + T result = null; + + ClientResource parentResource = getParent(); + if (parentResource != null) { + result = parentResource.wrap(resourceInterface); + } + + return result; + } + + /** + * Returns the number of retry attempts before reporting an error. The default value is 2. + * + * @return The number of retry attempts before reporting an error. + */ + public int getRetryAttempts() { + return retryAttempts; + } + + /** + * Returns the delay in milliseconds between two retry attempts. The default value is 2 seconds. + * + * @return The delay in milliseconds between two retry attempts. + */ + public long getRetryDelay() { + return retryDelay; + } + + /** + * Handles the call by invoking the next handler. The prototype request is retrieved via {@link + * #getRequest()} and cloned and the response is set as the latest with {@link + * #setResponse(Response)}. If necessary the {@link #setNext(Uniform)} method is called as well + * with a {@link Client} instance matching the request protocol. + * + * @return The optional response entity. + * @see #getNext() + */ + @Override + public Representation handle() { + Response response = handleOutbound(createRequest()); + return (response == null) ? null : response.getEntity(); + } + + /** + * Handles the call by cloning the prototype request, setting the method and entity. + * + * @param method The request method to use. + * @return The optional response entity. + */ + protected Representation handle(Method method) { + return handle(method, (Representation) null); + } + + /** + * Handles the call by cloning the prototype request, setting the method and entity. + * + * @param The expected type for the response entity. + * @param method The request method to use. + * @param resultClass The expected class for the response entity object. + * @return The response entity object. + * @throws ResourceException + */ + protected T handle(Method method, Class resultClass) throws ResourceException { + return handle(method, null, resultClass); + } + + /** + * Handles the call by cloning the prototype request, setting the method and entity. + * + * @param method The request method to use. + * @param mediaType The preferred result media type. + * @return The optional response entity. + */ + protected Representation handle(Method method, MediaType mediaType) { + return handle(method, null, mediaType); + } + + /** + * Handles an object entity. Automatically serializes the object using the {@link + * org.restlet.service.ConverterService}. + * + * @param The expected type for the response entity. + * @param method The request method to use. + * @param entity The object entity to post. + * @param resultClass The class of the response entity. + * @return The response object entity. + * @throws ResourceException + */ + protected T handle(Method method, Object entity, Class resultClass) + throws ResourceException { + org.restlet.service.ConverterService cs = getConverterService(); + ClientInfo clientInfo = getClientInfo(); + + if (clientInfo.getAcceptedMediaTypes().isEmpty()) { + cs.updatePreferences(clientInfo.getAcceptedMediaTypes(), resultClass); + } + + // Prepare the request by cloning the prototype request + Request request = createRequest(); + request.setMethod(method); + request.setClientInfo(clientInfo); + + if (entity != null) { + List entityVariants; + try { + entityVariants = cs.getVariants(entity.getClass(), null); + request.setEntity( + toRepresentation( + entity, + getConnegService() + .getPreferredVariant( + entityVariants, request, getMetadataService()))); + } catch (IOException e) { + throw new ResourceException(e); + } + } else { + request.setEntity(null); + } + + // Actually handle the call + Response response = handleOutbound(request); + Representation responseEntity = handleInbound(response); + return toObject(responseEntity, resultClass); + } + + /** + * Handles the call by cloning the prototype request, setting the method and entity. + * + * @param method The request method to use. + * @param entity The request entity to set. + * @return The optional response entity. + */ + protected Representation handle(Method method, Representation entity) { + return handle(method, entity, getClientInfo()); + } + + /** + * Handles the call by cloning the prototype request, setting the method and entity. + * + * @param method The request method to use. + * @param entity The request entity to set. + * @param clientInfo The client preferences. + * @return The optional response entity. + */ + protected Representation handle(Method method, Representation entity, ClientInfo clientInfo) { + // Prepare the request by cloning the prototype request + Request request = createRequest(); + request.setMethod(method); + request.setEntity(entity); + request.setClientInfo(clientInfo); + + // Actually handle the call + Response response = handleOutbound(request); + return handleInbound(response); + } + + /** + * Handles the call by cloning the prototype request, setting the method and entity. + * + * @param method The request method to use. + * @param entity The request entity to set. + * @param mediaType The preferred result media type. + * @return The optional response entity. + */ + protected Representation handle(Method method, Representation entity, MediaType mediaType) { + return handle(method, entity, new ClientInfo(mediaType)); + } + + /** + * Handle the call and follow redirection for safe methods. + * + * @param request The request to send. + * @param response The response to update. + * @param references The references that caused a redirection to prevent infinite loops. + * @param retryAttempt The number of remaining attempts. + * @param next The next handler handling the call. + */ + protected void handle( + Request request, + Response response, + List references, + int retryAttempt, + Uniform next) { + if (next != null) { + // Check if request entity buffering must be done + if (isRequestEntityBuffering()) { + request.bufferEntity(); + } + + // Actually handle the call + next.handle(request, response); + + if (isRetryOnError() + && response.getStatus().isRecoverableError() + && request.getMethod().isIdempotent() + && (retryAttempt < getRetryAttempts()) + && ((request.getEntity() == null) || request.getEntity().isAvailable())) { + retry(request, response, references, retryAttempt, next); + } else if (isFollowingRedirects() + && response.getStatus().isRedirection() + && (response.getLocationRef() != null)) { + boolean doRedirection = false; + + if (request.getMethod().isSafe()) { + doRedirection = true; + } else { + if (Status.REDIRECTION_SEE_OTHER.equals(response.getStatus())) { + // The user agent is redirected using the GET method + request.setMethod(Method.GET); + request.setEntity(null); + doRedirection = true; + } else if (Status.REDIRECTION_USE_PROXY.equals(response.getStatus())) { + doRedirection = true; + } + } + + if (doRedirection) { + redirect(request, response, references, retryAttempt, next); + } else { + getLogger() + .fine( + () -> + "Unable to redirect the client call after a response" + + response); + } + } + + // Check if response entity buffering must be done + if (isResponseEntityBuffering()) { + response.bufferEntity(); + } + } else { + getLogger().log(Level.WARNING, "Request ignored as no next Restlet is available"); + } + } + + /** + * Handles the inbound call. Note that only synchronous calls are processed. + * + * @param response + * @return The response's entity, if any. + */ + public Representation handleInbound(Response response) { + if (response == null) { + return null; + } + + // Verify that the request was synchronous + if (response.getRequest().isSynchronous()) { + if (response.getStatus().isError()) { + doError(response.getStatus()); + return null; + } + return response.getEntity(); + } + + return null; + } + + /** + * Handles the outbound call by invoking the next handler. + * + * @param request The request to handle. + * @return The response created. + * @see #getNext() + */ + public Response handleOutbound(Request request) { + final Response response = createResponse(request); + final Uniform nextUniform = getNext(); + + if (nextUniform != null) { + // Effectively handle the call + handle(request, response, null, 0, nextUniform); + + // Update the last received response. + setResponse(response); + } else { + getLogger() + .warning( + "Unable to process the call for a client resource. No next Restlet has been provided."); + } + + return response; + } + + /** + * Indicates if there is a next Restlet. + * + * @return True if there is a next Restlet. + */ + public boolean hasNext() { + return getNext() != null; + } + + /** + * Represents the resource using content negotiation to select the best variant based on the + * client preferences. This method is identical to {@link #get()} but doesn't return the actual + * content of the representation, only its metadata.
+ *
+ * Note that the client preferences will be automatically adjusted, but only for this request. + * If you want to change them once for all, you can use the {@link #getClientInfo()} method.
+ *
+ * If a success status is not returned, then a resource exception is thrown. + * + * @return The best representation. + * @throws ResourceException + * @see HTTP HEAD + * method + */ + public Representation head() throws ResourceException { + return handle(Method.HEAD); + } + + /** + * Represents the resource using a given media type. This method is identical to {@link + * #get(MediaType)} but doesn't return the actual content of the representation, only its + * metadata.
+ *
+ * Note that the client preferences will be automatically adjusted, but only for this request. + * If you want to change them once for all, you can use the {@link #getClientInfo()} method.
+ *
+ * If a success status is not returned, then a resource exception is thrown. + * + * @param mediaType The media type of the representation to retrieve. + * @return The representation matching the given media type. + * @throws ResourceException + * @see HTTP HEAD + * method + */ + public Representation head(MediaType mediaType) throws ResourceException { + return handle(Method.HEAD, mediaType); + } + + /** + * Indicates if redirections are followed. + * + * @return True if redirections are followed. + */ + public boolean isFollowingRedirects() { + return followingRedirects; + } + + /** + * Indicates if transient or unknown size response entities should be buffered after being + * received. This is useful to increase the chance of being able to resubmit a failed request + * due to network error, or to prevent chunked encoding from being used an HTTP connector. + * + * @return True if transient response entities should be buffered after being received. + */ + public boolean isRequestEntityBuffering() { + return requestEntityBuffering; + } + + /** + * Indicates if transient or unknown size response entities should be buffered after being + * received. This is useful to be able to systematically reuse and process a response entity + * several times after retrieval. + * + * @return True if transient response entities should be buffered after being received. + */ + public boolean isResponseEntityBuffering() { + return responseEntityBuffering; + } + + /** + * Indicates if idempotent requests should be retried on error. Default value is true. + * + * @return True if idempotent requests should be retried on error. + */ + public boolean isRetryOnError() { + return retryOnError; + } + + /** + * Describes the resource using content negotiation to select the best variant based on the + * client preferences. If a success status is not returned, then a resource exception is thrown. + * + * @return The best description. + * @throws ResourceException + * @see HTTP OPTIONS + * method + */ + public Representation options() throws ResourceException { + return handle(Method.OPTIONS); + } + + /** + * Describes the resource using a given media type. If a success status is not returned, then a + * resource exception is thrown. + * + * @param The expected type for the response entity. + * @param resultClass The expected class for the response entity object. + * @return The response entity object. + * @throws ResourceException + * @see HTTP OPTIONS + * method + */ + public T options(Class resultClass) throws ResourceException { + return handle(Method.OPTIONS, resultClass); + } + + /** + * Describes the resource using a given media type. If a success status is not returned, then a + * resource exception is thrown. + * + * @param mediaType The media type of the representation to retrieve. + * @return The matched description or null. + * @throws ResourceException + * @see HTTP OPTIONS + * method + */ + public Representation options(MediaType mediaType) throws ResourceException { + return handle(Method.OPTIONS, mediaType); + } + + /** + * Patches a resource with the given object as a delta state. Automatically serializes the + * object using the {@link org.restlet.service.ConverterService}. + * + * @param entity The object entity containing the patch. + * @return The optional result entity. + * @throws ResourceException + * @see HTTP PATCH method + */ + public Representation patch(Object entity) throws ResourceException { + try { + return patch(toRepresentation(entity)); + } catch (IOException e) { + throw new ResourceException(e); + } + } + + /** + * Patches a resource with the given object as a delta state. Automatically serializes the + * object using the {@link org.restlet.service.ConverterService}. + * + * @param The expected type for the response entity. + * @param entity The object entity containing the patch. + * @param resultClass The class of the response entity. + * @return The response object entity. + * @throws ResourceException + * @see HTTP PATCH method + */ + public T patch(Object entity, Class resultClass) throws ResourceException { + return handle(Method.PATCH, entity, resultClass); + } + + /** + * Patches a resource with the given object as delta state. Automatically serializes the object + * using the {@link org.restlet.service.ConverterService}. + * + * @param entity The object entity containing the patch. + * @param mediaType The media type of the representation to retrieve. + * @return The response object entity. + * @throws ResourceException + * @see HTTP PATCH method + */ + public Representation patch(Object entity, MediaType mediaType) throws ResourceException { + try { + return handle(Method.PATCH, toRepresentation(entity), mediaType); + } catch (IOException e) { + throw new ResourceException(e); + } + } + + /** + * Patches a resource with the given representation as delta state. If a success status is not + * returned, then a resource exception is thrown. + * + * @param entity The request entity containing the patch. + * @return The optional result entity. + * @throws ResourceException + * @see HTTP PATCH method + */ + public Representation patch(Representation entity) throws ResourceException { + return handle(Method.PATCH, entity); + } + + /** + * Posts an object entity. Automatically serializes the object using the {@link + * org.restlet.service.ConverterService}. + * + * @param entity The object entity to post. + * @return The optional result entity. + * @throws ResourceException + * @see HTTP POST + * method + */ + public Representation post(Object entity) throws ResourceException { + try { + return post(toRepresentation(entity)); + } catch (IOException e) { + throw new ResourceException(e); + } + } + + /** + * Posts an object entity. Automatically serializes the object using the {@link + * org.restlet.service.ConverterService}. + * + * @param The expected type for the response entity. + * @param entity The object entity to post. + * @param resultClass The class of the response entity. + * @return The response object entity. + * @throws ResourceException + * @see HTTP POST + * method + */ + public T post(Object entity, Class resultClass) throws ResourceException { + return handle(Method.POST, entity, resultClass); + } + + /** + * Posts an object entity. Automatically serializes the object using the {@link + * org.restlet.service.ConverterService}. + * + * @param entity The object entity to post. + * @param mediaType The media type of the representation to retrieve. + * @return The response object entity. + * @throws ResourceException + * @see HTTP POST + * method + */ + public Representation post(Object entity, MediaType mediaType) throws ResourceException { + try { + return handle(Method.POST, toRepresentation(entity), mediaType); + } catch (IOException e) { + throw new ResourceException(e); + } + } + + /** + * Posts a representation. If a success status is not returned, then a resource exception is + * thrown. + * + * @param entity The posted entity. + * @return The optional result entity. + * @throws ResourceException + * @see HTTP POST + * method + */ + public Representation post(Representation entity) throws ResourceException { + return handle(Method.POST, entity); + } + + /** + * Puts an object entity. Automatically serializes the object using the {@link + * org.restlet.service.ConverterService}. + * + * @param entity The object entity to put. + * @return The optional result entity. + * @throws ResourceException + * @see HTTP PUT + * method + */ + public Representation put(Object entity) throws ResourceException { + try { + return put(toRepresentation(entity)); + } catch (IOException e) { + throw new ResourceException(e); + } + } + + /** + * Puts an object entity. Automatically serializes the object using the {@link + * org.restlet.service.ConverterService}. + * + * @param The expected type for the response entity. + * @param entity The object entity to put. + * @param resultClass The class of the response entity. + * @return The response object entity. + * @throws ResourceException + * @see HTTP PUT + * method + */ + public T put(Object entity, Class resultClass) throws ResourceException { + return handle(Method.PUT, entity, resultClass); + } + + /** + * Puts an object entity. Automatically serializes the object using the {@link + * org.restlet.service.ConverterService}. + * + * @param entity The object entity to post. + * @param mediaType The media type of the representation to retrieve. + * @return The response object entity. + * @throws ResourceException + * @see HTTP PUT + * method + */ + public Representation put(Object entity, MediaType mediaType) throws ResourceException { + try { + return handle(Method.PUT, toRepresentation(entity), mediaType); + } catch (IOException e) { + throw new ResourceException(e); + } + } + + /** + * Creates or updates a resource with the given representation as new state to be stored. If a + * success status is not returned, then a resource exception is thrown. + * + * @param entity The request entity to store. + * @return The optional result entity. + * @throws ResourceException + * @see HTTP PUT + * method + */ + public Representation put(Representation entity) throws ResourceException { + return handle(Method.PUT, entity); + } + + /** + * Effectively redirects a client call. By default, it checks for infinite loops and unavailable + * entities, the references list is updated and the {@link #handle(Request, Response, List, int, + * Uniform)} method invoked. + * + * @param request The request to send. + * @param response The response to update. + * @param references The references that caused a redirection to prevent infinite loops. + * @param retryAttempt The number of remaining attempts. + * @param next The next handler handling the call. + */ + protected void redirect( + Request request, + Response response, + List references, + int retryAttempt, + Uniform next) { + Reference newTargetRef = response.getLocationRef(); + + if ((references != null) && references.contains(newTargetRef)) { + getLogger() + .warning(() -> "Infinite redirection loop detected with URI: " + newTargetRef); + } else if (request.getEntity() != null && !request.isEntityAvailable()) { + getLogger() + .warning( + "Unable to follow the redirection because the request entity isn't available anymore."); + } else { + if (references == null) { + references = new ArrayList<>(); + } + + if (references.size() >= getMaxRedirects()) { + getLogger() + .warning( + "Unable to follow the redirection because the request the maximum number of redirections for a single call has been reached."); + } else { + // Add to the list of redirection reference + // to prevent infinite loops + references.add(request.getResourceRef()); + request.setResourceRef(newTargetRef); + handle(request, response, references, 0, next); + } + } + } + + /** + * Effectively retries a failed client call. By default, it sleeps before the retry attempt and + * increments the number of retries. + * + * @param request The request to send. + * @param response The response to update. + * @param references The references that caused a redirection to prevent infinite loops. + * @param retryAttempt The number of remaining attempts. + * @param next The next handler handling the call. + */ + protected void retry( + Request request, + Response response, + List references, + int retryAttempt, + Uniform next) { + getLogger() + .info( + () -> + "A recoverable error was detected (" + + response.getStatus().getCode() + + "), attempting again in " + + getRetryDelay() + + " ms."); + + // Wait before attempting again + if (getRetryDelay() > 0) { + try { + Thread.sleep(getRetryDelay()); + } catch (InterruptedException e) { + getLogger().log(Level.FINE, "Retry delay sleep was interrupted", e); + // MITRE, CWE-391 - Unchecked Error Condition + Thread.currentThread().interrupt(); + } + } + + // Retry the call + handle(request, response, references, ++retryAttempt, next); + } + + /** + * Sets the request attribute value. + * + * @param name The attribute name. + * @param value The attribute to set. + */ + public void setAttribute(String name, Object value) { + getRequestAttributes().put(name, value); + } + + /** + * Sets the authentication response sent by a client to an origin server. + * + * @param challengeResponse The authentication response sent by a client to an origin server. + * @see Request#setChallengeResponse(ChallengeResponse) + */ + public void setChallengeResponse(ChallengeResponse challengeResponse) { + getRequest().setChallengeResponse(challengeResponse); + } + + /** + * Sets the authentication response sent by a client to an origin server given a scheme, + * identifier, and secret. + * + * @param scheme The challenge scheme. + * @param identifier The user identifier, such as a login name or an access key. + * @param secret The user secret, such as a password or a secret key. + */ + public void setChallengeResponse( + ChallengeScheme scheme, final String identifier, String secret) { + setChallengeResponse(new ChallengeResponse(scheme, identifier, secret)); + } + + /** + * Sets the client-specific information. + * + * @param clientInfo The client-specific information. + * @see Request#setClientInfo(ClientInfo) + */ + public void setClientInfo(ClientInfo clientInfo) { + getRequest().setClientInfo(clientInfo); + } + + /** + * Sets the conditions applying to this request. + * + * @param conditions The conditions applying to this request. + * @see Request#setConditions(Conditions) + */ + public void setConditions(Conditions conditions) { + getRequest().setConditions(conditions); + } + + /** + * Sets the cookies provided by the client. + * + * @param cookies The cookies provided by the client. + * @see Request#setCookies(Series) + */ + public void setCookies(Series cookies) { + getRequest().setCookies(cookies); + } + + /** + * Indicates if transient entities should be buffered after being received or before being sent. + * + * @param entityBuffering True if transient entities should be buffered. + * @see ClientResource#setRequestEntityBuffering(boolean) + * @see #setResponseEntityBuffering(boolean) + */ + public void setEntityBuffering(boolean entityBuffering) { + setRequestEntityBuffering(entityBuffering); + setResponseEntityBuffering(entityBuffering); + } + + /** + * Indicates if redirections are followed. + * + * @param followingRedirects True if redirections are followed. + */ + public void setFollowingRedirects(boolean followingRedirects) { + this.followingRedirects = followingRedirects; + } + + /** + * Sets the host reference. + * + * @param hostRef The host reference. + * @see Request#setHostRef(Reference) + */ + public void setHostRef(Reference hostRef) { + getRequest().setHostRef(hostRef); + } + + /** + * Sets the host reference using a URI string. + * + * @param hostUri The host URI. + * @see Request#setHostRef(String) + */ + public void setHostRef(String hostUri) { + getRequest().setHostRef(hostUri); + } + + /** + * Indicates if the call is loggable + * + * @param loggable True if the call is loggable + */ + public void setLoggable(boolean loggable) { + getRequest().setLoggable(loggable); + } + + /** + * Sets the maximum number of redirections that can be automatically followed for a single call. + * + * @param maxRedirects The maximum number of redirections that can be automatically followed for + * a single call. + */ + public void setMaxRedirects(int maxRedirects) { + this.maxRedirects = maxRedirects; + } + + /** + * Sets the method called. + * + * @param method The method called. + * @see Request#setMethod(Method) + */ + public void setMethod(Method method) { + getRequest().setMethod(method); + } + + /** + * Sets the next handler such as a Restlet or a Filter. + * + *

In addition, this method will set the context of the next Restlet if it is null by passing + * a reference to its own context. + * + * @param next The next handler. + */ + public void setNext(org.restlet.Uniform next) { + if (next instanceof final Restlet nextRestlet && nextRestlet.getContext() == null) { + nextRestlet.setContext(getContext()); + } + + this.next = next; + + // If true, it must be updated after calling this method + this.nextCreated = false; + } + + /** + * Sets the callback invoked on response reception. If the value is not null, then the + * associated request will be executed asynchronously. + * + * @param onResponseCallback The callback invoked on response reception. + */ + public void setOnResponse(Uniform onResponseCallback) { + getRequest().setOnResponse(onResponseCallback); + } + + /** + * Sets the callback invoked after sending the request. + * + * @param onSentCallback The callback invoked after sending the request. + */ + public void setOnSent(Uniform onSentCallback) { + getRequest().setOnSent(onSentCallback); + } + + /** + * Sets the original reference requested by the client. + * + * @param originalRef The original reference. + * @see Request#setOriginalRef(Reference) + */ + public void setOriginalRef(Reference originalRef) { + getRequest().setOriginalRef(originalRef); + } + + /** + * Sets the protocol used or to be used. + * + * @param protocol The protocol used or to be used. + */ + public void setProtocol(Protocol protocol) { + getRequest().setProtocol(protocol); + } + + /** + * Sets the proxy authentication response sent by a client to an origin server. + * + * @param challengeResponse The proxy authentication response sent by a client to an origin + * server. + * @see Request#setProxyChallengeResponse(ChallengeResponse) + */ + public void setProxyChallengeResponse(ChallengeResponse challengeResponse) { + getRequest().setProxyChallengeResponse(challengeResponse); + } + + /** + * Sets the proxy authentication response sent by a client to an origin server given a scheme, + * identifier, and secret. + * + * @param scheme The challenge scheme. + * @param identifier The user identifier, such as a login name or an access key. + * @param secret The user secret, such as a password or a secret key. + */ + public void setProxyChallengeResponse( + ChallengeScheme scheme, final String identifier, String secret) { + setProxyChallengeResponse(new ChallengeResponse(scheme, identifier, secret)); + } + + /** + * Sets the ranges to return from the target resource's representation. + * + * @param ranges The ranges. + * @see Request#setRanges(List) + */ + public void setRanges(List ranges) { + getRequest().setRanges(ranges); + } + + /** + * Sets the resource's reference. If the reference is relative, it will be resolved as an + * absolute reference. Also, the context's base reference will be reset. Finally, the reference + * will be normalized to ensure a consistent handling of the call. + * + * @param reference The resource reference. + * @see Request#setResourceRef(Reference) + */ + public void setReference(Reference reference) { + getRequest().setResourceRef(reference); + } + + /** + * Sets the resource's reference using a URI string. Note that the URI can be either absolute or + * relative to the context's base reference. + * + * @param uri The resource URI. + * @see Request#setResourceRef(String) + */ + public void setReference(String uri) { + getRequest().setResourceRef(uri); + } + + /** + * Sets the referrer reference if available. + * + * @param referrerRef The referrer reference. + * @see Request#setReferrerRef(Reference) + */ + public void setReferrerRef(Reference referrerRef) { + getRequest().setReferrerRef(referrerRef); + } + + /** + * Sets the referrer reference if available using a URI string. + * + * @param referrerUri The referrer URI. + * @see Request#setReferrerRef(String) + */ + public void setReferrerRef(String referrerUri) { + getRequest().setReferrerRef(referrerUri); + } + + /** + * Indicates if transient or unknown size response entities should be buffered after being + * received. This is useful to increase the chance of being able to resubmit a failed request + * due to network error, or to prevent chunked encoding from being used an HTTP connector. + * + * @param requestEntityBuffering True if transient request entities should be buffered after + * being received. + */ + public void setRequestEntityBuffering(boolean requestEntityBuffering) { + this.requestEntityBuffering = requestEntityBuffering; + } + + /** + * Indicates if transient or unknown size response entities should be buffered after being + * received. This is useful to be able to systematically reuse and process a response entity + * several times after retrieval. + * + * @param responseEntityBuffering True if transient response entities should be buffered after + * being received. + */ + public void setResponseEntityBuffering(boolean responseEntityBuffering) { + this.responseEntityBuffering = responseEntityBuffering; + } + + /** + * Sets the number of retry attempts before reporting an error. + * + * @param retryAttempts The number of retry attempts before reporting an error. + */ + public void setRetryAttempts(int retryAttempts) { + this.retryAttempts = retryAttempts; + } + + /** + * Sets the delay in milliseconds between two retry attempts. The default value is two seconds. + * + * @param retryDelay The delay in milliseconds between two retry attempts. + */ + public void setRetryDelay(long retryDelay) { + this.retryDelay = retryDelay; + } + + /** + * Indicates if idempotent requests should be retried on error. + * + * @param retryOnError True if idempotent requests should be retried on error. + */ + public void setRetryOnError(boolean retryOnError) { + this.retryOnError = retryOnError; + } + + /** + * Wraps the client resource to proxy calls to the given Java interface into Restlet method + * calls. Use the {@link org.restlet.engine.Engine} classloader to generate the proxy. + * + * @param + * @param resourceInterface The annotated resource interface class to proxy. + * @return The proxy instance. + */ + public T wrap(Class resourceInterface) { + return wrap(resourceInterface, org.restlet.engine.Engine.getInstance().getClassLoader()); + } + + /** + * Wraps the client resource to proxy calls to the given Java interface into Restlet method + * calls. + * + * @param + * @param resourceInterface The annotated resource interface class to proxy. + * @param classLoader The classloader used to instantiate the dynamic proxy. + * @return The proxy instance. + */ + @SuppressWarnings("unchecked") + public T wrap(Class resourceInterface, ClassLoader classLoader) { + final T result; + + // Create the client resource proxy + java.lang.reflect.InvocationHandler h = + new org.restlet.engine.resource.ClientInvocationHandler(this, resourceInterface); + + // Instantiate our dynamic proxy + result = + (T) + java.lang.reflect.Proxy.newProxyInstance( + classLoader, + new Class[] {ClientProxy.class, resourceInterface}, + h); + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/resource/Delete.java b/org.restlet/src/main/java/org/restlet/resource/Delete.java index 63870f41cc..b591cfc2f8 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Delete.java +++ b/org.restlet/src/main/java/org/restlet/resource/Delete.java @@ -1,42 +1,44 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.restlet.engine.connector.Method; import org.restlet.service.MetadataService; -import java.lang.annotation.*; - /** - * Annotation for methods that remove representations. Its semantics is - * equivalent to an HTTP DELETE method.
+ * Annotation for methods that remove representations. Its semantics are equivalent to an HTTP + * DELETE method.
*
* Example: - * + * *

  * @Delete()
  * public void removeAll();
- * 
+ *
  * @Delete("xml|json")
  * public Representation removeAll();
- * 
+ *
  * @Delete("json?param=val")
  * public Representation removeAllWithParam();
- * 
+ *
  * @Delete("json?param")
  * public Representation removeAllWithParam();
- * 
+ *
  * @Delete("?param")
  * public Representation removeAllWithParam();
  * 
- * + * * @author Jerome Louvel */ @Documented @@ -45,18 +47,15 @@ @Method("DELETE") public @interface Delete { - /** - * Specifies the media type extension of the response entity. If several media - * types are supported, their extension can be specified separated by "|" - * characters. Note that this isn't the full MIME type value, just the extension - * name declared in {@link MetadataService}. For a list of all predefined - * extensions, please check {@link MetadataService#addCommonExtensions()}. New - * extension can be registered using - * {@link MetadataService#addExtension(String, org.restlet.data.Metadata)} - * method. - * - * @return The result media types. - */ - String value() default ""; - + /** + * Specifies the media type extension of the response entity. If several media types are + * supported, their extension can be specified separated by "|" characters. Note that this isn't + * the full MIME type value, just the extension name declared in {@link MetadataService}. For a + * list of all predefined extensions, please check {@link + * MetadataService#addCommonExtensions()}. New extension can be registered using {@link + * MetadataService#addExtension(String, org.restlet.data.Metadata)} method. + * + * @return The result media types. + */ + String value() default ""; } diff --git a/org.restlet/src/main/java/org/restlet/resource/Directory.java b/org.restlet/src/main/java/org/restlet/resource/Directory.java index 6411a01d85..1bc87ae12e 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Directory.java +++ b/org.restlet/src/main/java/org/restlet/resource/Directory.java @@ -1,14 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -21,325 +23,299 @@ import org.restlet.representation.Representation; import org.restlet.representation.Variant; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - /** - * Finder mapping a directory of local resources. Those resources have - * representations accessed by the file system, the class loaders or other URI - * accessible protocols. Here is some sample code illustrating how to attach a - * directory to a router:
- * + * Finder mapping a directory of local resources. Those resources have representations accessed by + * the file system, the class loaders, or other URI-accessible protocols. Here is some sample code + * illustrating how to attach a directory to a router:
+ * *

  * Directory directory = new Directory(getContext(), "file:///user/data/files/");
  * Router router = new Router(getContext());
  * router.attach("/static/", directory);
  * 
- * - * An automatic content negotiation mechanism (similar to the one in Apache HTTP - * server) is used to select the best representation of a resource based on the - * available variants and on the client capabilities and preferences.
+ * + * An automatic content negotiation mechanism (similar to the one in Apache HTTP Server) is used to + * select the best representation of a resource based on the available variants and on the client + * capabilities and preferences.
*
- * The directory can be used in read-only or modifiable mode. In the latter - * case, you just need to set the "modifiable" property to true. The currently - * supported methods are PUT and DELETE.
+ * The directory can be used in read-only or modifiable mode. In the latter case, you need to set + * the "modifiable" property to true. The currently supported methods are PUT and DELETE.
*
- * When no index is available in a given directory, a representation can be - * automatically generated by the - * {@link #getIndexRepresentation(Variant, ReferenceList)} method, unless the - * "listingAllowed" property is turned off. You can even customize the way the - * index entries are sorted by using the {@link #setComparator(Comparator)} - * method. The default sorting uses the friendly Alphanum algorithm based on - * David Koelle's original - * idea, using a different and faster implementation contributed by Rob - * Heittman.
+ * When no index is available in a given directory, a representation can be automatically generated + * by the {@link #getIndexRepresentation(Variant, ReferenceList)} method, unless the + * "listingAllowed" property is turned off. You can even customize the way the index entries are + * sorted by using the {@link #setComparator(Comparator)} method. The default sorting uses the + * friendly Alphanum algorithm based on David Koelle's original idea, using a different and faster + * implementation contributed by Rob Heittman.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class Directory extends Finder { - /** The reference comparator to sort index pages. */ - private volatile Comparator comparator; - - /** - * Indicates if the subdirectories are deeply accessible (true by default). - */ - private volatile boolean deeplyAccessible; - - /** The index name, without extensions (ex: "index" or "home"). */ - private volatile String indexName; - - /** - * Indicates if the display of directory listings is allowed when no index file - * is found. - */ - private volatile boolean listingAllowed; - - /** - * Indicates if modifications to local resources are allowed (false by default). - */ - private volatile boolean modifiable; - - /** Indicates if the best content is automatically negotiated. */ - private volatile boolean negotiatingContent; - - /** The absolute root reference (file, clap URI). */ - private volatile Reference rootRef; - - /** - * Constructor. - * - * @param context The context. - * @param rootLocalReference The root URI. - */ - public Directory(Context context, Reference rootLocalReference) { - super(context); - - // First, let's normalize the root reference to prevent any issue with - // relative paths inside the reference leading to listing issues. - final String rootIdentifier = rootLocalReference.getTargetRef().getIdentifier(); - - if (rootIdentifier.endsWith("/")) { - this.rootRef = new Reference(rootIdentifier); - } else { - // We don't take the risk of exposing directory "file:///C:/AA" - // if only "file:///C:/A" was intended - this.rootRef = new Reference(rootIdentifier + "/"); - } - - this.comparator = new AlphaNumericComparator(); - this.deeplyAccessible = true; - this.indexName = "index"; - this.listingAllowed = false; - this.modifiable = false; - this.negotiatingContent = true; - setTargetClass(DirectoryServerResource.class); - setName("Directory"); - } - - /** - * Constructor. - * - * @param context The context. - * @param rootUri The absolute root URI.
- *
- * If you serve files from the file system, use file:// URIs and - * make sure that you register a FILE connector with your parent - * Component. On Windows, make sure that you add enough slash - * characters at the beginning, for example: - * file:///c:/dir/file
- *
- * If you serve files from a class loader, use clap:// URIs and - * make sure that you register a CLAP connector with your parent - * Component.
- *
- */ - public Directory(Context context, String rootUri) { - this(context, new Reference(rootUri)); - } - - /** - * Returns the reference comparator used to sort index pages. The default - * implementation used a friendly alphanum sorting. - * - * @return The reference comparator. - * @see AlphaNumericComparator - */ - public Comparator getComparator() { - return this.comparator; - } - - /** - * Returns the index name, without extensions. Returns "index" by default. - * - * @return The index name. - */ - public String getIndexName() { - return this.indexName; - } - - /** - * Returns an actual index representation for a given variant. - * - * @param variant The selected variant. - * @param indexContent The directory index to represent. - * @return The actual index representation. - */ - public Representation getIndexRepresentation(Variant variant, ReferenceList indexContent) { - Representation result = null; - if (variant.getMediaType().equals(MediaType.TEXT_HTML)) { - result = indexContent.getWebRepresentation(); - } else if (variant.getMediaType().equals(MediaType.TEXT_URI_LIST)) { - result = indexContent.getTextRepresentation(); - } - return result; - } - - /** - * Returns the variant representations of a directory index. This method can be - * subclassed to provide alternative representations. - * - * By default, it returns a simple HTML document and a textual URI list as - * variants. Note that a new instance of the list is created for each call. - * - * @param indexContent The list of references contained in the directory index. - * @return The variant representations of a directory. - */ - public List getIndexVariants(ReferenceList indexContent) { - final List result = new ArrayList<>(); - result.add(new Variant(MediaType.TEXT_HTML)); - result.add(new Variant(MediaType.TEXT_URI_LIST)); - return result; - } - - /** - * Returns the root URI from which the relative resource URIs will be looked up. - * - * @return The root URI. - */ - public Reference getRootRef() { - return this.rootRef; - } - - @Override - public void handle(Request request, Response response) { - request.getAttributes().put("org.restlet.directory", this); - super.handle(request, response); - } - - /** - * Indicates if the subdirectories are deeply accessible (true by default). - * - * @return True if the subdirectories are deeply accessible. - */ - public boolean isDeeplyAccessible() { - return this.deeplyAccessible; - } - - /** - * Indicates if the display of directory listings is allowed when no index file - * is found. - * - * @return True if the display of directory listings is allowed when no index - * file is found. - */ - public boolean isListingAllowed() { - return this.listingAllowed; - } - - /** - * Indicates if modifications to local resources (most likely files) are - * allowed. Returns false by default. - * - * @return True if modifications to local resources are allowed. - */ - public boolean isModifiable() { - return this.modifiable; - } - - /** - * Indicates if the best content is automatically negotiated. The default value is - * true. - * - * @return True if the best content is automatically negotiated. - */ - public boolean isNegotiatingContent() { - return this.negotiatingContent; - } - - /** - * Sets the reference comparator used to sort index pages. - * - * @param comparator The reference comparator. - */ - public void setComparator(Comparator comparator) { - this.comparator = comparator; - } - - /** - * Indicates if the subdirectories are deeply accessible (true by default). - * - * @param deeplyAccessible True if the subdirectories are deeply accessible. - */ - public void setDeeplyAccessible(boolean deeplyAccessible) { - this.deeplyAccessible = deeplyAccessible; - } - - /** - * Sets the index name, without extensions. - * - * @param indexName The index name. - */ - public void setIndexName(String indexName) { - this.indexName = indexName; - } - - /** - * Indicates if the display of directory listings is allowed when no index file - * is found. - * - * @param listingAllowed True if the display of directory listings is allowed - * when no index file is found. - */ - public void setListingAllowed(boolean listingAllowed) { - this.listingAllowed = listingAllowed; - } - - /** - * Indicates if modifications to local resources are allowed. - * - * @param modifiable True if modifications to local resources are allowed. - */ - public void setModifiable(boolean modifiable) { - this.modifiable = modifiable; - } - - /** - * Indicates if the best content is automatically negotiated. Default value is - * true. - * - * @param negotiatingContent True if the best content is automatically - * negotiated. - */ - public void setNegotiatingContent(boolean negotiatingContent) { - this.negotiatingContent = negotiatingContent; - } - - /** - * Sets the root URI from which the relative resource URIs will be lookep up. - * - * @param rootRef The root URI. - */ - public void setRootRef(Reference rootRef) { - this.rootRef = rootRef; - } - - /** - * Sets the reference comparator based on classic alphabetical order. - * - * @see #setComparator(Comparator) - */ - public void useAlphaComparator() { - setComparator(new AlphabeticalComparator()); - } - - /** - * Sets the reference comparator based on the more friendly "Alphanum Algorithm" - * created by David Koelle. The internal implementation used is based on an - * optimized public domain implementation provided by Rob Heittman from the - * Solertium Corporation. - * - * @see The original Alphanum - * Algorithm from David Koelle - * @see #setComparator(Comparator) - */ - public void useAlphaNumComparator() { - setComparator(new AlphabeticalComparator()); - } - + /** The reference comparator to sort index pages. */ + private volatile Comparator comparator; + + /** Indicates if the subdirectories are deeply accessible (true by default). */ + private volatile boolean deeplyAccessible; + + /** The index name, without extensions (ex: "index" or "home"). */ + private volatile String indexName; + + /** Indicates if the display of directory listings is allowed when no index file is found. */ + private volatile boolean listingAllowed; + + /** Indicates if modifications to local resources are allowed (false by default). */ + private volatile boolean modifiable; + + /** Indicates if the best content is automatically negotiated. */ + private volatile boolean negotiatingContent; + + /** The absolute root reference (file, clap URI). */ + private volatile Reference rootRef; + + /** + * Constructor. + * + * @param context The context. + * @param rootLocalReference The root URI. + */ + public Directory(Context context, Reference rootLocalReference) { + super(context); + + // First, let's normalize the root reference to prevent any issue with + // relative paths inside the reference leading to listing issues. + final String rootIdentifier = rootLocalReference.getTargetRef().getIdentifier(); + + if (rootIdentifier.endsWith("/")) { + this.rootRef = new Reference(rootIdentifier); + } else { + // We don't take the risk of exposing directory "file:///C:/AA" + // if only "file:///C:/A" was intended + this.rootRef = new Reference(rootIdentifier + "/"); + } + + this.comparator = new AlphaNumericComparator(); + this.deeplyAccessible = true; + this.indexName = "index"; + this.listingAllowed = false; + this.modifiable = false; + this.negotiatingContent = true; + setTargetClass(DirectoryServerResource.class); + setName("Directory"); + } + + /** + * Constructor. + * + * @param context The context. + * @param rootUri The absolute root URI.
+ *
+ * If you serve files from the file system, use file:// URIs and make sure that you register + * a FILE connector with your parent Component. On Windows, make sure that you add enough + * slash characters at the beginning, for example: file:///c:/dir/file
+ *
+ * If you serve files from a class loader, use clap:// URIs and make sure that you register + * a CLAP connector with your parent Component.
+ *
+ */ + public Directory(Context context, String rootUri) { + this(context, new Reference(rootUri)); + } + + /** + * Returns the reference comparator used to sort index pages. The default implementation used a + * friendly alphanum sorting. + * + * @return The reference comparator. + * @see AlphaNumericComparator + */ + public Comparator getComparator() { + return this.comparator; + } + + /** + * Returns the index name, without extensions. Returns "index" by default. + * + * @return The index name. + */ + public String getIndexName() { + return this.indexName; + } + + /** + * Returns an actual index representation for a given variant. + * + * @param variant The selected variant. + * @param indexContent The directory index to represent. + * @return The actual index representation. + */ + public Representation getIndexRepresentation(Variant variant, ReferenceList indexContent) { + Representation result = null; + if (variant.getMediaType().equals(MediaType.TEXT_HTML)) { + result = indexContent.getWebRepresentation(); + } else if (variant.getMediaType().equals(MediaType.TEXT_URI_LIST)) { + result = indexContent.getTextRepresentation(); + } + return result; + } + + /** + * Returns the variant representations of a directory index. This method can be subclassed to + * provide alternative representations. + * + *

By default, it returns a simple HTML document and a textual URI list as variants. Note + * that a new instance of the list is created for each call. + * + * @param indexContent The list of references contained in the directory index. + * @return The variant representations of a directory. + */ + public List getIndexVariants(ReferenceList indexContent) { + final List result = new ArrayList<>(); + result.add(new Variant(MediaType.TEXT_HTML)); + result.add(new Variant(MediaType.TEXT_URI_LIST)); + return result; + } + + /** + * Returns the root URI from which the relative resource URIs will be looked up. + * + * @return The root URI. + */ + public Reference getRootRef() { + return this.rootRef; + } + + @Override + public void handle(Request request, Response response) { + request.getAttributes().put("org.restlet.directory", this); + super.handle(request, response); + } + + /** + * Indicates if the subdirectories are deeply accessible (true by default). + * + * @return True if the subdirectories are deeply accessible. + */ + public boolean isDeeplyAccessible() { + return this.deeplyAccessible; + } + + /** + * Indicates if the display of directory listings is allowed when no index file is found. + * + * @return True if the display of directory listings is allowed when no index file is found. + */ + public boolean isListingAllowed() { + return this.listingAllowed; + } + + /** + * Indicates if modifications to local resources (most likely files) are allowed. Returns false + * by default. + * + * @return True if modifications to local resources are allowed. + */ + public boolean isModifiable() { + return this.modifiable; + } + + /** + * Indicates if the best content is automatically negotiated. The default value is true. + * + * @return True if the best content is automatically negotiated. + */ + public boolean isNegotiatingContent() { + return this.negotiatingContent; + } + + /** + * Sets the reference comparator used to sort index pages. + * + * @param comparator The reference comparator. + */ + public void setComparator(Comparator comparator) { + this.comparator = comparator; + } + + /** + * Indicates if the subdirectories are deeply accessible (true by default). + * + * @param deeplyAccessible True if the subdirectories are deeply accessible. + */ + public void setDeeplyAccessible(boolean deeplyAccessible) { + this.deeplyAccessible = deeplyAccessible; + } + + /** + * Sets the index name, without extensions. + * + * @param indexName The index name. + */ + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + /** + * Indicates if the display of directory listings is allowed when no index file is found. + * + * @param listingAllowed True if the display of directory listings is allowed when no index file + * is found. + */ + public void setListingAllowed(boolean listingAllowed) { + this.listingAllowed = listingAllowed; + } + + /** + * Indicates if modifications to local resources are allowed. + * + * @param modifiable True if modifications to local resources are allowed. + */ + public void setModifiable(boolean modifiable) { + this.modifiable = modifiable; + } + + /** + * Indicates if the best content is automatically negotiated. Default value is true. + * + * @param negotiatingContent True if the best content is automatically negotiated. + */ + public void setNegotiatingContent(boolean negotiatingContent) { + this.negotiatingContent = negotiatingContent; + } + + /** + * Sets the root URI from which the relative resource URIs will be looked up. + * + * @param rootRef The root URI. + */ + public void setRootRef(Reference rootRef) { + this.rootRef = rootRef; + } + + /** + * Sets the reference comparator based on classic alphabetical order. + * + * @see #setComparator(Comparator) + */ + public void useAlphaComparator() { + setComparator(new AlphabeticalComparator()); + } + + /** + * Sets the reference comparator based on the more friendly "Alphanum Algorithm" created by + * David Koelle. The internal implementation used is based on an optimized public domain + * implementation provided by Rob Heittman from the Solertium Corporation. + * + * @see The original Alphanum Algorithm from + * David Koelle + * @see #setComparator(Comparator) + */ + public void useAlphaNumComparator() { + setComparator(new AlphabeticalComparator()); + } } diff --git a/org.restlet/src/main/java/org/restlet/resource/Finder.java b/org.restlet/src/main/java/org/restlet/resource/Finder.java index aae04a5b71..62d605911f 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Finder.java +++ b/org.restlet/src/main/java/org/restlet/resource/Finder.java @@ -1,229 +1,229 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import java.lang.reflect.Constructor; +import java.util.logging.Level; +import java.util.logging.Logger; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.data.Status; -import java.lang.reflect.Constructor; -import java.util.logging.Level; -import java.util.logging.Logger; - /** - * Restlet that can find the target server resource that will effectively handle - * incoming calls. By default, based on a given {@link ServerResource} subclass - * available via the {@link #getTargetClass()} method, it automatically - * instantiates for each incoming call the target resource class using its - * default constructor and invoking the - * {@link ServerResource#init(Context, Request, Response)} method.
+ * Restlet that can find the target server resource that will effectively handle incoming calls. By + * default, based on a given {@link ServerResource} subclass available via the {@link + * #getTargetClass()} method, it automatically instantiates for each incoming call the target + * resource class using its default constructor and invoking the {@link ServerResource#init(Context, + * Request, Response)} method.
*
- * Once the target has been created, the call is automatically dispatched to the - * {@link ServerResource#handle()} method.
+ * Once the target has been created, the call is automatically dispatched to the {@link + * ServerResource#handle()} method.
*
- * Once the call is handled, the {@link ServerResource#release()} method is - * invoked to permit clean-up actions.
+ * Once the call is handled, the {@link ServerResource#release()} method is invoked to permit + * cleanup actions.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class Finder extends Restlet { - /** - * Creates a new finder instance based on the "targetClass" property. - * - * @param targetClass The target Resource class to attach. - * @param finderClass The optional finder class to instantiate. - * @param context The current Context. - * @param logger The logger. - * @return The new finder instance. - */ - public static Finder createFinder(Class targetClass, Class finderClass, - Context context, Logger logger) { - Finder result = null; - - if (finderClass != null) { - try { - Constructor constructor = finderClass.getConstructor(Context.class, Class.class); - - if (constructor != null) { - result = constructor.newInstance(context, targetClass); - } - } catch (Exception e) { - if (logger != null) { - logger.log(Level.WARNING, "Exception while instantiating the finder.", e); - } - } - } else { - result = new Finder(context, targetClass); - } - - return result; - } - - /** Target {@link ServerResource} subclass. */ - private volatile Class targetClass; - - /** - * Constructor. - */ - public Finder() { - this(null); - } - - /** - * Constructor. - * - * @param context The context. - */ - public Finder(Context context) { - super(context); - this.targetClass = null; - } - - /** - * Constructor. - * - * @param context The context. - * @param targetClass The target {@link ServerResource} subclass. - */ - public Finder(Context context, Class targetClass) { - super(context); - this.targetClass = targetClass; - } - - /** - * Creates a new instance of a given {@link ServerResource} subclass. Note that - * {@link Error} and {@link RuntimeException} thrown by {@link ServerResource} - * constructors are re-thrown by this method. Other exception are caught and - * logged. - * - * @param targetClass The target {@link ServerResource} subclass. - * @param request The request to handle. - * @param response The response to update. - * @return The created resource or null. - */ - public ServerResource create(Class targetClass, Request request, Response response) { - ServerResource result = null; - - if (targetClass != null) { - try { - // Invoke the default constructor - result = targetClass.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - getLogger().log(Level.WARNING, "Exception while instantiating the target server resource.", e); - } - } - - return result; - } - - /** - * Creates a new instance of the {@link ServerResource} subclass designated by - * the "targetClass" property. The default behavior is to invoke the - * {@link #create(Class, Request, Response)} with the "targetClass" property as - * a parameter. - * - * @param request The request to handle. - * @param response The response to update. - * @return The created resource or null. - */ - public ServerResource create(Request request, Response response) { - ServerResource result = null; - - if (getTargetClass() != null) { - result = create(getTargetClass(), request, response); - } - - return result; - } - - /** - * Finds the target {@link ServerResource} if available. The default behavior is - * to invoke the {@link #create(Request, Response)} method. - * - * @param request The request to handle. - * @param response The response to update. - * @return The target resource if available or null. - */ - public ServerResource find(Request request, Response response) { - return create(request, response); - } - - /** - * Returns the target resource class which must be either a subclass of - * {@link ServerResource}. - * - * @return the target Handler class. - */ - public Class getTargetClass() { - return this.targetClass; - } - - /** - * Handles a call. - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - public void handle(Request request, Response response) { - super.handle(request, response); - - if (isStarted()) { - ServerResource targetResource = find(request, response); - - if (targetResource == null) { - // If the current status is a success, but we couldn't - // find the target resource for the request's URI, - // then we set the response status to 404 (Not Found). - if (getLogger().isLoggable(Level.WARNING)) { - getLogger().warning("No target resource was defined for this finder: " + this); - } - - response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); - } else { - targetResource.init(getContext(), request, response); - - if ((response == null) || response.getStatus().isSuccess()) { - targetResource.handle(); - } else { - // Probably during the instantiation of the target - // server resource, or earlier the status was - // changed from the default one. Don't go further. - } - - targetResource.release(); - } - } - } - - /** - * Sets the target resource class which must be a subclass of - * {@link ServerResource}. - * - * @param targetClass The target resource class. It must be a subclass of - * {@link ServerResource}. - */ - public void setTargetClass(Class targetClass) { - this.targetClass = targetClass; - } - - @Override - public String toString() { - return getTargetClass() == null ? "Finder with no target class" - : "Finder for " + getTargetClass().getSimpleName(); - } - + /** + * Creates a new finder instance based on the "targetClass" property. + * + * @param targetClass The target Resource class to attach. + * @param finderClass The optional finder class to instantiate. + * @param context The current Context. + * @param logger The logger. + * @return The new finder instance. + */ + public static Finder createFinder( + Class targetClass, + Class finderClass, + Context context, + Logger logger) { + Finder result = null; + + if (finderClass != null) { + try { + Constructor constructor = + finderClass.getConstructor(Context.class, Class.class); + + if (constructor != null) { + result = constructor.newInstance(context, targetClass); + } + } catch (Exception e) { + if (logger != null) { + logger.log(Level.WARNING, "Exception while instantiating the finder.", e); + } + } + } else { + result = new Finder(context, targetClass); + } + + return result; + } + + /** Target {@link ServerResource} subclass. */ + private volatile Class targetClass; + + /** Constructor. */ + public Finder() { + this(null); + } + + /** + * Constructor. + * + * @param context The context. + */ + public Finder(Context context) { + super(context); + this.targetClass = null; + } + + /** + * Constructor. + * + * @param context The context. + * @param targetClass The target {@link ServerResource} subclass. + */ + public Finder(Context context, Class targetClass) { + super(context); + this.targetClass = targetClass; + } + + /** + * Creates a new instance of a given {@link ServerResource} subclass. Note that {@link Error} + * and {@link RuntimeException} thrown by {@link ServerResource} constructors are re-thrown by + * this method. Other exception is caught and logged. + * + * @param targetClass The target {@link ServerResource} subclass. + * @param request The request to handle. + * @param response The response to update. + * @return The created resource or null. + */ + public ServerResource create( + Class targetClass, Request request, Response response) { + ServerResource result = null; + + if (targetClass != null) { + try { + // Invoke the default constructor + result = targetClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + getLogger() + .log( + Level.WARNING, + "Exception while instantiating the target server resource.", + e); + } + } + + return result; + } + + /** + * Creates a new instance of the {@link ServerResource} subclass designated by the "targetClass" + * property. The default behavior is to invoke the {@link #create(Class, Request, Response)} + * with the "targetClass" property as a parameter. + * + * @param request The request to handle. + * @param response The response to update. + * @return The created resource or null. + */ + public ServerResource create(Request request, Response response) { + ServerResource result = null; + + if (getTargetClass() != null) { + result = create(getTargetClass(), request, response); + } + + return result; + } + + /** + * Finds the target {@link ServerResource} if available. The default behavior is to invoke the + * {@link #create(Request, Response)} method. + * + * @param request The request to handle. + * @param response The response to update. + * @return The target resource if available or null. + */ + public ServerResource find(Request request, Response response) { + return create(request, response); + } + + /** + * Returns the target resource class which must be either a subclass of {@link ServerResource}. + * + * @return the target Handler class. + */ + public Class getTargetClass() { + return this.targetClass; + } + + /** + * Handles a call. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public void handle(Request request, Response response) { + super.handle(request, response); + + if (isStarted()) { + ServerResource targetResource = find(request, response); + + if (targetResource == null) { + // If the current status is a success, but we couldn't + // find the target resource for the request's URI, + // then we set the response status to 404 (Not Found). + if (getLogger().isLoggable(Level.WARNING)) { + getLogger().warning("No target resource was defined for this finder: " + this); + } + + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + } else { + targetResource.init(getContext(), request, response); + + if ((response == null) || response.getStatus().isSuccess()) { + targetResource.handle(); + } else { + // Probably during the instantiation of the target + // server resource, or earlier, the status was + // changed from the default one. Don't go further. + } + + targetResource.release(); + } + } + } + + /** + * Sets the target resource class which must be a subclass of {@link ServerResource}. + * + * @param targetClass The target resource class. It must be a subclass of {@link + * ServerResource}. + */ + public void setTargetClass(Class targetClass) { + this.targetClass = targetClass; + } + + @Override + public String toString() { + return getTargetClass() == null + ? "Finder with no target class" + : "Finder for " + getTargetClass().getSimpleName(); + } } diff --git a/org.restlet/src/main/java/org/restlet/resource/Get.java b/org.restlet/src/main/java/org/restlet/resource/Get.java index df77fdceaa..b2e9e9d752 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Get.java +++ b/org.restlet/src/main/java/org/restlet/resource/Get.java @@ -1,45 +1,47 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.restlet.engine.connector.Method; import org.restlet.service.MetadataService; -import java.lang.annotation.*; - /** - * Annotation for methods that retrieve a resource representation. Its semantics - * is equivalent to an HTTP GET method.
+ * Annotation for methods that retrieve a resource representation. Its semantics are equivalent to + * an HTTP GET method.
*
* Example: - * + * *

  * @Get
  * public MyBean represent();
- * 
+ *
  * @Get("json")
  * public String toJson();
- * 
+ *
  * @Get("xml|html")
  * public Representation represent();
- * 
+ *
  * @Get("json?param=val")
  * public Representation representWithParam();
- * 
+ *
  * @Get("json?param")
  * public Representation representWithParam();
- * 
+ *
  * @Get("?param")
  * public Representation representWithParam();
  * 
- * + * * @author Jerome Louvel */ @Documented @@ -48,18 +50,15 @@ @Method("GET") public @interface Get { - /** - * Specifies the media type extension of the response entity. If several media - * types are supported, their extension can be specified separated by "|" - * characters. Note that this isn't the full MIME type value, just the extension - * name declared in {@link MetadataService}. For a list of all predefined - * extensions, please check {@link MetadataService#addCommonExtensions()}. New - * extension can be registered using - * {@link MetadataService#addExtension(String, org.restlet.data.Metadata)} - * method. - * - * @return The result media types. - */ - String value() default ""; - + /** + * Specifies the media type extension of the response entity. If several media types are + * supported, their extension can be specified separated by "|" characters. Note that this isn't + * the full MIME type value, just the extension name declared in {@link MetadataService}. For a + * list of all predefined extensions, please check {@link + * MetadataService#addCommonExtensions()}. New extension can be registered using {@link + * MetadataService#addExtension(String, org.restlet.data.Metadata)} method. + * + * @return The result media types. + */ + String value() default ""; } diff --git a/org.restlet/src/main/java/org/restlet/resource/Options.java b/org.restlet/src/main/java/org/restlet/resource/Options.java index 01a7492790..826f0bdfcc 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Options.java +++ b/org.restlet/src/main/java/org/restlet/resource/Options.java @@ -1,42 +1,44 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.restlet.engine.connector.Method; import org.restlet.service.MetadataService; -import java.lang.annotation.*; - /** - * Annotation for methods that describe a resource. Its semantics is equivalent - * to an HTTP OPTIONS method.
+ * Annotation for methods that describe a resource. Its semantics are equivalent to an HTTP OPTIONS + * method.
*
* Example: - * + * *

  * @Options
  * public ApplicationInfo describe();
- * 
+ *
  * @Options("wadl|html")
  * public Representation describe();
- * 
+ *
  * @Options("wadl?param=val")
  * public Representation describeWithParam();
- * 
+ *
  * @Options("wadl?param")
  * public Representation describeWithParam();
- * 
+ *
  * @Options("?param")
  * public Representation describeWithParam();
  * 
- * + * * @author Jerome Louvel */ @Documented @@ -45,18 +47,15 @@ @Method("OPTIONS") public @interface Options { - /** - * Specifies the media type extension of the response entity. If several media - * types are supported, their extension can be specified separated by "|" - * characters. Note that this isn't the full MIME type value, just the extension - * name declared in {@link MetadataService}. For a list of all predefined - * extensions, please check {@link MetadataService#addCommonExtensions()}. New - * extension can be registered using - * {@link MetadataService#addExtension(String, org.restlet.data.Metadata)} - * method. - * - * @return The result media types. - */ - String value() default ""; - + /** + * Specifies the media type extension of the response entity. If several media types are + * supported, their extension can be specified separated by "|" characters. Note that this isn't + * the full MIME type value, just the extension name declared in {@link MetadataService}. For a + * list of all predefined extensions, please check {@link + * MetadataService#addCommonExtensions()}. New extension can be registered using {@link + * MetadataService#addExtension(String, org.restlet.data.Metadata)} method. + * + * @return The result media types. + */ + String value() default ""; } diff --git a/org.restlet/src/main/java/org/restlet/resource/Patch.java b/org.restlet/src/main/java/org/restlet/resource/Patch.java index 6da6843e43..1cc40bceb9 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Patch.java +++ b/org.restlet/src/main/java/org/restlet/resource/Patch.java @@ -1,47 +1,48 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.restlet.engine.connector.Method; import org.restlet.service.MetadataService; -import java.lang.annotation.*; - /** - * Annotation for methods that apply submitted representations as a patch. Its - * semantics is equivalent to an HTTP PATCH method. Note that your method must - * have one input parameter if you want it to be selected for requests - * containing an entity.
+ * Annotation for methods that apply submitted representations as a patch. Its semantics are + * equivalent to an HTTP PATCH method. Note that your method must have one input parameter if you + * want it to be selected for requests containing an entity.
*
* Example: - * + * *

  * @Patch
  * public Representation update(Representation input);
- * 
+ *
  * @Patch("json-patch")
  * public String updateJson(String value);
- * 
+ *
  * @Patch("json-patch|xml-patch:xml|json")
  * public Representation update(Representation value);
- * 
+ *
  * @Patch("json?param=val")
  * public Representation updateWithParam(String value);
- * 
+ *
  * @Patch("json?param")
  * public Representation updateWithParam(String value);
- * 
+ *
  * @Patch("?param")
  * public Representation updateWithParam(String value);
  * 
- * + * * @author Jerome Louvel */ @Documented @@ -50,24 +51,19 @@ @Method("PATCH") public @interface Patch { - /** - * Specifies the media type of the request and response entities as extensions. - * If only one extension is provided, the extension applies to both request and - * response entities. If two extensions are provided, separated by a colon, then - * the first one is for the request entity and the second one for the response - * entity.
- *
- * If several media types are supported, their extension can be specified - * separated by "|" characters. Note that this isn't the full MIME type value, - * just the extension name declared in {@link MetadataService}. For a list of - * all predefined extensions, please check - * {@link MetadataService#addCommonExtensions()}. New extension can be - * registered using - * {@link MetadataService#addExtension(String, org.restlet.data.Metadata)} - * method. - * - * @return The media types of request and/or response entities. - */ - String value() default ""; - + /** + * Specifies the media type of the request and response entities as extensions. If only one + * extension is provided, the extension applies to both request and response entities. If two + * extensions are provided, separated by a colon, then the first one is for the request entity + * and the second one for the response entity.
+ *
+ * If several media types are supported, their extension can be specified separated by "|" + * characters. Note that this isn't the full MIME type value, just the extension name declared + * in {@link MetadataService}. For a list of all predefined extensions, please check {@link + * MetadataService#addCommonExtensions()}. New extension can be registered using {@link + * MetadataService#addExtension(String, org.restlet.data.Metadata)} method. + * + * @return The media types of request and/or response entities. + */ + String value() default ""; } diff --git a/org.restlet/src/main/java/org/restlet/resource/Post.java b/org.restlet/src/main/java/org/restlet/resource/Post.java index 7bffa7b6e2..cbcb3c1b2e 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Post.java +++ b/org.restlet/src/main/java/org/restlet/resource/Post.java @@ -1,47 +1,48 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.restlet.engine.connector.Method; import org.restlet.service.MetadataService; -import java.lang.annotation.*; - /** - * Annotation for methods that accept submitted representations. Its semantics - * is equivalent to an HTTP POST method. Note that your method must have one - * input parameter if you want it to be selected for requests containing an - * entity.
+ * Annotation for methods that accept submitted representations. Its semantics are equivalent to an + * HTTP POST method. Note that your method must have one input parameter if you want it to be + * selected for requests containing an entity.
*
* Example: - * + * *

  * @Post
  * public MyOutputBean accept(MyInputBean input);
- * 
+ *
  * @Post("json")
  * public String acceptJson(String value);
- * 
+ *
  * @Post("xml|json:xml|json")
  * public Representation accept(Representation xmlValue);
- * 
+ *
  * @Post("json?param=val")
  * public Representation acceptWithParam(String value);
- * 
+ *
  * @Post("json?param")
  * public Representation acceptWithParam(String value);
- * 
+ *
  * @Post("?param")
  * public Representation acceptWithParam(String value);
  * 
- * + * * @author Jerome Louvel */ @Documented @@ -50,24 +51,19 @@ @Method("POST") public @interface Post { - /** - * Specifies the media type of the request and response entities as extensions. - * If only one extension is provided, the extension applies to both request and - * response entities. If two extensions are provided, separated by a colon, then - * the first one is for the request entity and the second one for the response - * entity.
- *
- * If several media types are supported, their extension can be specified - * separated by "|" characters. Note that this isn't the full MIME type value, - * just the extension name declared in {@link MetadataService}. For a list of - * all predefined extensions, please check - * {@link MetadataService#addCommonExtensions()}. New extension can be - * registered using - * {@link MetadataService#addExtension(String, org.restlet.data.Metadata)} - * method. - * - * @return The media types of request and/or response entities. - */ - String value() default ""; - + /** + * Specifies the media type of the request and response entities as extensions. If only one + * extension is provided, the extension applies to both request and response entities. If two + * extensions are provided, separated by a colon, then the first one is for the request entity + * and the second one for the response entity.
+ *
+ * If several media types are supported, their extension can be specified separated by "|" + * characters. Note that this isn't the full MIME type value, just the extension name declared + * in {@link MetadataService}. For a list of all predefined extensions, please check {@link + * MetadataService#addCommonExtensions()}. New extension can be registered using {@link + * MetadataService#addExtension(String, org.restlet.data.Metadata)} method. + * + * @return The media types of request and/or response entities. + */ + String value() default ""; } diff --git a/org.restlet/src/main/java/org/restlet/resource/Put.java b/org.restlet/src/main/java/org/restlet/resource/Put.java index 4ad3984356..b6ebfac47e 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Put.java +++ b/org.restlet/src/main/java/org/restlet/resource/Put.java @@ -1,47 +1,48 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.restlet.engine.connector.Method; import org.restlet.service.MetadataService; -import java.lang.annotation.*; - /** - * Annotation for methods that store submitted representations. Its semantics is - * equivalent to an HTTP PUT method. Note that your method must have one input - * parameter if you want it to be selected for requests containing an - * entity.
+ * Annotation for methods that store submitted representations. Its semantics are equivalent to an + * HTTP PUT method. Note that your method must have one input parameter if you want it to be + * selected for requests containing an entity.
*
* Example: - * + * *

  * @Put
  * public MyOutputBean store(MyInputBean input);
- * 
+ *
  * @Put("json")
  * public String storeJson(String value);
- * 
+ *
  * @Put("json|xml:xml|json")
  * public Representation store(Representation value);
- * 
+ *
  * @Put("json?param=val")
  * public Representation storeWithParam(String value);
- * 
+ *
  * @Put("json?param")
  * public Representation storeWithParam(String value);
- * 
+ *
  * @Put("?param")
  * public Representation storeWithParam(String value);
  * 
- * + * * @author Jerome Louvel */ @Documented @@ -50,24 +51,19 @@ @Method("PUT") public @interface Put { - /** - * Specifies the media type of the request and response entities as extensions. - * If only one extension is provided, the extension applies to both request and - * response entities. If two extensions are provided, separated by a colon, then - * the first one is for the request entity and the second one for the response - * entity.
- *
- * If several media types are supported, their extension can be specified - * separated by "|" characters. Note that this isn't the full MIME type value, - * just the extension name declared in {@link MetadataService}. For a list of - * all predefined extensions, please check - * {@link MetadataService#addCommonExtensions()}. New extension can be - * registered using - * {@link MetadataService#addExtension(String, org.restlet.data.Metadata)} - * method. - * - * @return The media types of request and/or response entities. - */ - String value() default ""; - + /** + * Specifies the media type of the request and response entities as extensions. If only one + * extension is provided, the extension applies to both request and response entities. If two + * extensions are provided, separated by a colon, then the first one is for the request entity + * and the second one for the response entity.
+ *
+ * If several media types are supported, their extension can be specified separated by "|" + * characters. Note that this isn't the full MIME type value, just the extension name declared + * in {@link MetadataService}. For a list of all predefined extensions, please check {@link + * MetadataService#addCommonExtensions()}. New extension can be registered using {@link + * MetadataService#addExtension(String, org.restlet.data.Metadata)} method. + * + * @return The media types of request and/or response entities. + */ + String value() default ""; } diff --git a/org.restlet/src/main/java/org/restlet/resource/Resource.java b/org.restlet/src/main/java/org/restlet/resource/Resource.java index 275daaea36..6640f093f3 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Resource.java +++ b/org.restlet/src/main/java/org/restlet/resource/Resource.java @@ -1,881 +1,869 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; +import org.restlet.data.CacheDirective; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ClientInfo; +import org.restlet.data.Conditions; +import org.restlet.data.Cookie; +import org.restlet.data.CookieSetting; +import org.restlet.data.Dimension; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.data.Range; +import org.restlet.data.Reference; +import org.restlet.data.ServerInfo; import org.restlet.data.Status; -import org.restlet.data.*; import org.restlet.representation.Representation; import org.restlet.representation.Variant; import org.restlet.service.MetadataService; import org.restlet.util.Series; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - /** - * Base resource class exposing the uniform REST interface. Intended conceptual - * target of a hypertext reference. An uniform resource encapsulates a - * {@link Context}, a {@link Request} and a {@link Response}, corresponding to a - * specific target resource.
+ * Base resource class exposing the uniform REST interface. Intended conceptual target of a + * hypertext reference. A uniform resource encapsulates a {@link Context}, a {@link Request} and a + * {@link Response}, corresponding to a specific target resource.
*
- * It also defines a precise life cycle. First, the instance is created and the - * {@link #init(Context, Request, Response)} method is invoked. If you need to - * do some additional initialization, you should just override the - * {@link #doInit()} method.
+ * It also defines a precise life cycle. First, the instance is created and the {@link + * #init(Context, Request, Response)} method is invoked. If you need to do some additional + * initialization, you should override the {@link #doInit()} method.
*
- * Then, the abstract {@link #handle()} method can be invoked. For concrete - * behavior, see the {@link ClientResource} and {@link ServerResource} - * subclasses. Note that the state of the resource can be changed several times - * and the {@link #handle()} method called more than once, but always by the + * Then, the abstract {@link #handle()} method can be invoked. For concrete behavior, see the {@link + * ClientResource} and {@link ServerResource} subclasses. Note that the state of the resource can be + * changed several times and the {@link #handle()} method called more than once, but always by the * same thread.
*
- * Finally, the final {@link #release()} method can be called to clean-up the - * resource, with a chance for the developer to do some additional clean-up by - * overriding the {@link #doRelease()} method.
- *
- * Note also that throwable raised such as {@link Error} and {@link Exception} - * can be caught in a single point by overriding the {@link #doCatch(Throwable)} + * Finally, the final {@link #release()} method can be called to clean up the resource, with a + * chance for the developer to do some additional cleanup by overriding the {@link #doRelease()} * method.
*
- * "The central feature that distinguishes the REST architectural style from - * other network-based styles is its emphasis on a uniform interface between - * components. By applying the software engineering principle of generality to - * the component interface, the overall system architecture is simplified and - * the visibility of interactions is improved. Implementations are decoupled - * from the services they provide, which encourages independent evolvability." - * Roy T. Fielding
+ * Note also that throwable raised such as {@link Error} and {@link Exception} can be caught in a + * single point by overriding the {@link #doCatch(Throwable)} method.
+ *
+ * "The central feature that distinguishes the REST architectural style from other network-based + * styles is its emphasis on a uniform interface between components. By applying the software + * engineering principle of generality to the component interface, the overall system architecture + * is simplified and the visibility of interactions is improved. Implementations are decoupled from + * the services they provide, which encourages independent evolvability." Roy T. Fielding
*
- * Concurrency note: contrary to the {@link org.restlet.Uniform} class and its - * main {@link Restlet} subclass where a single instance can handle several - * calls concurrently, one instance of {@link Resource} is created for each call - * handled and accessed by only one thread at a time. - * - * @see Source - * dissertation + * Concurrency note: contrary to the {@link org.restlet.Uniform} class and its main {@link Restlet} + * subclass where a single instance can handle several calls concurrently, one instance of {@link + * Resource} is created for each call handled and accessed by only one thread at a time. + * + * @see Source + * dissertation * @author Jerome Louvel */ public abstract class Resource { - /** - * Converts the given {@link String} value into a {@link Boolean} or null. - * - * @param value The value to convert or null. - * @return The converted {@link Boolean} value or null. - */ - public static Boolean toBoolean(String value) { - return (value != null) ? Boolean.valueOf(value) : null; - } - - /** - * Converts the given {@link String} value into a {@link Byte} or null. - * - * @param value The value to convert or null. - * @return The converted {@link Byte} value or null. - */ - public static Byte toByte(String value) { - return (value != null) ? Byte.valueOf(value) : null; - } - - /** - * Converts the given {@link String} value into an {@link Double} or null. - * - * @param value The value to convert or null. - * @return The converted {@link Double} value or null. - */ - public static Double toDouble(String value) { - return (value != null) ? Double.valueOf(value) : null; - } - - /** - * Converts the given {@link String} value into a {@link Float} or null. - * - * @param value The value to convert or null. - * @return The converted {@link Float} value or null. - */ - public static Float toFloat(String value) { - return (value != null) ? Float.valueOf(value) : null; - } - - /** - * Converts the given {@link String} value into an {@link Integer} or null. - * - * @param value The value to convert or null. - * @return The converted {@link Integer} value or null. - */ - public static Integer toInteger(String value) { - return (value != null) ? Integer.valueOf(value) : null; - } - - /** - * Converts the given {@link String} value into an {@link Long} or null. - * - * @param value The value to convert or null. - * @return The converted {@link Long} value or null. - */ - public static Long toLong(String value) { - return (value != null) ? Long.valueOf(value) : null; - } - - /** - * Converts the given {@link String} value into a {@link Short} or null. - * - * @param value The value to convert or null. - * @return The converted {@link Short} value or null. - */ - public static Short toShort(String value) { - return (value != null) ? Short.valueOf(value) : null; - } - - /** The parent application. */ - private volatile org.restlet.Application application; - - /** The parent context. */ - private volatile Context context; - - /** The handled request. */ - private volatile Request request; - - /** The handled response. */ - private volatile Response response; - - /** - * Invoked when a {@link Throwable} is caught during initialization, handling or - * releasing. - * - * @param throwable The caught error or exception. - */ - protected void doCatch(Throwable throwable) { - getLogger().log(Level.INFO, "Exception or error caught in resource", throwable); - } - - /** - * Invoked when an error response status is received. - * - * @param errorStatus The error status received. - */ - protected void doError(Status errorStatus) { - } - - /** - * Invoked when an error response status is received. - * - * @param errorStatus The error status received. - * @param errorMessage The custom error message. - */ - protected final void doError(Status errorStatus, String errorMessage) { - doError(new Status(errorStatus, errorMessage)); - } - - /** - * Set-up method that can be overridden in order to initialize the state of the - * resource. By default it does nothing. - * - * @see #init(Context, Request, Response) - */ - protected void doInit() throws ResourceException { - } - - /** - * Clean-up method that can be overridden in order to release the state of the - * resource. By default it does nothing. - * - * @see #release() - */ - protected void doRelease() throws ResourceException { - } - - /** - * Returns the set of methods allowed for the current client by the resource. - * The result can vary based on the client's user agent, authentication and - * authorization data provided by the client. - * - * @return The set of allowed methods. - */ - public Set getAllowedMethods() { - return getResponse() == null ? null : getResponse().getAllowedMethods(); - } - - /** - * Returns the parent application. If it wasn't set, it attempts to retrieve the - * current one via {@link org.restlet.Application#getCurrent()} if it exists, or - * instantiates a new one as a last resort. - * - * @return The parent application if it exists, or a new one. - */ - public org.restlet.Application getApplication() { - org.restlet.Application result = this.application; - - if (result == null) { - result = org.restlet.Application.getCurrent(); - - if (result == null) { - result = new org.restlet.Application(getContext()); - } - - this.application = result; - } - - return result; - } - - /** - * Returns the attribute value by looking up the given name in the request or - * response attributes maps. This is typically used for variables that are - * declared in the URI template used to route the call to this resource. - * - * @param name The attribute name. - * @return The matching request or response attribute value. - */ - public abstract String getAttribute(String name); - - /** - * Returns the list of authentication requests sent by an origin server to a - * client. If none is available, an empty list is returned. - * - * @return The list of authentication requests. - * @see Response#getChallengeRequests() - */ - public List getChallengeRequests() { - return getResponse() == null ? null : getResponse().getChallengeRequests(); - } - - /** - * Returns the authentication response sent by a client to an origin server. - * - * @return The authentication response sent by a client to an origin server. - * @see Request#getChallengeResponse() - */ - public ChallengeResponse getChallengeResponse() { - return getRequest() == null ? null : getRequest().getChallengeResponse(); - } - - /** - * Returns the client-specific information. Creates a new instance if no one has - * been set. - * - * @return The client-specific information. - * @see Request#getClientInfo() - */ - public ClientInfo getClientInfo() { - return getRequest() == null ? null : getRequest().getClientInfo(); - } - - /** - * Returns the modifiable conditions applying to this request. Creates a new - * instance if no one has been set. - * - * @return The conditions applying to this call. - * @see Request#getConditions() - */ - public Conditions getConditions() { - return getRequest() == null ? null : getRequest().getConditions(); - } - - /** - * Returns the application's content negotiation service or create a new one. - * - * @return The content negotiation service. - */ - public org.restlet.service.ConnegService getConnegService() { - org.restlet.service.ConnegService result = null; - - result = getApplication().getConnegService(); - - if (result == null) { - result = new org.restlet.service.ConnegService(); - } - - return result; - } - - /** - * Returns the current context. - * - * @return The current context. - */ - public Context getContext() { - return context; - } - - /** - * Returns the application's converter service or create a new one. - * - * @return The converter service. - */ - public org.restlet.service.ConverterService getConverterService() { - org.restlet.service.ConverterService result = null; - - result = getApplication().getConverterService(); - - if (result == null) { - result = new org.restlet.service.ConverterService(); - } - - return result; - } - - /** - * Returns the modifiable series of cookies provided by the client. Creates a - * new instance if no one has been set. - * - * @return The cookies provided by the client. - * @see Request#getCookies() - */ - public Series getCookies() { - return getRequest() == null ? null : getRequest().getCookies(); - } - - /** - * Returns the modifiable series of cookie settings provided by the server. - * Creates a new instance if no one has been set. - * - * @return The cookie settings provided by the server. - * @see Response#getCookieSettings() - */ - public Series getCookieSettings() { - return getResponse() == null ? null : getResponse().getCookieSettings(); - } - - /** - * Returns the modifiable set of selecting dimensions on which the response - * entity may vary. If some server-side content negotiation is done, this set - * should be properly updated, other it can be left empty. Creates a new - * instance if no one has been set. - * - * @return The set of dimensions on which the response entity may vary. - * @see Response#getDimensions() - */ - public Set getDimensions() { - return getResponse() == null ? null : getResponse().getDimensions(); - } - - /** - * Returns the host reference. This may be different from the resourceRef's - * host, for example for URNs and other URIs that don't contain host - * information. - * - * @return The host reference. - * @see Request#getHostRef() - */ - public Reference getHostRef() { - return getRequest() == null ? null : getRequest().getHostRef(); - } - - /** - * Returns the reference that the client should follow for redirections or - * resource creations. - * - * @return The redirection reference. - * @see Response#getLocationRef() - */ - public Reference getLocationRef() { - return getResponse() == null ? null : getResponse().getLocationRef(); - } - - /** - * Returns the logger. - * - * @return The logger. - */ - public Logger getLogger() { - return getContext() != null ? getContext().getLogger() : Context.getCurrentLogger(); - } - - /** - * Returns the resource reference's optional matrix. - * - * @return The resource reference's optional matrix. - * @see Reference#getMatrixAsForm() - */ - public Form getMatrix() { - return getReference() == null ? null : getReference().getMatrixAsForm(); - } - - /** - * Returns the first value of the matrix parameter given its name if existing, - * or null. - * - * @param name The matrix parameter name. - * @return The first value of the matrix parameter. - */ - public String getMatrixValue(String name) { - String result = null; - Form matrix = getMatrix(); - - if (matrix != null) { - result = matrix.getFirstValue(name); - } - - return result; - } - - /** - * Returns the maximum number of intermediaries. - * - * @return The maximum number of intermediaries. - */ - public int getMaxForwards() { - return getRequest() == null ? null : getRequest().getMaxForwards(); - } - - /** - * Returns the application's metadata service or create a new one. - * - * @return The metadata service. - */ - public MetadataService getMetadataService() { - MetadataService result = null; - - result = getApplication().getMetadataService(); - - if (result == null) { - result = new MetadataService(); - } - - return result; - } - - /** - * Returns the method. - * - * @return The method. - * @see Request#getMethod() - */ - public Method getMethod() { - return getRequest() == null ? null : getRequest().getMethod(); - } - - /** - * Returns the original reference as requested by the client. Note that this - * property is not used during request routing. - * - * @return The original reference. - * @see Request#getOriginalRef() - */ - public Reference getOriginalRef() { - return getRequest() == null ? null : getRequest().getOriginalRef(); - } - - /** - * Returns the protocol by first returning the resourceRef.schemeProtocol - * property if it is set, or the baseRef.schemeProtocol property otherwise. - * - * @return The protocol or null if not available. - * @see Request#getProtocol() - */ - public Protocol getProtocol() { - return getRequest() == null ? null : getRequest().getProtocol(); - } - - /** - * Returns the list of proxy authentication requests sent by an origin server to - * a client. If none is available, an empty list is returned. - * - * @return The list of proxy authentication requests. - * @see Response#getProxyChallengeRequests() - */ - public List getProxyChallengeRequests() { - return getResponse() == null ? null : getResponse().getProxyChallengeRequests(); - } - - /** - * Returns the proxy authentication response sent by a client to an origin - * server. - * - * @return The proxy authentication response sent by a client to an origin - * server. - * @see Request#getProxyChallengeResponse() - */ - public ChallengeResponse getProxyChallengeResponse() { - return getRequest() == null ? null : getRequest().getProxyChallengeResponse(); - } - - /** - * Returns the resource reference's optional query. Note that modifications to - * the returned {@link Form} object aren't reported to the underlying reference. - * - * @return The resource reference's optional query. - * @see Reference#getQueryAsForm() - */ - public Form getQuery() { - return getReference() == null ? null : getReference().getQueryAsForm(); - } - - /** - * Returns the first value of the query parameter given its name if existing, or - * null. - * - * @param name The query parameter name. - * @return The first value of the query parameter. - */ - public String getQueryValue(String name) { - String result = null; - Form query = getQuery(); - - if (query != null) { - result = query.getFirstValue(name); - } - - return result; - } - - /** - * Returns the ranges to return from the target resource's representation. - * - * @return The ranges to return. - * @see Request#getRanges() - */ - public List getRanges() { - return getRequest() == null ? null : getRequest().getRanges(); - } - - /** - * Returns the URI reference. - * - * @return The URI reference. - */ - public Reference getReference() { - return getRequest() == null ? null : getRequest().getResourceRef(); - } - - /** - * Returns the referrer reference if available. - * - * @return The referrer reference. - */ - public Reference getReferrerRef() { - return getRequest() == null ? null : getRequest().getReferrerRef(); - } - - /** - * Returns the handled request. - * - * @return The handled request. - */ - public Request getRequest() { - return request; - } - - /** - * Returns the request attributes. - * - * @return The request attributes. - * @see Request#getAttributes() - */ - public Map getRequestAttributes() { - return getRequest() == null ? null : getRequest().getAttributes(); - } - - /** - * Returns the request cache directives. Note that when used with HTTP - * connectors, this property maps to the "Cache-Control" header. - * - * @return The cache directives. - */ - public List getRequestCacheDirectives() { - return getRequest() == null ? null : getRequest().getCacheDirectives(); - } - - /** - * Returns the request entity representation. - * - * @return The request entity representation. - */ - public Representation getRequestEntity() { - return getRequest() == null ? null : getRequest().getEntity(); - } - - /** - * Returns the handled response. - * - * @return The handled response. - */ - public Response getResponse() { - return response; - } - - /** - * Returns the response attributes. - * - * @return The response attributes. - * @see Response#getAttributes() - */ - public Map getResponseAttributes() { - return getResponse() == null ? null : getResponse().getAttributes(); - } - - /** - * Returns the response cache directives. Note that when used with HTTP - * connectors, this property maps to the "Cache-Control" header. - * - * @return The cache directives. - */ - public List getResponseCacheDirectives() { - return getResponse() == null ? null : getResponse().getCacheDirectives(); - } - - /** - * Returns the response entity representation. - * - * @return The response entity representation. - */ - public Representation getResponseEntity() { - return getResponse() == null ? null : getResponse().getEntity(); - } - - /** - * Returns the application root reference. - * - * @return The application root reference. - * @see Request#getRootRef() - */ - public Reference getRootRef() { - return getRequest() == null ? null : getRequest().getRootRef(); - } - - /** - * Returns the server-specific information. Creates a new instance if no one has - * been set. - * - * @return The server-specific information. - * @see Response#getServerInfo() - */ - public ServerInfo getServerInfo() { - return getResponse() == null ? null : getResponse().getServerInfo(); - } - - /** - * Returns the status. - * - * @return The status. - * @see Response#getStatus() - */ - public Status getStatus() { - return getResponse() == null ? null : getResponse().getStatus(); - } - - /** - * Returns the application's status service or create a new one. - * - * @return The status service. - */ - public org.restlet.service.StatusService getStatusService() { - org.restlet.service.StatusService result = null; - - result = getApplication().getStatusService(); - - if (result == null) { - result = new org.restlet.service.StatusService(); - } - - return result; - } - - /** - * Handles the call composed of the current context, request and response. - * - * @return The optional response entity. - */ - public abstract Representation handle(); - - /** - * Initialization method setting the environment of the current resource - * instance. It the calls the {@link #doInit()} method that can be overridden. - * - * @param context The current context. - * @param request The handled request. - * @param response The handled response. - */ - public void init(Context context, Request request, Response response) { - this.context = context; - this.request = request; - this.response = response; - - try { - doInit(); - } catch (Throwable t) { - doCatch(t); - } - } - - /** - * Indicates if the message was or will be exchanged confidentially, for example - * via a SSL-secured connection. - * - * @return True if the message is confidential. - * @see Request#isConfidential() - */ - public boolean isConfidential() { - return getRequest() == null ? null : getRequest().isConfidential(); - } - - /** - * Indicates if the call is loggable - * - * @return True if the call is loggable - */ - public boolean isLoggable() { - return getRequest() == null ? null : getRequest().isLoggable(); - } - - /** - * Releases the resource by calling {@link #doRelease()}. - */ - public final void release() { - try { - doRelease(); - } catch (Throwable t) { - doCatch(t); - } - } - - /** - * Sets the parent application. - * - * @param application The parent application. - */ - public void setApplication(org.restlet.Application application) { - this.application = application; - } - - /** - * Sets the request or response attribute value. - * - * @param name The attribute name. - * @param value The attribute to set. - */ - public abstract void setAttribute(String name, Object value); - - /** - * Sets the query value for the named parameter. If no query is defined, it - * creates one. If the same parameter exists, it replaces it altogether. - * - * @param name The query parameter name. - * @param value The query parameter value. - */ - public void setQueryValue(String name, String value) { - Form query = getQuery(); - - if (query == null) { - query = new Form(); - } - - query.set(name, value); - - try { - getReference().setQuery(query.encode()); - } catch (IOException e) { - getLogger().fine("Unable to set the query value"); - } - } - - /** - * Sets the handled request. - * - * @param request The handled request. - */ - public void setRequest(Request request) { - this.request = request; - } - - /** - * Sets the handled response. - * - * @param response The handled response. - */ - public void setResponse(Response response) { - this.response = response; - } - - /** - * Converts a representation into a Java object. Leverages the - * {@link org.restlet.service.ConverterService}. - * - * @param The expected class of the Java object. - * @param source The source representation to convert. - * @param target The target class of the Java object. - * @return The converted Java object. - * @throws ResourceException - */ - public T toObject(Representation source, Class target) throws ResourceException { - T result = null; - - if (source != null) { - try { - org.restlet.service.ConverterService cs = getConverterService(); - result = cs.toObject(source, target, this); - } catch (ResourceException e) { - throw e; - } catch (Exception e) { - throw new ResourceException(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE, e); - } - } - - return result; - } - - /** - * Converts an object into a representation based on the default converter - * service variant. - * - * @param source The object to convert. - * @return The wrapper representation. - * @throws IOException - */ - public Representation toRepresentation(Object source) throws IOException { - return toRepresentation(source, (Variant) null); - } - - /** - * Converts an object into a representation based on a given media type. - * - * @param source The object to convert. - * @param target The target representation media type. - * @return The wrapper representation. - * @throws IOException - */ - public Representation toRepresentation(Object source, MediaType target) throws IOException { - return toRepresentation(source, new Variant(target)); - } - - /** - * Converts an object into a representation based on client preferences. - * - * @param source The object to convert. - * @param target The target representation variant. - * @return The wrapper representation. - * @throws IOException - */ - public Representation toRepresentation(Object source, Variant target) throws IOException { - Representation result = null; - - if (source != null) { - org.restlet.service.ConverterService cs = getConverterService(); - result = cs.toRepresentation(source, target, this); - } - - return result; - } - - @Override - public String toString() { - return (getRequest() == null ? "" : getRequest().toString()) - + (getResponse() == null ? "" : " => " + getResponse().toString()); - } + /** + * Converts the given {@link String} value into a {@link Boolean} or null. + * + * @param value The value to convert or null. + * @return The converted {@link Boolean} value or null. + */ + public static Boolean toBoolean(String value) { + return (value != null) ? Boolean.valueOf(value) : null; + } + + /** + * Converts the given {@link String} value into a {@link Byte} or null. + * + * @param value The value to convert or null. + * @return The converted {@link Byte} value or null. + */ + public static Byte toByte(String value) { + return (value != null) ? Byte.valueOf(value) : null; + } + + /** + * Converts the given {@link String} value into an {@link Double} or null. + * + * @param value The value to convert or null. + * @return The converted {@link Double} value or null. + */ + public static Double toDouble(String value) { + return (value != null) ? Double.valueOf(value) : null; + } + + /** + * Converts the given {@link String} value into a {@link Float} or null. + * + * @param value The value to convert or null. + * @return The converted {@link Float} value or null. + */ + public static Float toFloat(String value) { + return (value != null) ? Float.valueOf(value) : null; + } + + /** + * Converts the given {@link String} value into an {@link Integer} or null. + * + * @param value The value to convert or null. + * @return The converted {@link Integer} value or null. + */ + public static Integer toInteger(String value) { + return (value != null) ? Integer.valueOf(value) : null; + } + + /** + * Converts the given {@link String} value into an {@link Long} or null. + * + * @param value The value to convert or null. + * @return The converted {@link Long} value or null. + */ + public static Long toLong(String value) { + return (value != null) ? Long.valueOf(value) : null; + } + + /** + * Converts the given {@link String} value into a {@link Short} or null. + * + * @param value The value to convert or null. + * @return The converted {@link Short} value or null. + */ + public static Short toShort(String value) { + return (value != null) ? Short.valueOf(value) : null; + } + + /** The parent application. */ + private volatile org.restlet.Application application; + + /** The parent context. */ + private volatile Context context; + + /** The handled request. */ + private volatile Request request; + + /** The handled response. */ + private volatile Response response; + + /** + * Invoked when a {@link Throwable} is caught during initialization, handling, or releasing. + * + * @param throwable The caught error or exception. + */ + protected void doCatch(Throwable throwable) { + getLogger().log(Level.INFO, "Exception or error caught in resource", throwable); + } + + /** + * Invoked when an error response status is received. + * + * @param errorStatus The error status received. + */ + protected void doError(Status errorStatus) {} + + /** + * Invoked when an error response status is received. + * + * @param errorStatus The error status received. + * @param errorMessage The custom error message. + */ + protected final void doError(Status errorStatus, String errorMessage) { + doError(new Status(errorStatus, errorMessage)); + } + + /** + * Setup method that can be overridden to initialize the state of the resource. By default, it + * does nothing. + * + * @see #init(Context, Request, Response) + */ + protected void doInit() throws ResourceException {} + + /** + * Cleanup method that can be overridden to release the state of the resource. By default, it + * does nothing. + * + * @see #release() + */ + protected void doRelease() throws ResourceException {} + + /** + * Returns the set of methods allowed for the current client by the resource. The result can + * vary based on the client's user agent, authentication and authorization data provided by the + * client. + * + * @return The set of allowed methods. + */ + public Set getAllowedMethods() { + return getResponse() == null ? null : getResponse().getAllowedMethods(); + } + + /** + * Returns the parent application. If it wasn't set, it attempts to retrieve the current one via + * {@link org.restlet.Application#getCurrent()} if it exists, or instantiates a new one as a + * last resort. + * + * @return The parent application if it exists, or a new one. + */ + public org.restlet.Application getApplication() { + org.restlet.Application result = this.application; + + if (result == null) { + result = org.restlet.Application.getCurrent(); + + if (result == null) { + result = new org.restlet.Application(getContext()); + } + + this.application = result; + } + + return result; + } + + /** + * Returns the attribute value by looking up the given name in the request or response + * attributes maps. This is typically used for variables that are declared in the URI template + * used to route the call to this resource. + * + * @param name The attribute name. + * @return The matching request or response attribute value. + */ + public abstract String getAttribute(String name); + + /** + * Returns the list of authentication requests sent by an origin server to a client. If none is + * available, an empty list is returned. + * + * @return The list of authentication requests. + * @see Response#getChallengeRequests() + */ + public List getChallengeRequests() { + return getResponse() == null ? null : getResponse().getChallengeRequests(); + } + + /** + * Returns the authentication response sent by a client to an origin server. + * + * @return The authentication response sent by a client to an origin server. + * @see Request#getChallengeResponse() + */ + public ChallengeResponse getChallengeResponse() { + return getRequest() == null ? null : getRequest().getChallengeResponse(); + } + + /** + * Returns the client-specific information. Creates a new instance if no one has been set. + * + * @return The client-specific information. + * @see Request#getClientInfo() + */ + public ClientInfo getClientInfo() { + return getRequest() == null ? null : getRequest().getClientInfo(); + } + + /** + * Returns the modifiable conditions applying to this request. Creates a new instance if no one + * has been set. + * + * @return The conditions applying to this call. + * @see Request#getConditions() + */ + public Conditions getConditions() { + return getRequest() == null ? null : getRequest().getConditions(); + } + + /** + * Returns the application's content negotiation service or create a new one. + * + * @return The content negotiation service. + */ + public org.restlet.service.ConnegService getConnegService() { + org.restlet.service.ConnegService result = null; + + result = getApplication().getConnegService(); + + if (result == null) { + result = new org.restlet.service.ConnegService(); + } + + return result; + } + + /** + * Returns the current context. + * + * @return The current context. + */ + public Context getContext() { + return context; + } + + /** + * Returns the application's converter service or create a new one. + * + * @return The converter service. + */ + public org.restlet.service.ConverterService getConverterService() { + org.restlet.service.ConverterService result = null; + + result = getApplication().getConverterService(); + + if (result == null) { + result = new org.restlet.service.ConverterService(); + } + + return result; + } + + /** + * Returns the modifiable series of cookies provided by the client. Creates a new instance if no + * one has been set. + * + * @return The cookies provided by the client. + * @see Request#getCookies() + */ + public Series getCookies() { + return getRequest() == null ? null : getRequest().getCookies(); + } + + /** + * Returns the modifiable series of cookie settings provided by the server. Creates a new + * instance if no one has been set. + * + * @return The cookie settings provided by the server. + * @see Response#getCookieSettings() + */ + public Series getCookieSettings() { + return getResponse() == null ? null : getResponse().getCookieSettings(); + } + + /** + * Returns the modifiable set of selecting dimensions on which the response entity may vary. If + * some server-side content negotiation is done, this set should be properly updated, other it + * can be left empty. Creates a new instance if no one has been set. + * + * @return The set of dimensions on which the response entity may vary. + * @see Response#getDimensions() + */ + public Set getDimensions() { + return getResponse() == null ? null : getResponse().getDimensions(); + } + + /** + * Returns the host reference. This may be different from the resourceRef's host, for example + * for URNs and other URIs that don't contain host information. + * + * @return The host reference. + * @see Request#getHostRef() + */ + public Reference getHostRef() { + return getRequest() == null ? null : getRequest().getHostRef(); + } + + /** + * Returns the reference that the client should follow for redirections or resource creations. + * + * @return The redirection reference. + * @see Response#getLocationRef() + */ + public Reference getLocationRef() { + return getResponse() == null ? null : getResponse().getLocationRef(); + } + + /** + * Returns the logger. + * + * @return The logger. + */ + public Logger getLogger() { + return getContext() != null ? getContext().getLogger() : Context.getCurrentLogger(); + } + + /** + * Returns the resource reference's optional matrix. + * + * @return The resource reference's optional matrix. + * @see Reference#getMatrixAsForm() + */ + public Form getMatrix() { + return getReference() == null ? null : getReference().getMatrixAsForm(); + } + + /** + * Returns the first value of the matrix parameter given its name if existing, or null. + * + * @param name The matrix parameter name. + * @return The first value of the matrix parameter. + */ + public String getMatrixValue(String name) { + String result = null; + Form matrix = getMatrix(); + + if (matrix != null) { + result = matrix.getFirstValue(name); + } + + return result; + } + + /** + * Returns the maximum number of intermediaries. + * + * @return The maximum number of intermediaries. + */ + public int getMaxForwards() { + return getRequest() == null ? 0 : getRequest().getMaxForwards(); + } + + /** + * Returns the application's metadata service or create a new one. + * + * @return The metadata service. + */ + public MetadataService getMetadataService() { + MetadataService result = null; + + result = getApplication().getMetadataService(); + + if (result == null) { + result = new MetadataService(); + } + + return result; + } + + /** + * Returns the method. + * + * @return The method. + * @see Request#getMethod() + */ + public Method getMethod() { + return getRequest() == null ? null : getRequest().getMethod(); + } + + /** + * Returns the original reference as requested by the client. Note that this property is not + * used during request routing. + * + * @return The original reference. + * @see Request#getOriginalRef() + */ + public Reference getOriginalRef() { + return getRequest() == null ? null : getRequest().getOriginalRef(); + } + + /** + * Returns the protocol by first returning the resourceRef.schemeProtocol property if it is set, + * or the baseRef.schemeProtocol property otherwise. + * + * @return The protocol or null if not available. + * @see Request#getProtocol() + */ + public Protocol getProtocol() { + return getRequest() == null ? null : getRequest().getProtocol(); + } + + /** + * Returns the list of proxy authentication requests sent by an origin server to a client. If + * none is available, an empty list is returned. + * + * @return The list of proxy authentication requests. + * @see Response#getProxyChallengeRequests() + */ + public List getProxyChallengeRequests() { + return getResponse() == null ? null : getResponse().getProxyChallengeRequests(); + } + + /** + * Returns the proxy authentication response sent by a client to an origin server. + * + * @return The proxy authentication response sent by a client to an origin server. + * @see Request#getProxyChallengeResponse() + */ + public ChallengeResponse getProxyChallengeResponse() { + return getRequest() == null ? null : getRequest().getProxyChallengeResponse(); + } + + /** + * Returns the resource reference's optional query. Note that modifications to the returned + * {@link Form} object aren't reported to the underlying reference. + * + * @return The resource reference's optional query. + * @see Reference#getQueryAsForm() + */ + public Form getQuery() { + return getReference() == null ? null : getReference().getQueryAsForm(); + } + + /** + * Returns the first value of the query parameter given its name if existing, or null. + * + * @param name The query parameter name. + * @return The first value of the query parameter. + */ + public String getQueryValue(String name) { + String result = null; + Form query = getQuery(); + + if (query != null) { + result = query.getFirstValue(name); + } + + return result; + } + + /** + * Returns the ranges to return from the target resource's representation. + * + * @return The ranges to return. + * @see Request#getRanges() + */ + public List getRanges() { + return getRequest() == null ? null : getRequest().getRanges(); + } + + /** + * Returns the URI reference. + * + * @return The URI reference. + */ + public Reference getReference() { + return getRequest() == null ? null : getRequest().getResourceRef(); + } + + /** + * Returns the referrer reference if available. + * + * @return The referrer reference. + */ + public Reference getReferrerRef() { + return getRequest() == null ? null : getRequest().getReferrerRef(); + } + + /** + * Returns the handled request. + * + * @return The handled request. + */ + public Request getRequest() { + return request; + } + + /** + * Returns the request attributes. + * + * @return The request attributes. + * @see Request#getAttributes() + */ + public Map getRequestAttributes() { + return getRequest() == null ? null : getRequest().getAttributes(); + } + + /** + * Returns the request cache directives. Note that when used with HTTP connectors, this property + * maps to the "Cache-Control" header. + * + * @return The cache directives. + */ + public List getRequestCacheDirectives() { + return getRequest() == null ? null : getRequest().getCacheDirectives(); + } + + /** + * Returns the request entity representation. + * + * @return The request entity representation. + */ + public Representation getRequestEntity() { + return getRequest() == null ? null : getRequest().getEntity(); + } + + /** + * Returns the handled response. + * + * @return The handled response. + */ + public Response getResponse() { + return response; + } + + /** + * Returns the response attributes. + * + * @return The response attributes. + * @see Response#getAttributes() + */ + public Map getResponseAttributes() { + return getResponse() == null ? null : getResponse().getAttributes(); + } + + /** + * Returns the response cache directives. Note that when used with HTTP connectors, this + * property maps to the "Cache-Control" header. + * + * @return The cache directives. + */ + public List getResponseCacheDirectives() { + return getResponse() == null ? null : getResponse().getCacheDirectives(); + } + + /** + * Returns the response entity representation. + * + * @return The response entity representation. + */ + public Representation getResponseEntity() { + return getResponse() == null ? null : getResponse().getEntity(); + } + + /** + * Returns the application root reference. + * + * @return The application root reference. + * @see Request#getRootRef() + */ + public Reference getRootRef() { + return getRequest() == null ? null : getRequest().getRootRef(); + } + + /** + * Returns the server-specific information. Creates a new instance if no one has been set. + * + * @return The server-specific information. + * @see Response#getServerInfo() + */ + public ServerInfo getServerInfo() { + return getResponse() == null ? null : getResponse().getServerInfo(); + } + + /** + * Returns the status. + * + * @return The status. + * @see Response#getStatus() + */ + public Status getStatus() { + return getResponse() == null ? null : getResponse().getStatus(); + } + + /** + * Returns the application's status service or create a new one. + * + * @return The status service. + */ + public org.restlet.service.StatusService getStatusService() { + org.restlet.service.StatusService result = null; + + result = getApplication().getStatusService(); + + if (result == null) { + result = new org.restlet.service.StatusService(); + } + + return result; + } + + /** + * Handles the call composed of the current context, request, and response. + * + * @return The optional response entity. + */ + public abstract Representation handle(); + + /** + * Initialization method setting the environment of the current resource instance. It the calls + * the {@link #doInit()} method that can be overridden. + * + * @param context The current context. + * @param request The handled request. + * @param response The handled response. + */ + public void init(Context context, Request request, Response response) { + this.context = context; + this.request = request; + this.response = response; + + try { + doInit(); + } catch (Throwable t) { + doCatch(t); + } + } + + /** + * Indicates if the message was or will be exchanged confidentially, for example, via an + * SSL-secured connection. + * + * @return True if the message is confidential. + * @see Request#isConfidential() + */ + public boolean isConfidential() { + return getRequest() == null ? null : getRequest().isConfidential(); + } + + /** + * Indicates if the call is loggable + * + * @return True if the call is loggable + */ + public boolean isLoggable() { + return getRequest() == null ? null : getRequest().isLoggable(); + } + + /** Releases the resource by calling {@link #doRelease()}. */ + public final void release() { + try { + doRelease(); + } catch (Throwable t) { + doCatch(t); + } + } + + /** + * Sets the parent application. + * + * @param application The parent application. + */ + public void setApplication(org.restlet.Application application) { + this.application = application; + } + + /** + * Sets the request or response attribute value. + * + * @param name The attribute name. + * @param value The attribute to set. + */ + public abstract void setAttribute(String name, Object value); + + /** + * Sets the query value for the named parameter. If no query is defined, it creates one. If the + * same parameter exists, it replaces it altogether. + * + * @param name The query parameter name. + * @param value The query parameter value. + */ + public void setQueryValue(String name, String value) { + Form query = getQuery(); + + if (query == null) { + query = new Form(); + } + + query.set(name, value); + + try { + getReference().setQuery(query.encode()); + } catch (IOException e) { + getLogger().fine("Unable to set the query value"); + } + } + + /** + * Sets the handled request. + * + * @param request The handled request. + */ + public void setRequest(Request request) { + this.request = request; + } + + /** + * Sets the handled response. + * + * @param response The handled response. + */ + public void setResponse(Response response) { + this.response = response; + } + + /** + * Converts a representation into a Java object. Leverages the {@link + * org.restlet.service.ConverterService}. + * + * @param The expected class of the Java object. + * @param source The source representation to convert. + * @param target The target class of the Java object. + * @return The converted Java object. + * @throws ResourceException + */ + public T toObject(Representation source, Class target) throws ResourceException { + T result = null; + + if (source != null) { + try { + org.restlet.service.ConverterService cs = getConverterService(); + result = cs.toObject(source, target, this); + } catch (ResourceException e) { + throw e; + } catch (Exception e) { + throw new ResourceException(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE, e); + } + } + + return result; + } + + /** + * Converts an object into a representation based on the default converter service variant. + * + * @param source The object to convert. + * @return The wrapper representation. + * @throws IOException + */ + public Representation toRepresentation(Object source) throws IOException { + return toRepresentation(source, (Variant) null); + } + + /** + * Converts an object into a representation based on a given media type. + * + * @param source The object to convert. + * @param target The target representation media type. + * @return The wrapper representation. + * @throws IOException + */ + public Representation toRepresentation(Object source, MediaType target) throws IOException { + return toRepresentation(source, new Variant(target)); + } + + /** + * Converts an object into a representation based on client preferences. + * + * @param source The object to convert. + * @param target The target representation variant. + * @return The wrapper representation. + * @throws IOException + */ + public Representation toRepresentation(Object source, Variant target) throws IOException { + Representation result = null; + + if (source != null) { + org.restlet.service.ConverterService cs = getConverterService(); + result = cs.toRepresentation(source, target, this); + } + + return result; + } + + @Override + public String toString() { + return (getRequest() == null ? "" : getRequest().toString()) + + (getResponse() == null ? "" : " => " + getResponse().toString()); + } } diff --git a/org.restlet/src/main/java/org/restlet/resource/ResourceException.java b/org.restlet/src/main/java/org/restlet/resource/ResourceException.java index f8f5fc54cd..045b663302 100644 --- a/org.restlet/src/main/java/org/restlet/resource/ResourceException.java +++ b/org.restlet/src/main/java/org/restlet/resource/ResourceException.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; import org.restlet.Request; @@ -15,226 +14,228 @@ /** * Encapsulates a response status and the optional cause as a checked exception. - * + * * @author Jerome Louvel */ public class ResourceException extends RuntimeException { - private static final long serialVersionUID = 1L; - - /** The status associated to this exception. */ - private transient Status status; - - /** The request associated to this exception. Could be null. */ - private transient Request request; - - /** The response associated to this exception. Could be null. */ - private transient Response response; - - /** - * Constructor. - * - * @param code The specification code of the encapsulated status. - */ - public ResourceException(int code) { - this(new Status(code)); - } - - /** - * Constructor. - * - * @param code The specification code of the encapsulated status. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - */ - public ResourceException(int code, String reasonPhrase) { - this(new Status(code, reasonPhrase)); - } - - /** - * Constructor. - * - * @param code The specification code of the encapsulated status. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - * @param description The description of the encapsulated status. - */ - public ResourceException(int code, String reasonPhrase, String description) { - this(new Status(code, reasonPhrase, description)); - } - - /** - * Constructor. - * - * @param code The specification code of the encapsulated status. - * @param name The name of the encapsulated status. - * @param description The description of the encapsulated status. - * @param uri The URI of the specification describing the method. - */ - public ResourceException(int code, String name, String description, String uri) { - this(new Status(code, name, description, uri)); - } - - /** - * Constructor. - * - * @param code The specification code of the encapsulated status. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - * @param description The description of the encapsulated status. - * @param uri The URI of the specification describing the method. - * @param cause The wrapped cause error or exception. - */ - public ResourceException(int code, String reasonPhrase, String description, String uri, Throwable cause) { - this(new Status(code, cause, reasonPhrase, description, uri), cause); - } - - /** - * Constructor. - * - * @param code The specification code of the encapsulated status. - * @param cause The wrapped cause error or exception. - */ - public ResourceException(int code, Throwable cause) { - this(new Status(code, cause), cause); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param throwable The related error or exception. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - */ - public ResourceException(int code, Throwable throwable, String reasonPhrase) { - this(new Status(code, throwable, reasonPhrase, null, null)); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param throwable The related error or exception. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - * @param description The longer description. - */ - public ResourceException(int code, Throwable throwable, String reasonPhrase, String description) { - this(new Status(code, throwable, reasonPhrase, description, null)); - } - - /** - * Constructor. - * - * @param code The specification code. - * @param throwable The related error or exception. - * @param reasonPhrase The short reason phrase displayed next to the status code - * in a HTTP response. - * @param description The longer description. - * @param uri The URI of the specification describing the method. - */ - public ResourceException(int code, Throwable throwable, String reasonPhrase, String description, String uri) { - this(new Status(code, throwable, reasonPhrase, description, uri)); - } - - /** - * Constructor. - * - * @param status The status to associate. - */ - public ResourceException(Status status) { - this(status, (status == null) ? null : status.getThrowable()); - } - - /** - * Constructor. - * - * @param status The status to associate. - */ - public ResourceException(Status status, Request request, Response response) { - this(status, (status == null) ? null : status.getThrowable(), request, response); - } - - /** - * Constructor. - * - * @param status The status to copy. - * @param description The description of the encapsulated status. - */ - public ResourceException(Status status, String description) { - this(new Status(status, description)); - } - - /** - * Constructor. - * - * @param status The status to copy. - * @param description The description of the encapsulated status. - * @param cause The wrapped cause error or exception. - */ - public ResourceException(Status status, String description, Throwable cause) { - this(new Status(status, cause, null, description), cause); - } - - /** - * Constructor. - * - * @param status The status to associate. - * @param cause The wrapped cause error or exception. - */ - public ResourceException(Status status, Throwable cause) { - this(status, cause, null, null); - } - - /** - * Constructor. - * - * @param status The status to associate. - * @param cause The wrapped cause error or exception. - */ - public ResourceException(Status status, Throwable cause, Request request, Response response) { - super((status == null) ? null : status.toString(), cause); - this.status = status; - this.request = request; - this.response = response; - } - - /** - * Constructor that set the status to - * {@link org.restlet.data.Status#SERVER_ERROR_INTERNAL} including the related - * error or exception. - * - * @param cause The wrapped cause error or exception. - */ - public ResourceException(Throwable cause) { - this(new Status(Status.SERVER_ERROR_INTERNAL, cause), cause); - } - - /** - * Returns the request associated to this exception. - * - * @return The request associated to this exception. - */ - public Request getRequest() { - return this.request; - } - - /** - * Returns the response associated to this exception. - * - * @return The response associated to this exception. - */ - public Response getResponse() { - return this.response; - } - - /** - * Returns the status associated to this exception. - * - * @return The status associated to this exception. - */ - public Status getStatus() { - return this.status; - } + private static final long serialVersionUID = 1L; + + /** The status associated with this exception. */ + private transient Status status; + + /** The request associated with this exception. Could be null. */ + private transient Request request; + + /** The response associated with this exception. Could be null. */ + private transient Response response; + + /** + * Constructor. + * + * @param code The specification code of the encapsulated status. + */ + public ResourceException(int code) { + this(new Status(code)); + } + + /** + * Constructor. + * + * @param code The specification code of the encapsulated status. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + */ + public ResourceException(int code, String reasonPhrase) { + this(new Status(code, reasonPhrase)); + } + + /** + * Constructor. + * + * @param code The specification code of the encapsulated status. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + * @param description The description of the encapsulated status. + */ + public ResourceException(int code, String reasonPhrase, String description) { + this(new Status(code, reasonPhrase, description)); + } + + /** + * Constructor. + * + * @param code The specification code of the encapsulated status. + * @param name The name of the encapsulated status. + * @param description The description of the encapsulated status. + * @param uri The URI of the specification describing the method. + */ + public ResourceException(int code, String name, String description, String uri) { + this(new Status(code, name, description, uri)); + } + + /** + * Constructor. + * + * @param code The specification code of the encapsulated status. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + * @param description The description of the encapsulated status. + * @param uri The URI of the specification describing the method. + * @param cause The wrapped cause error or exception. + */ + public ResourceException( + int code, String reasonPhrase, String description, String uri, Throwable cause) { + this(new Status(code, cause, reasonPhrase, description, uri), cause); + } + + /** + * Constructor. + * + * @param code The specification code of the encapsulated status. + * @param cause The wrapped cause error or exception. + */ + public ResourceException(int code, Throwable cause) { + this(new Status(code, cause), cause); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param throwable The related error or exception. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + */ + public ResourceException(int code, Throwable throwable, String reasonPhrase) { + this(new Status(code, throwable, reasonPhrase, null, null)); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param throwable The related error or exception. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + * @param description The longer description. + */ + public ResourceException( + int code, Throwable throwable, String reasonPhrase, String description) { + this(new Status(code, throwable, reasonPhrase, description, null)); + } + + /** + * Constructor. + * + * @param code The specification code. + * @param throwable The related error or exception. + * @param reasonPhrase The short reason phrase displayed next to the status code in an HTTP + * response. + * @param description The longer description. + * @param uri The URI of the specification describing the method. + */ + public ResourceException( + int code, Throwable throwable, String reasonPhrase, String description, String uri) { + this(new Status(code, throwable, reasonPhrase, description, uri)); + } + + /** + * Constructor. + * + * @param status The status to associate. + */ + public ResourceException(Status status) { + this(status, (status == null) ? null : status.getThrowable()); + } + + /** + * Constructor. + * + * @param status The status to associate. + */ + public ResourceException(Status status, Request request, Response response) { + this(status, (status == null) ? null : status.getThrowable(), request, response); + } + + /** + * Constructor. + * + * @param status The status to copy. + * @param description The description of the encapsulated status. + */ + public ResourceException(Status status, String description) { + this(new Status(status, description)); + } + + /** + * Constructor. + * + * @param status The status to copy. + * @param description The description of the encapsulated status. + * @param cause The wrapped cause error or exception. + */ + public ResourceException(Status status, String description, Throwable cause) { + this(new Status(status, cause, null, description), cause); + } + + /** + * Constructor. + * + * @param status The status to associate. + * @param cause The wrapped cause error or exception. + */ + public ResourceException(Status status, Throwable cause) { + this(status, cause, null, null); + } + + /** + * Constructor. + * + * @param status The status to associate. + * @param cause The wrapped cause error or exception. + */ + public ResourceException(Status status, Throwable cause, Request request, Response response) { + super((status == null) ? null : status.toString(), cause); + this.status = status; + this.request = request; + this.response = response; + } + + /** + * Constructor that set the status to {@link org.restlet.data.Status#SERVER_ERROR_INTERNAL} + * including the related error or exception. + * + * @param cause The wrapped cause error or exception. + */ + public ResourceException(Throwable cause) { + this(new Status(Status.SERVER_ERROR_INTERNAL, cause), cause); + } + + /** + * Returns the request associated with this exception. + * + * @return The request associated with this exception. + */ + public Request getRequest() { + return this.request; + } + + /** + * Returns the response associated with this exception. + * + * @return The response associated with this exception. + */ + public Response getResponse() { + return this.response; + } + + /** + * Returns the status associated with this exception. + * + * @return The status associated with this exception. + */ + public Status getStatus() { + return this.status; + } } diff --git a/org.restlet/src/main/java/org/restlet/resource/Result.java b/org.restlet/src/main/java/org/restlet/resource/Result.java index 43ab57d88e..3a7a6b1955 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Result.java +++ b/org.restlet/src/main/java/org/restlet/resource/Result.java @@ -1,34 +1,32 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** * Callback interface for asynchronous tasks. - * + * * @param The class of the result object returned in case of success. * @author Jerome Louvel */ public interface Result { - /** - * Method called back by the associated object when a failure is detected. - * - * @param caught The exception or error caught. - */ - void onFailure(Throwable caught); - - /** - * Method called back by the associated object in case of success. - * - * @param result The result object. - */ - void onSuccess(T result); + /** + * Method called back by the associated object when a failure is detected. + * + * @param caught The exception or error caught. + */ + void onFailure(Throwable caught); + /** + * Method called back by the associated object in case of success. + * + * @param result The result object. + */ + void onSuccess(T result); } diff --git a/org.restlet/src/main/java/org/restlet/resource/ServerResource.java b/org.restlet/src/main/java/org/restlet/resource/ServerResource.java index 6b822a8c8a..a1528c2332 100644 --- a/org.restlet/src/main/java/org/restlet/resource/ServerResource.java +++ b/org.restlet/src/main/java/org/restlet/resource/ServerResource.java @@ -1,17 +1,35 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; -import org.restlet.*; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.Uniform; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.CookieSetting; +import org.restlet.data.Dimension; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.data.ServerInfo; import org.restlet.data.Status; -import org.restlet.data.*; import org.restlet.engine.resource.AnnotationInfo; import org.restlet.engine.resource.AnnotationUtils; import org.restlet.engine.resource.MethodAnnotationInfo; @@ -25,1685 +43,1611 @@ import org.restlet.service.ConverterService; import org.restlet.util.Series; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.logging.Level; - /** - * Base class for server-side resources. It acts as a wrapper to a given call, - * including the incoming {@link Request} and the outgoing {@link Response}. + * Base class for server-side resources. It acts as a wrapper to a given call, including the + * incoming {@link Request} and the outgoing {@link Response}.
*
+ * It's life cycle is managed by a {@link Finder} created either explicitly or more likely + * implicitly when your {@link ServerResource} subclass is attached to a {@link Filter} or a {@link + * Router} via the {@link Filter#setNext(Class)} or {@link Router#attach(String, Class)} methods for + * example. After instantiation using the default constructor, the final {@link #init(Context, + * Request, Response)} method is invoked, setting the context, request, and response. You can + * intercept this by overriding the {@link #doInit()} method. Then, if the response status is still + * a success, the {@link #handle()} method is invoked to actually handle the call. Finally, the + * final {@link #release()} method is invoked to do the necessary cleanup, which you can intercept + * by overriding the {@link #doRelease()} method. During this life cycle, if any exception is + * caught, then the {@link #doCatch(Throwable)} method is invoked.
*
- * It's life cycle is managed by a {@link Finder} created either explicitly or - * more likely implicitly when your {@link ServerResource} subclass is attached - * to a {@link Filter} or a {@link Router} via the {@link Filter#setNext(Class)} - * or {@link Router#attach(String, Class)} methods for example. After - * instantiation using the default constructor, the final - * {@link #init(Context, Request, Response)} method is invoked, setting the - * context, request and response. You can intercept this by overriding the - * {@link #doInit()} method. Then, if the response status is still a success, - * the {@link #handle()} method is invoked to actually handle the call. Finally, - * the final {@link #release()} method is invoked to do the necessary clean-up, - * which you can intercept by overriding the {@link #doRelease()} method. During - * this life cycle, if any exception is caught, then the - * {@link #doCatch(Throwable)} method is invoked.
+ * Note that when an annotated method manually sets the response entity, if this entity is + * available, then it will be preserved and the result of the annotated method ignored.
*
- * Note that when an annotated method manually sets the response entity, if this - * entity is available then it will be preserved and the result of the annotated - * method ignored.
+ * In addition, there are two ways to declare representation variants, one is based on the {@link + * #getVariants()} method and another one on the annotated methods. Both approaches can't, however, + * be used at the same time for now.
*
- * In addition, there are two ways to declare representation variants, one is - * based on the {@link #getVariants()} method and another one on the annotated - * methods. Both approaches can't however be used at the same time for now.
- *
- * Concurrency note: contrary to the {@link org.restlet.Uniform} class and its - * main {@link Restlet} subclass where a single instance can handle several - * calls concurrently, one instance of {@link ServerResource} is created for - * each call handled and accessed by only one thread at a time. - * + * Concurrency note: contrary to the {@link org.restlet.Uniform} class and its main {@link Restlet} + * subclass where a single instance can handle several calls concurrently, one instance of {@link + * ServerResource} is created for each call handled and accessed by only one thread at a time. + * * @author Jerome Louvel */ public abstract class ServerResource extends Resource { - /** Indicates if annotations are supported. */ - private volatile boolean annotated; - - /** Indicates if conditional handling is enabled. */ - private volatile boolean conditional; - - /** The description. */ - private volatile String description; - - /** Indicates if the identified resource exists. */ - private volatile boolean existing; - - /** The display name. */ - private volatile String name; - - /** Indicates if content negotiation of response entities is enabled. */ - private volatile boolean negotiated; - - /** Modifiable list of variants. */ - private volatile List variants; - - /** - * Initializer block to ensure that the basic properties are initialized - * consistently across constructors. - */ - { - this.annotated = true; - this.conditional = true; - this.existing = true; - this.negotiated = true; - this.variants = null; - } - - /** - * Default constructor. Note that the - * {@link #init(Context, Request, Response)}() method will be invoked right - * after the creation of the resource. - */ - public ServerResource() { - } - - /** - * Ask the connector to abort the related network connection, for example - * immediately closing the socket. - */ - public void abort() { - getResponse().abort(); - } - - /** - * Asks the response to immediately commit making it ready to be sent back to - * the client. Note that all server connectors don't necessarily support this - * feature. - */ - public void commit() { - getResponse().commit(); - } - - /** - * Deletes the resource and all its representations. This method is only invoked - * if content negotiation has been disabled as indicated by the - * {@link #isNegotiated()}, otherwise the {@link #delete(Variant)} method is - * invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. - * - * @return The optional response entity. - * @throws ResourceException - * @see HTTP DELETE method - */ - protected Representation delete() throws ResourceException { - Representation result = null; - MethodAnnotationInfo annotationInfo; - - try { - annotationInfo = getAnnotation(Method.DELETE); - - if (annotationInfo != null) { - result = doHandle(annotationInfo, null); - } else { - doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - } catch (IOException e) { - throw new ResourceException(e); - } - - return result; - } - - /** - * Deletes the resource and all its representations. A variant parameter is - * passed to indicate which representation should be returned if any.
- *
- * This method is only invoked if content negotiation has been enabled as - * indicated by the {@link #isNegotiated()}, otherwise the {@link #delete()} - * method is invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. - * - * @param variant The variant of the response entity. - * @return The optional response entity. - * @throws ResourceException - * @see #get(Variant) - * @see HTTP DELETE method - */ - protected Representation delete(Variant variant) throws ResourceException { - Representation result = null; - - if (variant instanceof VariantInfo) { - result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); - } else { - doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - - return result; - } - - /** - * Describes the available variants to help client-side content negotiation. - * Return null by default. - * - * @return The description of available variants. - */ - protected Representation describeVariants() { - Representation result = null; - - // The list of all variants is transmitted to the client - // final ReferenceList refs = new ReferenceList(variants.size()); - // for (final Variant variant : variants) { - // if (variant.getIdentifier() != null) { - // refs.add(variant.getIdentifier()); - // } - // } - // - // result = refs.getTextRepresentation(); - return result; - } - - /** - * Invoked when an error or an exception is caught during initialization, - * handling or releasing. By default, updates the responses's status with the - * result of - * {@link org.restlet.service.StatusService#toStatus(Throwable, Resource)}. - * - * @param throwable The caught error or exception. - */ - protected void doCatch(Throwable throwable) { - Level level = Level.INFO; - Status status = getStatusService().toStatus(throwable, this); - - if (status.isServerError()) { - level = Level.SEVERE; - } else if (status.isConnectorError()) { - level = Level.INFO; - } else if (status.isClientError()) { - level = Level.FINE; - } - - getLogger().log(level, "Exception or error caught in server resource", throwable); - - if (getResponse() != null) { - getResponse().setStatus(status); - Representation errorEntity = getStatusService().toRepresentation(status, this); - getResponse().setEntity(errorEntity); - } - } - - /** - * Handles a call by first verifying the optional request conditions and - * continue the processing if possible. Note that in order to evaluate those - * conditions, {@link #getInfo()} or {@link #getInfo(Variant)} methods might be - * invoked. - * - * @return The response entity. - * @throws ResourceException - */ - protected Representation doConditionalHandle() throws ResourceException { - Representation result = null; - - if (getConditions().hasSome()) { - RepresentationInfo resultInfo = null; - - if (existing) { - if (isNegotiated()) { - Variant preferredVariant = getPreferredVariant(getVariants(Method.GET)); - - if (preferredVariant == null && getConnegService().isStrict()) { - doError(Status.CLIENT_ERROR_NOT_ACCEPTABLE); - } else { - resultInfo = doGetInfo(preferredVariant); - } - } else { - resultInfo = doGetInfo(); - } - - if (resultInfo == null) { - if ((getStatus() == null) - || (getStatus().isSuccess() && !Status.SUCCESS_NO_CONTENT.equals(getStatus()))) { - doError(Status.CLIENT_ERROR_NOT_FOUND); - } else { - // Keep the current status as the developer might - // prefer a special status like 'method not authorized'. - } - } else { - Status status = getConditions().getStatus(getMethod(), resultInfo); - - if (status != null) { - if (status.isError()) { - doError(status); - } else { - setStatus(status); - } - } - } - } else { - Status status = getConditions().getStatus(getMethod(), resultInfo); - - if (status != null) { - if (status.isError()) { - doError(status); - } else { - setStatus(status); - } - } - } - - if ((Method.GET.equals(getMethod()) || Method.HEAD.equals(getMethod())) - && resultInfo instanceof Representation) { - result = (Representation) resultInfo; - } else if ((getStatus() != null) && getStatus().isSuccess()) { - // Conditions were passed successfully, continue the normal - // processing. - if (isNegotiated()) { - // Reset the list of variants, as the method differs. - getVariants().clear(); - result = doNegotiatedHandle(); - } else { - result = doHandle(); - } - } - } else { - if (isNegotiated()) { - result = doNegotiatedHandle(); - } else { - result = doHandle(); - } - } - - return result; - } - - /** - * By default, it sets the status on the response. - */ - @Override - protected void doError(Status errorStatus) { - setStatus(errorStatus); - } - - /** - * Returns a descriptor of the response entity returned by a {@link Method#GET} - * call. - * - * @return The response entity. - * @throws ResourceException - */ - private RepresentationInfo doGetInfo() throws ResourceException { - RepresentationInfo result = null; - MethodAnnotationInfo annotationInfo; - - try { - annotationInfo = getAnnotation(Method.GET); - - if (annotationInfo != null) { - result = doHandle(annotationInfo, null); - } else { - result = getInfo(); - } - } catch (IOException e) { - throw new ResourceException(e); - } - - return result; - } - - /** - * Returns a descriptor of the response entity returned by a negotiated - * {@link Method#GET} call. - * - * @param variant The selected variant descriptor. - * @return The response entity descriptor. - * @throws ResourceException - */ - private RepresentationInfo doGetInfo(Variant variant) throws ResourceException { - RepresentationInfo result = null; - - if (variant != null) { - if (variant instanceof VariantInfo) { - result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); - } else if (variant instanceof RepresentationInfo) { - result = (RepresentationInfo) variant; - } else { - result = getInfo(variant); - } - } else { - result = doGetInfo(); - } - - return result; - } - - /** - * Effectively handles a call without content negotiation of the response - * entity. The default behavior is to dispatch the call to one of the - * {@link #get()}, {@link #post(Representation)}, {@link #put(Representation)}, - * {@link #delete()}, {@link #head()} or {@link #options()} methods. - * - * @return The response entity. - * @throws ResourceException - */ - protected Representation doHandle() throws ResourceException { - Representation result = null; - Method method = getMethod(); - - if (method == null) { - setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "No method specified"); - } else { - if (method.equals(Method.PUT)) { - result = put(getRequestEntity()); - } else if (method.equals(Method.PATCH)) { - result = patch(getRequestEntity()); - } else if (isExisting()) { - if (method.equals(Method.GET)) { - result = get(); - } else if (method.equals(Method.POST)) { - result = post(getRequestEntity()); - } else if (method.equals(Method.DELETE)) { - result = delete(); - } else if (method.equals(Method.HEAD)) { - result = head(); - } else if (method.equals(Method.OPTIONS)) { - result = options(); - } else { - result = doHandle(method, getQuery(), getRequestEntity()); - } - } else { - doError(Status.CLIENT_ERROR_NOT_FOUND); - } - } - - return result; - } - - /** - * Effectively handles a call with content negotiation of the response entity - * using an annotated method. - * - * @param annotationInfo The annotation descriptor. - * @param variant The response variant expected (can be null). - * @return The response entity. - * @throws ResourceException - */ - protected Representation doHandle(MethodAnnotationInfo annotationInfo, Variant variant) throws ResourceException { - Representation result = null; - Class[] parameterTypes = annotationInfo.getJavaInputTypes(); - - // Invoke the annotated method and get the resulting object. - Object resultObject = null; - - try { - if (parameterTypes.length > 0) { - List parameters = new ArrayList(); - Object parameter = null; - - for (Class parameterType : parameterTypes) { - if (Variant.class.equals(parameterType)) { - parameters.add(variant); - } else { - if (getRequestEntity() != null && getRequestEntity().isAvailable() - && getRequestEntity().getSize() != 0) { - // Assume there is content to be read. - // NB: it does not handle the case where the size is - // unknown, but there is no content. - parameter = toObject(getRequestEntity(), parameterType); - - if (parameter == null) { - throw new ResourceException(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE); - } - } else { - parameter = null; - } - - parameters.add(parameter); - } - } - - resultObject = annotationInfo.getJavaMethod().invoke(this, parameters.toArray()); - } else { - resultObject = annotationInfo.getJavaMethod().invoke(this); - } - - if (resultObject != null) { - result = toRepresentation(resultObject, variant); - } - - } catch (IllegalArgumentException | IllegalAccessException | IOException e) { - throw new ResourceException(e); - } catch (InvocationTargetException e) { - if (e.getTargetException() instanceof ResourceException) { - throw (ResourceException) e.getTargetException(); - } - - throw new ResourceException(e.getTargetException()); - } + /** Indicates if annotations are supported. */ + private volatile boolean annotated; + + /** Indicates if conditional handling is enabled. */ + private volatile boolean conditional; + + /** The description. */ + private volatile String description; + + /** Indicates if the identified resource exists. */ + private volatile boolean existing; + + /** The display name. */ + private volatile String name; + + /** Indicates if content negotiation of response entities is enabled. */ + private volatile boolean negotiated; + + /** Modifiable list of variants. */ + private volatile List variants; + + /** + * Initializer block to ensure that the basic properties are initialized consistently across + * constructors. + */ + { + this.annotated = true; + this.conditional = true; + this.existing = true; + this.negotiated = true; + this.variants = null; + } + + /** + * Default constructor. Note that the {@link #init(Context, Request, Response)}() method will be + * invoked right after the creation of the resource. + */ + protected ServerResource() {} + + /** + * Ask the connector to abort the related network connection, for example immediately closing + * the socket. + */ + public void abort() { + getResponse().abort(); + } + + /** + * Asks the response to immediately commit making it ready to be sent back to the client. Note + * that all server connectors don't necessarily support this feature. + */ + public void commit() { + getResponse().commit(); + } + + /** + * Deletes the resource and all its representations. This method is only invoked if content + * negotiation has been disabled as indicated by the {@link #isNegotiated()}, otherwise the + * {@link #delete(Variant)} method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. + * + * @return The optional response entity. + * @throws ResourceException + * @see HTTP DELETE + * method + */ + protected Representation delete() throws ResourceException { + Representation result = null; + MethodAnnotationInfo annotationInfo = getAnnotation(Method.DELETE); + + if (annotationInfo != null) { + result = doHandle(annotationInfo, null); + } else { + doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + + return result; + } + + /** + * Deletes the resource and all its representations. A variant parameter is passed to indicate + * which representation should be returned, if any.
+ *
+ * This method is only invoked if content negotiation has been enabled as indicated by the + * {@link #isNegotiated()}, otherwise the {@link #delete()} method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. + * + * @param variant The variant of the response entity. + * @return The optional response entity. + * @throws ResourceException + * @see #get(Variant) + * @see HTTP DELETE + * method + */ + protected Representation delete(Variant variant) throws ResourceException { + Representation result = null; + + if (variant instanceof VariantInfo) { + result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); + } else { + doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + + return result; + } + + /** + * Describes the available variants to help client-side content negotiation. Return null by + * default. + * + * @return The description of available variants. + */ + protected Representation describeVariants() { + Representation result = null; + + // The list of all variants is transmitted to the client + // final ReferenceList refs = new ReferenceList(variants.size()); + // for (final Variant variant : variants) { + // if (variant.getIdentifier() != null) { + // refs.add(variant.getIdentifier()); + // } + // } + // + // result = refs.getTextRepresentation(); + return result; + } + + /** + * Invoked when an error or an exception is caught during initialization, handling, or + * releasing. By default, updates the response's status with the result of {@link + * org.restlet.service.StatusService#toStatus(Throwable, Resource)}. + * + * @param throwable The caught error or exception. + */ + protected void doCatch(Throwable throwable) { + final Level level; + Status status = getStatusService().toStatus(throwable, this); + + if (status.isServerError()) { + level = Level.SEVERE; + } else if (status.isConnectorError()) { + level = Level.INFO; + } else if (status.isClientError()) { + level = Level.FINE; + } else { + level = Level.INFO; + } + + getLogger().log(level, "Exception or error caught in server resource", throwable); + + if (getResponse() != null) { + getResponse().setStatus(status); + Representation errorEntity = getStatusService().toRepresentation(status, this); + getResponse().setEntity(errorEntity); + } + } + + /** + * Handles a call by first verifying the optional request conditions and continue the processing + * if possible. Note that to evaluate those conditions, {@link #getInfo()} or {@link + * #getInfo(Variant)} methods might be invoked. + * + * @return The response entity. + * @throws ResourceException + */ + protected Representation doConditionalHandle() throws ResourceException { + Representation result = null; + + if (getConditions().hasSome()) { + RepresentationInfo resultInfo = null; + + if (existing) { + if (isNegotiated()) { + Variant preferredVariant = getPreferredVariant(getVariants(Method.GET)); + + if (preferredVariant == null && getConnegService().isStrict()) { + doError(Status.CLIENT_ERROR_NOT_ACCEPTABLE); + } else { + resultInfo = doGetInfo(preferredVariant); + } + } else { + resultInfo = doGetInfo(); + } + + if (resultInfo == null) { + if ((getStatus() == null) + || (getStatus().isSuccess() + && !Status.SUCCESS_NO_CONTENT.equals(getStatus()))) { + doError(Status.CLIENT_ERROR_NOT_FOUND); + } else { + // Keep the current status as the developer might + // prefer a special status like 'method not authorized'. + } + } else { + Status status = getConditions().getStatus(getMethod(), resultInfo); + + if (status != null) { + if (status.isError()) { + doError(status); + } else { + setStatus(status); + } + } + } + } else { + Status status = getConditions().getStatus(getMethod(), resultInfo); + + if (status != null) { + if (status.isError()) { + doError(status); + } else { + setStatus(status); + } + } + } + + if ((Method.GET.equals(getMethod()) || Method.HEAD.equals(getMethod())) + && resultInfo instanceof Representation representation) { + result = representation; + } else if ((getStatus() != null) && getStatus().isSuccess()) { + // Conditions were passed successfully, continue the normal + // processing. + if (isNegotiated()) { + // Reset the list of variants, as the method differs. + getVariants().clear(); + result = doNegotiatedHandle(); + } else { + result = doHandle(); + } + } + } else { + if (isNegotiated()) { + result = doNegotiatedHandle(); + } else { + result = doHandle(); + } + } + + return result; + } + + /** By default, it sets the status on the response. */ + @Override + protected void doError(Status errorStatus) { + setStatus(errorStatus); + } + + /** + * Returns a descriptor of the response entity returned by a {@link Method#GET} call. + * + * @return The response entity. + * @throws ResourceException + */ + private RepresentationInfo doGetInfo() throws ResourceException { + final RepresentationInfo result; + + MethodAnnotationInfo annotationInfo = getAnnotation(Method.GET); + + if (annotationInfo != null) { + result = doHandle(annotationInfo, null); + } else { + result = getInfo(); + } + + return result; + } + + /** + * Returns a descriptor of the response entity returned by a negotiated {@link Method#GET} call. + * + * @param variant The selected variant descriptor. + * @return The response entity descriptor. + * @throws ResourceException + */ + private RepresentationInfo doGetInfo(Variant variant) throws ResourceException { + final RepresentationInfo result; + + if (variant != null) { + if (variant instanceof VariantInfo) { + result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); + } else if (variant instanceof RepresentationInfo) { + result = (RepresentationInfo) variant; + } else { + result = getInfo(variant); + } + } else { + result = doGetInfo(); + } + + return result; + } + + /** + * Effectively handles a call without content negotiation of the response entity. The default + * behavior is to dispatch the call to one of the {@link #get()}, {@link #post(Representation)}, + * {@link #put(Representation)}, {@link #delete()}, {@link #head()} or {@link #options()} + * methods. + * + * @return The response entity. + * @throws ResourceException + */ + protected Representation doHandle() throws ResourceException { + Representation result = null; + Method method = getMethod(); + + if (method == null) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "No method specified"); + } else { + if (method.equals(Method.PUT)) { + result = put(getRequestEntity()); + } else if (method.equals(Method.PATCH)) { + result = patch(getRequestEntity()); + } else if (isExisting()) { + if (method.equals(Method.GET)) { + result = get(); + } else if (method.equals(Method.POST)) { + result = post(getRequestEntity()); + } else if (method.equals(Method.DELETE)) { + result = delete(); + } else if (method.equals(Method.HEAD)) { + result = head(); + } else if (method.equals(Method.OPTIONS)) { + result = options(); + } else { + result = doHandle(method, getQuery(), getRequestEntity()); + } + } else { + doError(Status.CLIENT_ERROR_NOT_FOUND); + } + } + + return result; + } + + /** + * Effectively handles a call with content negotiation of the response entity using an annotated + * method. + * + * @param annotationInfo The annotation descriptor. + * @param variant The response variant expected (can be null). + * @return The response entity. + * @throws ResourceException + */ + protected Representation doHandle(MethodAnnotationInfo annotationInfo, Variant variant) + throws ResourceException { + Representation result = null; + Class[] parameterTypes = annotationInfo.getJavaInputTypes(); + + // Invoke the annotated method and get the resulting object. + Object resultObject = null; + + try { + if (parameterTypes.length > 0) { + List parameters = new ArrayList<>(); + Object parameter = null; + + for (Class parameterType : parameterTypes) { + if (Variant.class.equals(parameterType)) { + parameters.add(variant); + } else { + if (getRequestEntity() != null + && getRequestEntity().isAvailable() + && getRequestEntity().getSize() != 0) { + // Assume there is content to be read. + // NB: it does not handle the case where the size is + // unknown, but there is no content. + parameter = toObject(getRequestEntity(), parameterType); + + if (parameter == null) { + throw new ResourceException( + Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE); + } + } else { + parameter = null; + } + + parameters.add(parameter); + } + } + + resultObject = annotationInfo.getJavaMethod().invoke(this, parameters.toArray()); + } else { + resultObject = annotationInfo.getJavaMethod().invoke(this); + } + + if (resultObject != null) { + result = toRepresentation(resultObject, variant); + } + + } catch (IllegalArgumentException | IllegalAccessException | IOException e) { + throw new ResourceException(e); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof ResourceException resourceException) { + throw resourceException; + } + + throw new ResourceException(e.getTargetException()); + } + + return result; + } + + /** + * Handles a call and checks the request's method and entity. If the method is not supported, + * the response status is set to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. If the request's entity is no + * supported, the response status is set to {@link Status#CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE}. + * + * @param method The request method. + * @param query The query parameters. + * @param entity The request entity (can be null, or unavailable). + * @return The response entity. + */ + private Representation doHandle(Method method, Form query, Representation entity) + throws ResourceException { + Representation result = null; + + if (getAnnotation(method) != null) { + // We know the method is supported, let's check the entity. + MethodAnnotationInfo annotationInfo = getAnnotation(method, query, entity); + + if (annotationInfo != null) { + result = doHandle(annotationInfo, null); + } else { + // The request entity is not supported. + doError(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE); + } + } else { + doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + + return result; + } + + /** + * Effectively handles a call with content negotiation of the response entity. The default + * behavior is to dispatch the call to one of the {@link #get(Variant)}, {@link + * #post(Representation, Variant)}, {@link #put(Representation, Variant)}, {@link + * #delete(Variant)}, {@link #head(Variant)} or {@link #options(Variant)} methods. + * + * @param variant The response variant expected. + * @return The response entity. + * @throws ResourceException + */ + protected Representation doHandle(Variant variant) throws ResourceException { + Representation result = null; + Method method = getMethod(); + + if (method == null) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "No method specified"); + } else { + if (method.equals(Method.PUT)) { + result = put(getRequestEntity(), variant); + } else if (method.equals(Method.PATCH)) { + result = patch(getRequestEntity(), variant); + } else if (isExisting()) { + if (method.equals(Method.GET)) { + if (variant instanceof Representation representation) { + result = representation; + } else { + result = get(variant); + } + } else if (method.equals(Method.POST)) { + result = post(getRequestEntity(), variant); + } else if (method.equals(Method.DELETE)) { + result = delete(variant); + } else if (method.equals(Method.HEAD)) { + if (variant instanceof Representation representation) { + result = representation; + } else { + result = head(variant); + } + } else if (method.equals(Method.OPTIONS)) { + if (variant instanceof Representation representation) { + result = representation; + } else { + result = options(variant); + } + } else if (variant instanceof VariantInfo variantInfo) { + result = doHandle(variantInfo.getAnnotationInfo(), variant); + } else { + doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + } else { + doError(Status.CLIENT_ERROR_NOT_FOUND); + } + } + + return result; + } + + /** + * Effectively handles a call with content negotiation of the response entity. The default + * behavior is to dispatch the call to call a matching annotated method or one of the {@link + * #get(Variant)}, {@link #post(Representation, Variant)}, {@link #put(Representation, + * Variant)}, {@link #delete(Variant)}, {@link #head(Variant)} or {@link #options(Variant)} + * methods.
+ *
+ * If no acceptable variant is found, the {@link + * org.restlet.data.Status#CLIENT_ERROR_NOT_ACCEPTABLE} status is set. + * + * @return The response entity. + * @throws ResourceException + */ + protected Representation doNegotiatedHandle() throws ResourceException { + Representation result = null; + + if ((getVariants() != null) && (!getVariants().isEmpty())) { + Variant preferredVariant = getPreferredVariant(getVariants()); + + if (preferredVariant == null) { + // No variant was found matching the client preferences + doError(Status.CLIENT_ERROR_NOT_ACCEPTABLE); + result = describeVariants(); + } else { + // Update the variant dimensions used for content + // negotiation + updateDimensions(); + result = doHandle(preferredVariant); + } + } else { + // No variant declared for this method. + result = doHandle(); + } + + return result; + } + + /** + * Returns a full representation. This method is only invoked if content negotiation has been + * disabled as indicated by the {@link #isNegotiated()} , otherwise the {@link #get(Variant)} + * method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. + * + * @return The resource's representation. + * @throws ResourceException + * @see HTTP GET + * method + */ + protected Representation get() throws ResourceException { + Representation result = null; + MethodAnnotationInfo annotationInfo = getAnnotation(Method.GET); + + if (annotationInfo != null) { + result = doHandle(annotationInfo, null); + } else { + doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + + return result; + } + + /** + * Returns a full representation for a given variant. A variant parameter is passed to indicate + * which representation should be returned, if any.
+ *
+ * This method is only invoked if content negotiation has been enabled as indicated by the + * {@link #isNegotiated()}, otherwise the {@link #get()} method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
+ * + * @param variant The variant whose full representation must be returned. + * @return The resource's representation. + * @throws ResourceException + * @see #get(Variant) + */ + protected Representation get(Variant variant) throws ResourceException { + Representation result = null; + + if (variant instanceof VariantInfo) { + result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); + } else { + doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + + return result; + } + + /** + * Returns the first annotation descriptor matching the given method. + * + * @param method The method to match. + * @return The annotation descriptor. + */ + protected MethodAnnotationInfo getAnnotation(Method method) { + return getAnnotation(method, getQuery(), null); + } + + /** + * Returns the first annotation descriptor matching the given method. + * + * @param method The method to match. + * @param query The query parameters. + * @param entity The request entity or null. + * @return The annotation descriptor. + */ + protected MethodAnnotationInfo getAnnotation(Method method, Form query, Representation entity) { + if (isAnnotated()) { + return AnnotationUtils.getInstance() + .getMethodAnnotation( + getAnnotations(), + method, + query, + entity, + getMetadataService(), + getConverterService()); + } + + return null; + } + + /** + * Returns the annotation descriptors. + * + * @return The annotation descriptors. + */ + protected List getAnnotations() { + return isAnnotated() ? AnnotationUtils.getInstance().getAnnotations(getClass()) : null; + } + + /** + * Returns the attribute value by looking up the given name in the request attributes maps. The + * toString() method is then invoked on the attribute value. This is typically used for + * variables that are declared in the URI template used to route the call to this resource. + * + * @param name The attribute name. + * @return The request attribute value. + */ + public String getAttribute(String name) { + Object value = getRequestAttributes().get(name); + return (value == null) ? null : value.toString(); + } + + /** + * Returns the description. + * + * @return The description + */ + public String getDescription() { + return this.description; + } + + /** + * Returns information about the resource's representation. This metadata is important for + * conditional method processing. The advantage over the complete {@link Representation} class + * is that it is much lighter to create. This method is only invoked if content negotiation has + * been disabled as indicated by the {@link #isNegotiated()}, otherwise the {@link + * #getInfo(Variant)} method is invoked.
+ *
+ * The default behavior is to invoke the {@link #get()} method. + * + * @return Information about the resource's representation. + * @throws ResourceException + */ + protected RepresentationInfo getInfo() throws ResourceException { + return get(); + } + + /** + * Returns information about the resource's representation. This metadata is important for + * conditional method processing. The advantage over the complete {@link Representation} class + * is that it is much lighter to create. A variant parameter is passed to indicate which + * representation should be returned, if any.
+ *
+ * This method is only invoked if content negotiation has been enabled as indicated by the + * {@link #isNegotiated()}, otherwise the {@link #getInfo(Variant)} method is invoked.
+ *
+ * The default behavior is to invoke the {@link #get(Variant)} method. + * + * @param variant The variant whose representation information must be returned. + * @return Information about the resource's representation. + * @throws ResourceException + */ + protected RepresentationInfo getInfo(Variant variant) throws ResourceException { + return get(variant); + } + + /** + * Returns the display name. + * + * @return The display name. + */ + public String getName() { + return this.name; + } + + /** + * Returns the callback invoked after sending the response. + * + * @return The callback invoked after sending the response. + */ + public Uniform getOnSent() { + return getResponse().getOnSent(); + } + + /** + * Returns the preferred variant among a list of available variants. The selection is based on + * the client preferences using the {@link + * org.restlet.service.ConnegService#getPreferredVariant(List, Request, + * org.restlet.service.MetadataService)} method. + * + * @param variants The available variants. + * @return The preferred variant. + */ + protected Variant getPreferredVariant(List variants) { + Variant result = null; + + // If variants were found, select the best matching one + if ((variants != null) && (!variants.isEmpty())) { + result = + getConnegService() + .getPreferredVariant(variants, getRequest(), getMetadataService()); + } + + return result; + } + + /** + * Retrieves an existing role or creates a new one if needed based on its name. Note that a null + * description will be set if the role has to be created. + * + * @param name The role name to find or create. + * @return The role found or created. + */ + public Role getRole(String name) { + return Role.get(getApplication(), name); + } + + /** + * Returns a modifiable list of exposed variants for the current request method. You can declare + * variants manually by updating the result list, by overriding this method. By default, the + * variants will be provided based on annotated methods. + * + * @return The modifiable list of variants. + * @throws IOException + */ + public List getVariants() { + return getVariants(getMethod()); + } + + /** + * Returns a modifiable list of exposed variants for the given method. You can declare variants + * manually by updating the result list, by overriding this method. By default, the variants + * will be provided based on annotated methods. + * + * @param method The method. + * @return The modifiable list of variants. + */ + protected List getVariants(final Method method) { + + if (this.variants == null && isAnnotated() && hasAnnotations()) { + this.variants = + getVariantsFromAnnotations((Method.HEAD.equals(method)) ? Method.GET : method); + } else if (this.variants == null) { + this.variants = new ArrayList<>(); + } + + return this.variants; + } + + private List getVariantsFromAnnotations(Method method) { + final List result = new ArrayList<>(); + + for (AnnotationInfo annotationInfo : getAnnotations()) { + if (!(annotationInfo instanceof final MethodAnnotationInfo methodAnnotationInfo)) { + continue; + } + + final boolean methodAnnotationIsCompatible = + methodAnnotationInfo.isCompatible( + method, + getQuery(), + getRequestEntity(), + getMetadataService(), + getConverterService()); + + if (!methodAnnotationIsCompatible) { + continue; + } + + final List responseVariants = + methodAnnotationInfo.getResponseVariants( + getMetadataService(), getConverterService()); + + if (responseVariants == null) { + continue; + } + + // Compute an affinity score between this annotation and the input entity. + float score = 0.5f; + if ((getRequest().getEntity() != null) && getRequest().getEntity().isAvailable()) { + final MediaType requestEntityMediaType = getRequest().getEntity().getMediaType(); + final List amts = + getMetadataService().getAllMediaTypes(methodAnnotationInfo.getInput()); + if (amts != null) { + for (MediaType amt : amts) { + if (amt.equals(requestEntityMediaType)) { + score = 1.0f; + } else if (amt.includes(requestEntityMediaType)) { + score = Math.max(0.8f, score); + } else if (amt.isCompatible(requestEntityMediaType)) { + score = Math.max(0.6f, score); + } + } + } + } else if (methodAnnotationInfo.getInput() == null + || methodAnnotationInfo + .getInput() + .isEmpty()) { // the annotation does not declare input media type + score = 1.0f; + } else if (methodAnnotationInfo.getJavaInputTypes() == null + || methodAnnotationInfo.getJavaInputTypes().length + == 0) { // the annotated method does not require an entity + score = 0.9f; + } + + for (Variant variant : responseVariants) { + final VariantInfo variantInfo = new VariantInfo(variant, methodAnnotationInfo); + variantInfo.setInputScore(score); + result.add(variantInfo); + } + } + + return result; + } + + /** + * Handles any call to this resource. The default implementation checks the {@link + * #isConditional()} and {@link #isNegotiated()} method to determine which one of the {@link + * #doConditionalHandle()}, {@link #doNegotiatedHandle()} and {@link #doHandle()} methods should + * be invoked. It also catches any {@link ResourceException} thrown and updates the response + * status using the {@link #setStatus(Status, Throwable, String)} method.
+ *
+ * After handling, if the status is set to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}, then {@link + * #updateAllowedMethods()} is invoked to give the resource a chance to inform the client about + * the allowed methods. + * + * @return The response entity, but this method is still responsible for setting the response + * entity. + */ + @Override + public Representation handle() { + Representation result = null; + + // If the resource is not available after initialization and if this a + // retrieval method, then return a "not found" response. + if (!isExisting() && getMethod().isSafe()) { + doError(Status.CLIENT_ERROR_NOT_FOUND); + } else { + try { + if (isConditional()) { + result = doConditionalHandle(); + } else if (isNegotiated()) { + result = doNegotiatedHandle(); + } else { + result = doHandle(); + } + + if (result != null) { + // If the user manually set the entity, keep it + getResponse().setEntity(result); + } + + } catch (Throwable t) { + doCatch(t); + } finally { + if (Status.CLIENT_ERROR_METHOD_NOT_ALLOWED.equals(getStatus())) { + updateAllowedMethods(); + } else if (Status.SUCCESS_OK.equals(getStatus()) + && (getResponseEntity() == null || !getResponseEntity().isAvailable())) { + getLogger() + .fine( + "A response with a 200 (Ok) status should have an entity. " + + "Changing the status to 204 (No content)."); + setStatus(Status.SUCCESS_NO_CONTENT); + } + } + } + + return result; + } + + /** + * Indicates if annotations were defined on this resource. + * + * @return True if annotations were defined on this resource. + */ + protected boolean hasAnnotations() { + return (getAnnotations() != null) && (!getAnnotations().isEmpty()); + } + + /** + * Returns a representation whose metadata will be returned to the client. This method is only + * invoked if content negotiation has been disabled as indicated by the {@link #isNegotiated()}, + * otherwise the {@link #head(Variant)} method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. + * + * @return The resource's representation. + * @throws ResourceException + * @see HTTP GET + * method + */ + protected Representation head() throws ResourceException { + return get(); + } + + /** + * Returns a representation whose metadata will be returned to the client. A variant parameter + * is passed to indicate which representation should be returned, if any.
+ *
+ * This method is only invoked if content negotiation has been enabled as indicated by the + * {@link #isNegotiated()}, otherwise the {@link #head()} method is invoked.
+ *
+ * The default implementation directly returns the variant if it is already an instance of + * {@link Representation}. In other cases, you need to override this method to provide your own + * implementation. * + * + * @param variant The variant whose full representation must be returned. + * @return The resource's representation. + * @throws ResourceException + * @see #get(Variant) + */ + protected Representation head(Variant variant) throws ResourceException { + return get(variant); + } + + /** + * Indicates if annotations are supported. The default value is true. + * + * @return True if annotations are supported. + */ + public boolean isAnnotated() { + return annotated; + } + + /** + * Indicates if the response should be automatically committed. When processing a request on the + * server-side, setting this property to 'false' let you ask the server connector to wait before + * sending the response back to the client when the initial calling thread returns. This will + * let you do further updates to the response and manually calling {@link #commit()} later on, + * using another thread. + * + * @return True if the response should be automatically committed. + */ + public boolean isAutoCommitting() { + return getResponse().isAutoCommitting(); + } + + /** + * Indicates if the response has already been committed. + * + * @return True if the response has already been committed. + */ + public boolean isCommitted() { + return getResponse().isCommitted(); + } + + /** + * Indicates if conditional handling is enabled. The default value is true. + * + * @return True if conditional handling is enabled. + */ + public boolean isConditional() { + return conditional; + } + + /** + * Indicates if the identified resource exists. The default value is true. + * + * @return True if the identified resource exists. + */ + public boolean isExisting() { + return existing; + } + + /** + * Indicates if the authenticated client user associated with the current request is in the + * given role name. + * + * @param roleName The role name to test. + * @return True if the authenticated subject is in the given role. + */ + public boolean isInRole(String roleName) { + return getClientInfo().getRoles().contains(getRole(roleName)); + } + + /** + * Indicates if content negotiation of response entities is enabled. The default value is true. + * + * @return True if content negotiation of response entities is enabled. + */ + public boolean isNegotiated() { + return this.negotiated; + } + + /** + * Indicates the communication options available for this resource. This method is only invoked + * if content negotiation has been disabled as indicated by the {@link #isNegotiated()}, + * otherwise the {@link #options(Variant)} method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. + * + * @return The optional response entity. + */ + protected Representation options() throws ResourceException { + Representation result = null; + MethodAnnotationInfo annotationInfo = getAnnotation(Method.OPTIONS); + + // Updates the list of allowed methods + updateAllowedMethods(); + + if (annotationInfo != null) { + result = doHandle(annotationInfo, null); + } else { + doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + + return result; + } + + /** + * Indicates the communication options available for this resource. A variant parameter is + * passed to indicate which representation should be returned, if any.
+ *
+ * This method is only invoked if content negotiation has been enabled as indicated by the + * {@link #isNegotiated()}, otherwise the {@link #options()} method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
+ * + * @param variant The variant of the response entity. + * @return The optional response entity. + * @see #get(Variant) + */ + protected Representation options(Variant variant) throws ResourceException { + Representation result = null; + + // Updates the list of allowed methods + updateAllowedMethods(); + + if (variant instanceof VariantInfo) { + result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); + } else { + doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } return result; - } - - /** - * Handles a call and checks the request's method and entity. If the method is - * not supported, the response status is set to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. If the - * request's entity is no supported, the response status is set to - * {@link Status#CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE}. - * - * @param method The request method. - * @param query The query parameters. - * @param entity The request entity (can be null, or unavailable). - * @return The response entity. - * @throws IOException - */ - private Representation doHandle(Method method, Form query, Representation entity) throws ResourceException { - Representation result = null; - - try { - if (getAnnotation(method) != null) { - // We know the method is supported, let's check the entity. - MethodAnnotationInfo annotationInfo = getAnnotation(method, query, entity); - - if (annotationInfo != null) { - result = doHandle(annotationInfo, null); - } else { - // The request entity is not supported. - doError(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE); - } - } else { - doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - } catch (IOException e) { - throw new ResourceException(e); - } - - return result; - } - - /** - * Effectively handles a call with content negotiation of the response entity. - * The default behavior is to dispatch the call to one of the - * {@link #get(Variant)}, {@link #post(Representation,Variant)}, - * {@link #put(Representation,Variant)}, {@link #delete(Variant)}, - * {@link #head(Variant)} or {@link #options(Variant)} methods. - * - * @param variant The response variant expected. - * @return The response entity. - * @throws ResourceException - */ - protected Representation doHandle(Variant variant) throws ResourceException { - Representation result = null; - Method method = getMethod(); - - if (method == null) { - setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "No method specified"); - } else { - if (method.equals(Method.PUT)) { - result = put(getRequestEntity(), variant); - } else if (method.equals(Method.PATCH)) { - result = patch(getRequestEntity(), variant); - } else if (isExisting()) { - if (method.equals(Method.GET)) { - if (variant instanceof Representation) { - result = (Representation) variant; - } else { - result = get(variant); - } - } else if (method.equals(Method.POST)) { - result = post(getRequestEntity(), variant); - } else if (method.equals(Method.DELETE)) { - result = delete(variant); - } else if (method.equals(Method.HEAD)) { - if (variant instanceof Representation) { - result = (Representation) variant; - } else { - result = head(variant); - } - } else if (method.equals(Method.OPTIONS)) { - if (variant instanceof Representation) { - result = (Representation) variant; - } else { - result = options(variant); - } - } else if (variant instanceof VariantInfo) { - result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); - } else { - doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - } else { - doError(Status.CLIENT_ERROR_NOT_FOUND); - } - } - - return result; - } - - /** - * Effectively handles a call with content negotiation of the response entity. - * The default behavior is to dispatch the call to call a matching annotated - * method or one of the {@link #get(Variant)}, - * {@link #post(Representation,Variant)}, {@link #put(Representation,Variant)}, - * {@link #delete(Variant)}, {@link #head(Variant)} or {@link #options(Variant)} - * methods.
- *
- * If no acceptable variant is found, the - * {@link org.restlet.data.Status#CLIENT_ERROR_NOT_ACCEPTABLE} status is set. - * - * @return The response entity. - * @throws ResourceException - */ - protected Representation doNegotiatedHandle() throws ResourceException { - Representation result = null; - - if ((getVariants() != null) && (!getVariants().isEmpty())) { - Variant preferredVariant = getPreferredVariant(getVariants()); - - if (preferredVariant == null) { - // No variant was found matching the client preferences - doError(Status.CLIENT_ERROR_NOT_ACCEPTABLE); - result = describeVariants(); - } else { - // Update the variant dimensions used for content - // negotiation - updateDimensions(); - result = doHandle(preferredVariant); - } - } else { - // No variant declared for this method. - result = doHandle(); - } - - return result; - } - - /** - * Returns a full representation. This method is only invoked if content - * negotiation has been disabled as indicated by the {@link #isNegotiated()} , - * otherwise the {@link #get(Variant)} method is invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. - * - * @return The resource's representation. - * @throws ResourceException - * @see HTTP GET - * method - */ - protected Representation get() throws ResourceException { - Representation result = null; - MethodAnnotationInfo annotationInfo; - - try { - annotationInfo = getAnnotation(Method.GET); - - if (annotationInfo != null) { - result = doHandle(annotationInfo, null); - } else { - doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - } catch (IOException e) { - throw new ResourceException(e); - } - - return result; - } - - /** - * Returns a full representation for a given variant. A variant parameter is - * passed to indicate which representation should be returned if any.
- *
- * This method is only invoked if content negotiation has been enabled as - * indicated by the {@link #isNegotiated()}, otherwise the {@link #get()} method - * is invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
- * - * @param variant The variant whose full representation must be returned. - * @return The resource's representation. - * @see #get(Variant) - * @throws ResourceException - */ - protected Representation get(Variant variant) throws ResourceException { - Representation result = null; - - if (variant instanceof VariantInfo) { - result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); - } else { - doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - - return result; - } - - /** - * Returns the first annotation descriptor matching the given method. - * - * @param method The method to match. - * @return The annotation descriptor. - * @throws IOException - */ - protected MethodAnnotationInfo getAnnotation(Method method) throws IOException { - return getAnnotation(method, getQuery(), null); - } - - /** - * Returns the first annotation descriptor matching the given method. - * - * @param method The method to match. - * @param query The query parameters. - * @param entity The request entity or null. - * @return The annotation descriptor. - * @throws IOException - */ - protected MethodAnnotationInfo getAnnotation(Method method, Form query, Representation entity) throws IOException { - if (isAnnotated()) { - return AnnotationUtils.getInstance().getMethodAnnotation(getAnnotations(), method, query, entity, - getMetadataService(), getConverterService()); - } - - return null; - } - - /** - * Returns the annotation descriptors. - * - * @return The annotation descriptors. - */ - protected List getAnnotations() { - return isAnnotated() ? AnnotationUtils.getInstance().getAnnotations(getClass()) : null; - } - - /** - * Returns the attribute value by looking up the given name in the request - * attributes maps. The toString() method is then invoked on the attribute - * value. This is typically used for variables that are declared in the URI - * template used to route the call to this resource. - * - * @param name The attribute name. - * @return The request attribute value. - */ - public String getAttribute(String name) { - Object value = getRequestAttributes().get(name); - return (value == null) ? null : value.toString(); - } - - /** - * Returns the description. - * - * @return The description - */ - public String getDescription() { - return this.description; - } - - /** - * Returns information about the resource's representation. Those metadata are - * important for conditional method processing. The advantage over the complete - * {@link Representation} class is that it is much lighter to create. This - * method is only invoked if content negotiation has been disabled as indicated - * by the {@link #isNegotiated()}, otherwise the {@link #getInfo(Variant)} - * method is invoked.
- *
- * The default behavior is to invoke the {@link #get()} method. - * - * @return Information about the resource's representation. - * @throws ResourceException - */ - protected RepresentationInfo getInfo() throws ResourceException { - return get(); - } - - /** - * Returns information about the resource's representation. Those metadata are - * important for conditional method processing. The advantage over the complete - * {@link Representation} class is that it is much lighter to create. A variant - * parameter is passed to indicate which representation should be returned if - * any.
- *
- * This method is only invoked if content negotiation has been enabled as - * indicated by the {@link #isNegotiated()}, otherwise the - * {@link #getInfo(Variant)} method is invoked.
- *
- * The default behavior is to invoke the {@link #get(Variant)} method. - * - * @param variant The variant whose representation information must be returned. - * @return Information about the resource's representation. - * @throws ResourceException - */ - protected RepresentationInfo getInfo(Variant variant) throws ResourceException { - return get(variant); - } - - /** - * Returns the display name. - * - * @return The display name. - */ - public String getName() { - return this.name; - } - - /** - * Returns the callback invoked after sending the response. - * - * @return The callback invoked after sending the response. - */ - public Uniform getOnSent() { - return getResponse().getOnSent(); - } - - /** - * Returns the preferred variant among a list of available variants. The - * selection is based on the client preferences using the - * {@link org.restlet.service.ConnegService#getPreferredVariant(List, Request, org.restlet.service.MetadataService)} - * method. - * - * @param variants The available variants. - * @return The preferred variant. - */ - protected Variant getPreferredVariant(List variants) { - Variant result = null; - - // If variants were found, select the best matching one - if ((variants != null) && (!variants.isEmpty())) { - result = getConnegService().getPreferredVariant(variants, getRequest(), getMetadataService()); - } - - return result; - } - - /** - * Retrieves an existing role or creates a new one if needed based on its name. - * Note that a null description will be set if the role has to be created. - * - * @param name The role name to find or create. - * @return The role found or created. - */ - public Role getRole(String name) { - return Role.get(getApplication(), name); - } - - /** - * Returns a modifiable list of exposed variants for the current request method. - * You can declare variants manually by updating the result list , by overriding - * this method. By default, the variants will be provided based on annotated - * methods. - * - * @return The modifiable list of variants. - * @throws IOException - */ - public List getVariants() { - return getVariants(getMethod()); - } - - /** - * Returns a modifiable list of exposed variants for the given method. You can - * declare variants manually by updating the result list, by overriding this - * method. By default, the variants will be provided based on annotated methods. - * - * @param method The method. - * @return The modifiable list of variants. - */ - protected List getVariants(final Method method) { - - if (this.variants == null && isAnnotated() && hasAnnotations()) { - this.variants = getVariantsFromAnnotations((Method.HEAD.equals(method)) ? Method.GET : method); - } else if (this.variants == null) { - this.variants = new ArrayList<>(); - } - - return this.variants; - } - - private List getVariantsFromAnnotations(Method method) { - final List result = new ArrayList<>(); - - for (AnnotationInfo annotationInfo : getAnnotations()) { - if (!(annotationInfo instanceof MethodAnnotationInfo)) { - continue; - } - - final MethodAnnotationInfo methodAnnotationInfo = (MethodAnnotationInfo) annotationInfo; - try { - final boolean methodAnnotationIsCompatible = methodAnnotationInfo.isCompatible(method, getQuery(), getRequestEntity(), - getMetadataService(), getConverterService()); - - if (!methodAnnotationIsCompatible) { - continue; - } - - final List responseVariants = methodAnnotationInfo.getResponseVariants(getMetadataService(), getConverterService()); - - if (responseVariants == null) { - continue; - } - - // Compute an affinity score between this annotation and the input entity. - float score = 0.5f; - if ((getRequest().getEntity() != null) && getRequest().getEntity().isAvailable()) { - final MediaType requestEntityMediaType = getRequest().getEntity().getMediaType(); - final List amts = getMetadataService().getAllMediaTypes(methodAnnotationInfo.getInput()); - if (amts != null) { - for (MediaType amt : amts) { - if (amt.equals(requestEntityMediaType)) { - score = 1.0f; - } else if (amt.includes(requestEntityMediaType)) { - score = Math.max(0.8f, score); - } else if (amt.isCompatible(requestEntityMediaType)) { - score = Math.max(0.6f, score); - } - } - } - } else if (methodAnnotationInfo.getInput() == null || methodAnnotationInfo.getInput().isEmpty()) { // the annotation does not declare input media type - score = 1.0f; - } else if (methodAnnotationInfo.getJavaInputTypes() == null || methodAnnotationInfo.getJavaInputTypes().length == 0) { // the annotated method does not require an entity - score = 0.9f; - } - - for (Variant variant : responseVariants) { - final VariantInfo variantInfo = new VariantInfo(variant, methodAnnotationInfo); - variantInfo.setInputScore(score); - result.add(variantInfo); - } - - } catch (IOException e) { - getLogger().log(Level.FINE, "Unable to get variants from annotation", e); - } - } - - return result; - } - - /** - * Handles any call to this resource. The default implementation check the - * {@link #isConditional()} and {@link #isNegotiated()} method to determine - * which one of the {@link #doConditionalHandle()}, - * {@link #doNegotiatedHandle()} and {@link #doHandle()} methods should be - * invoked. It also catches any {@link ResourceException} thrown and updates the - * response status using the {@link #setStatus(Status, Throwable, String)} - * method.
- *
- * After handling, if the status is set to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}, then - * {@link #updateAllowedMethods()} is invoked to give the resource a chance to - * inform the client about the allowed methods. - * - * @return The response entity, but this method is still responsible for setting - * the response entity. - */ - @Override - public Representation handle() { - Representation result = null; - - // If the resource is not available after initialization and if this a - // retrieval method, then return a "not found" response. - if (!isExisting() && getMethod().isSafe()) { - doError(Status.CLIENT_ERROR_NOT_FOUND); - } else { - try { - if (isConditional()) { - result = doConditionalHandle(); - } else if (isNegotiated()) { - result = doNegotiatedHandle(); - } else { - result = doHandle(); - } - - if (result != null) { - // If the user manually set the entity, keep it - getResponse().setEntity(result); - } - - } catch (Throwable t) { - doCatch(t); - } finally { - if (Status.CLIENT_ERROR_METHOD_NOT_ALLOWED.equals(getStatus())) { - updateAllowedMethods(); - } else if (Status.SUCCESS_OK.equals(getStatus()) - && (getResponseEntity() == null || !getResponseEntity().isAvailable())) { - getLogger().fine("A response with a 200 (Ok) status should have an entity. " - + "Changing the status to 204 (No content)."); - setStatus(Status.SUCCESS_NO_CONTENT); - } - } - } - - return result; - } - - /** - * Indicates if annotations were defined on this resource. - * - * @return True if annotations were defined on this resource. - */ - protected boolean hasAnnotations() { - return (getAnnotations() != null) && (!getAnnotations().isEmpty()); - } - - /** - * Returns a representation whose metadata will be returned to the client. This - * method is only invoked if content negotiation has been disabled as indicated - * by the {@link #isNegotiated()}, otherwise the {@link #head(Variant)} method - * is invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. - * - * @return The resource's representation. - * @throws ResourceException - * @see HTTP GET - * method - */ - protected Representation head() throws ResourceException { - return get(); - } - - /** - * Returns a representation whose metadata will be returned to the client. A - * variant parameter is passed to indicate which representation should be - * returned if any.
- *
- * This method is only invoked if content negotiation has been enabled as - * indicated by the {@link #isNegotiated()}, otherwise the {@link #head()} - * method is invoked.
- *
- * The default implementation directly returns the variant if it is already an - * instance of {@link Representation}. In other cases, you need to override this - * method in order to provide your own implementation. * - * - * @param variant The variant whose full representation must be returned. - * @return The resource's representation. - * @see #get(Variant) - * @throws ResourceException - */ - protected Representation head(Variant variant) throws ResourceException { - return get(variant); - } - - /** - * Indicates if annotations are supported. The default value is true. - * - * @return True if annotations are supported. - */ - public boolean isAnnotated() { - return annotated; - } - - /** - * Indicates if the response should be automatically committed. When processing - * a request on the server-side, setting this property to 'false' let you ask to - * the server connector to wait before sending the response back to the client - * when the initial calling thread returns. This will let you do further updates - * to the response and manually calling {@link #commit()} later on, using - * another thread. - * - * @return True if the response should be automatically committed. - */ - public boolean isAutoCommitting() { - return getResponse().isAutoCommitting(); - } - - /** - * Indicates if the response has already been committed. - * - * @return True if the response has already been committed. - */ - public boolean isCommitted() { - return getResponse().isCommitted(); - } - - /** - * Indicates if conditional handling is enabled. The default value is true. - * - * @return True if conditional handling is enabled. - */ - public boolean isConditional() { - return conditional; - } - - /** - * Indicates if the identified resource exists. The default value is true. - * - * @return True if the identified resource exists. - */ - public boolean isExisting() { - return existing; - } - - /** - * Indicates if the authenticated client user associated to the current request - * is in the given role name. - * - * @param roleName The role name to test. - * @return True if the authenticated subject is in the given role. - */ - public boolean isInRole(String roleName) { - return getClientInfo().getRoles().contains(getRole(roleName)); - } - - /** - * Indicates if content negotiation of response entities is enabled. The default - * value is true. - * - * @return True if content negotiation of response entities is enabled. - */ - public boolean isNegotiated() { - return this.negotiated; - } - - /** - * Indicates the communication options available for this resource. This method - * is only invoked if content negotiation has been disabled as indicated by the - * {@link #isNegotiated()}, otherwise the {@link #options(Variant)} method is - * invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. - * - * @return The optional response entity. - */ - protected Representation options() throws ResourceException { - Representation result = null; - MethodAnnotationInfo annotationInfo; - - try { - annotationInfo = getAnnotation(Method.OPTIONS); - - // Updates the list of allowed methods - updateAllowedMethods(); - - if (annotationInfo != null) { - result = doHandle(annotationInfo, null); - } else { - doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - } catch (IOException e) { - throw new ResourceException(e); - } - - return result; - } - - /** - * Indicates the communication options available for this resource. A variant - * parameter is passed to indicate which representation should be returned if - * any.
- *
- * This method is only invoked if content negotiation has been enabled as - * indicated by the {@link #isNegotiated()}, otherwise the {@link #options()} - * method is invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
- * - * @param variant The variant of the response entity. - * @return The optional response entity. - * @see #get(Variant) - */ - protected Representation options(Variant variant) throws ResourceException { - Representation result = null; - - // Updates the list of allowed methods - updateAllowedMethods(); - - if (variant instanceof VariantInfo) { - result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); - } else { - doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - - return result; - } - - /** - * Apply a patch entity to the current representation of the resource retrieved - * by calling {@link #get()}. By default, the - * {@link ConverterService#applyPatch(Representation, Representation)} method is - * used and then the {@link #put(Representation)} method called. - * - * @param entity The patch entity to apply. - * @return The optional result entity. - * @throws ResourceException - * @see HTTP PATCH method - */ - protected Representation patch(Representation entity) throws ResourceException { - AnnotationInfo annotationInfo; - try { - annotationInfo = getAnnotation(Method.PATCH); - if (annotationInfo != null) { - return doHandle(Method.PATCH, getQuery(), entity); - } else { - // Default implementation - return put(getConverterService().applyPatch(get(), entity)); - // doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - } catch (IOException e) { - throw new ResourceException(e); - } - } - - /** - * Apply a patch entity to the current representation of the resource retrieved - * by calling {@link #get()}. By default, the - * {@link ConverterService#applyPatch(Representation, Representation)} method is - * used and then the {@link #put(Representation, Variant)} method called. - * - * @param entity The patch entity to apply. - * @param variant The variant of the response entity. - * @return The optional result entity. - * @throws ResourceException - * @see HTTP PATCH method - */ - protected Representation patch(Representation entity, Variant variant) throws ResourceException { - Representation result = null; - - try { - if (variant instanceof VariantInfo) { - result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); - } else { - // Default implementation - result = put(getConverterService().applyPatch(get(), entity), variant); - // doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - } catch (IOException e) { - throw new ResourceException(e); - } - return result; - } - - /** - * Posts a representation to the resource at the target URI reference. This - * method is only invoked if content negotiation has been disabled as indicated - * by the {@link #isNegotiated()}, otherwise the - * {@link #post(Representation, Variant)} method is invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. - * - * @param entity The posted entity. - * @return The optional response entity. - * @throws ResourceException - * @see #get(Variant) - * @see HTTP POST - * method - */ - protected Representation post(Representation entity) throws ResourceException { - return doHandle(Method.POST, getQuery(), entity); - } - - /** - * Posts a representation to the resource at the target URI reference. A variant - * parameter is passed to indicate which representation should be returned if - * any.
- *
- * This method is only invoked if content negotiation has been enabled as - * indicated by the {@link #isNegotiated()}, otherwise the - * {@link #post(Representation)} method is invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
- * - * @param entity The posted entity. - * @param variant The variant of the response entity. - * @return The optional result entity. - * @throws ResourceException - * @see HTTP POST method - */ - protected Representation post(Representation entity, Variant variant) throws ResourceException { - Representation result = null; - - if (variant instanceof VariantInfo) { - result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); - } else { - doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - - return result; - } - - /** - * Creates or updates a resource with the given representation as new state to - * be stored. This method is only invoked if content negotiation has been - * disabled as indicated by the {@link #isNegotiated()}, otherwise the - * {@link #put(Representation, Variant)} method is invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. - * - * @param entity The representation to store. - * @return The optional result entity. - * @throws ResourceException - * @see HTTP PUT - * method - */ - protected Representation put(Representation entity) throws ResourceException { - return doHandle(Method.PUT, getQuery(), entity); - } - - /** - * Creates or updates a resource with the given representation as new state to - * be stored. A variant parameter is passed to indicate which representation - * should be returned if any.
- *
- * This method is only invoked if content negotiation has been enabled as - * indicated by the {@link #isNegotiated()}, otherwise the - * {@link #put(Representation)} method is invoked.
- *
- * The default behavior is to set the response status to - * {@link org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
- * - * @param representation The representation to store. - * @param variant The variant of the response entity. - * @return The optional result entity. - * @throws ResourceException - * @see #get(Variant) - * @see HTTP PUT method - */ - protected Representation put(Representation representation, Variant variant) throws ResourceException { - Representation result = null; - - if (variant instanceof VariantInfo) { - result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); - } else { - doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - - return result; - } - - /** - * Permanently redirects the client to a target URI. The client is expected to - * reuse the same method for the new request. - * - * @param targetRef The target URI reference. - */ - public void redirectPermanent(Reference targetRef) { - if (getResponse() != null) { - getResponse().redirectPermanent(targetRef); - } - } - - /** - * Permanently redirects the client to a target URI. The client is expected to - * reuse the same method for the new request.
- *
- * If you pass a relative target URI, it will be resolved with the current base - * reference of the request's resource reference (see - * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}. - * - * @param targetUri The target URI. - */ - public void redirectPermanent(String targetUri) { - if (getResponse() != null) { - getResponse().redirectPermanent(targetUri); - } - } - - /** - * Redirects the client to a different URI that SHOULD be retrieved using a GET - * method on that resource. This method exists primarily to allow the output of - * a POST-activated script to redirect the user agent to a selected resource. - * The new URI is not a substitute reference for the originally requested - * resource. - * - * @param targetRef The target reference. - */ - public void redirectSeeOther(Reference targetRef) { - if (getResponse() != null) { - getResponse().redirectSeeOther(targetRef); - } - } - - /** - * Redirects the client to a different URI that SHOULD be retrieved using a GET - * method on that resource. This method exists primarily to allow the output of - * a POST-activated script to redirect the user agent to a selected resource. - * The new URI is not a substitute reference for the originally requested - * resource.
- *
- * If you pass a relative target URI, it will be resolved with the current base - * reference of the request's resource reference (see - * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}. - * - * @param targetUri The target URI. - */ - public void redirectSeeOther(String targetUri) { - if (getResponse() != null) { - getResponse().redirectSeeOther(targetUri); - } - } - - /** - * Temporarily redirects the client to a target URI. The client is expected to - * reuse the same method for the new request. - * - * @param targetRef The target reference. - */ - public void redirectTemporary(Reference targetRef) { - if (getResponse() != null) { - getResponse().redirectTemporary(targetRef); - } - } - - /** - * Temporarily redirects the client to a target URI. The client is expected to - * reuse the same method for the new request.
- *
- * If you pass a relative target URI, it will be resolved with the current base - * reference of the request's resource reference (see - * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}. - * - * @param targetUri The target URI. - */ - public void redirectTemporary(String targetUri) { - if (getResponse() != null) { - getResponse().redirectTemporary(targetUri); - } - } - - /** - * Sets the set of methods allowed on the requested resource. The set instance - * set must be thread-safe (use {@link CopyOnWriteArraySet} for example. - * - * @param allowedMethods The set of methods allowed on the requested resource. - * @see Response#setAllowedMethods(Set) - */ - public void setAllowedMethods(Set allowedMethods) { - if (getResponse() != null) { - getResponse().setAllowedMethods(allowedMethods); - } - } - - /** - * Indicates if annotations are supported. The default value is true. - * - * @param annotated Indicates if annotations are supported. - */ - public void setAnnotated(boolean annotated) { - this.annotated = annotated; - } - - /** - * Sets the response attribute value. - * - * @param name The attribute name. - * @param value The attribute to set. - */ - public void setAttribute(String name, Object value) { - getResponseAttributes().put(name, value); - } - - /** - * Indicates if the response should be automatically committed. - * - * @param autoCommitting True if the response should be automatically committed - */ - public void setAutoCommitting(boolean autoCommitting) { - getResponse().setAutoCommitting(autoCommitting); - } - - /** - * Sets the list of authentication requests sent by an origin server to a - * client. The list instance set must be thread-safe (use - * {@link CopyOnWriteArrayList} for example. - * - * @param requests The list of authentication requests sent by an origin server - * to a client. - * @see Response#setChallengeRequests(List) - */ - public void setChallengeRequests(List requests) { - if (getResponse() != null) { - getResponse().setChallengeRequests(requests); - } - } - - /** - * Indicates if the response has already been committed. - * - * @param committed True if the response has already been committed. - */ - public void setCommitted(boolean committed) { - getResponse().setCommitted(committed); - } - - /** - * Indicates if conditional handling is enabled. The default value is true. - * - * @param conditional True if conditional handling is enabled. - */ - public void setConditional(boolean conditional) { - this.conditional = conditional; - } - - /** - * Sets the cookie settings provided by the server. - * - * @param cookieSettings The cookie settings provided by the server. - * @see Response#setCookieSettings(Series) - */ - public void setCookieSettings(Series cookieSettings) { - if (getResponse() != null) { - getResponse().setCookieSettings(cookieSettings); - } - } - - /** - * Sets the description. - * - * @param description The description. - */ - public void setDescription(String description) { - this.description = description; - } - - /** - * Sets the set of dimensions on which the response entity may vary. The set - * instance set must be thread-safe (use {@link CopyOnWriteArraySet} for - * example. - * - * @param dimensions The set of dimensions on which the response entity may - * vary. - * @see Response#setDimensions(Set) - */ - public void setDimensions(Set dimensions) { - if (getResponse() != null) { - getResponse().setDimensions(dimensions); - } - } - - /** - * Indicates if the identified resource exists. The default value is true. - * - * @param exists Indicates if the identified resource exists. - */ - public void setExisting(boolean exists) { - this.existing = exists; - } - - /** - * Sets the reference that the client should follow for redirections or resource - * creations. - * - * @param locationRef The reference to set. - * @see Response#setLocationRef(Reference) - */ - public void setLocationRef(Reference locationRef) { - if (getResponse() != null) { - getResponse().setLocationRef(locationRef); - } - } - - /** - * Sets the reference that the client should follow for redirections or resource - * creations. If you pass a relative location URI, it will be resolved with the - * current base reference of the request's resource reference (see - * {@link Request#getResourceRef()} and {@link Reference#getBaseRef()}. - * - * @param locationUri The URI to set. - * @see Response#setLocationRef(String) - */ - public void setLocationRef(String locationUri) { - if (getResponse() != null) { - getResponse().setLocationRef(locationUri); - } - } - - /** - * Sets the display name. - * - * @param name The display name. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Indicates if content negotiation of response entities is enabled. The default - * value is true. - * - * @param negotiateContent True if content negotiation of response entities is - * enabled. - */ - public void setNegotiated(boolean negotiateContent) { - this.negotiated = negotiateContent; - } - - /** - * Sets the callback invoked after sending the response. - * - * @param onSentCallback The callback invoked after sending the response. - */ - public void setOnSent(Uniform onSentCallback) { - getResponse().setOnSent(onSentCallback); - } - - /** - * Sets the list of proxy authentication requests sent by an origin server to a - * client. The list instance set must be thread-safe (use - * {@link CopyOnWriteArrayList} for example. - * - * @param requests The list of proxy authentication requests sent by an origin - * server to a client. - * @see Response#setProxyChallengeRequests(List) - */ - public void setProxyChallengeRequests(List requests) { - if (getResponse() != null) { - getResponse().setProxyChallengeRequests(requests); - } - } - - /** - * Sets the server-specific information. - * - * @param serverInfo The server-specific information. - * @see Response#setServerInfo(ServerInfo) - */ - public void setServerInfo(ServerInfo serverInfo) { - if (getResponse() != null) { - getResponse().setServerInfo(serverInfo); - } - } - - /** - * Sets the status. - * - * @param status The status to set. - * @see Response#setStatus(Status) - */ - public void setStatus(Status status) { - if (getResponse() != null) { - getResponse().setStatus(status); - } - } - - /** - * Sets the status. - * - * @param status The status to set. - * @param message The status message. - * @see Response#setStatus(Status, String) - */ - public void setStatus(Status status, String message) { - if (getResponse() != null) { - getResponse().setStatus(status, message); - } - } - - /** - * Sets the status. - * - * @param status The status to set. - * @param throwable The related error or exception. - * @see Response#setStatus(Status, Throwable) - */ - public void setStatus(Status status, Throwable throwable) { - if (getResponse() != null) { - getResponse().setStatus(status, throwable); - } - } - - /** - * Sets the status. - * - * @param status The status to set. - * @param throwable The related error or exception. - * @param message The status message. - * @see Response#setStatus(Status, Throwable, String) - */ - public void setStatus(Status status, Throwable throwable, String message) { - if (getResponse() != null) { - getResponse().setStatus(status, throwable, message); - } - } - - /** - * Invoked when the list of allowed methods needs to be updated. The - * {@link #getAllowedMethods()} or the {@link #setAllowedMethods(Set)} methods - * should be used. The default implementation lists the annotated methods. - */ - public void updateAllowedMethods() { - getAllowedMethods().clear(); - List annotations = getAnnotations(); - - if (annotations != null) { - for (AnnotationInfo annotationInfo : annotations) { - if (annotationInfo instanceof MethodAnnotationInfo) { - MethodAnnotationInfo methodAnnotationInfo = (MethodAnnotationInfo) annotationInfo; - - if (!getAllowedMethods().contains(methodAnnotationInfo.getRestletMethod())) { - getAllowedMethods().add(methodAnnotationInfo.getRestletMethod()); - } - } - } - } - } - - /** - * Update the dimensions that were used for content negotiation. By default, it - * adds the {@link Dimension#CHARACTER_SET}, {@link Dimension#ENCODING}, - * {@link Dimension#LANGUAGE}and {@link Dimension#MEDIA_TYPE} constants. - */ - protected void updateDimensions() { - getDimensions().add(Dimension.CHARACTER_SET); - getDimensions().add(Dimension.ENCODING); - getDimensions().add(Dimension.LANGUAGE); - getDimensions().add(Dimension.MEDIA_TYPE); - } + } + + /** + * Apply a patch entity to the current representation of the resource retrieved by calling + * {@link #get()}. By default, the {@link ConverterService#applyPatch(Representation, + * Representation)} method is used and then the {@link #put(Representation)} method called. + * + * @param entity The patch entity to apply. + * @return The optional result entity. + * @throws ResourceException + * @see HTTP PATCH method + */ + protected Representation patch(Representation entity) throws ResourceException { + AnnotationInfo annotationInfo; + try { + annotationInfo = getAnnotation(Method.PATCH); + if (annotationInfo != null) { + return doHandle(Method.PATCH, getQuery(), entity); + } else { + // Default implementation + return put(getConverterService().applyPatch(get(), entity)); + // doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + } catch (IOException e) { + throw new ResourceException(e); + } + } + + /** + * Apply a patch entity to the current representation of the resource retrieved by calling + * {@link #get()}. By default, the {@link ConverterService#applyPatch(Representation, + * Representation)} method is used and then the {@link #put(Representation, Variant)} method + * called. + * + * @param entity The patch entity to apply. + * @param variant The variant of the response entity. + * @return The optional result entity. + * @throws ResourceException + * @see HTTP PATCH method + */ + protected Representation patch(Representation entity, Variant variant) + throws ResourceException { + Representation result = null; + + try { + if (variant instanceof VariantInfo) { + result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); + } else { + // Default implementation + result = put(getConverterService().applyPatch(get(), entity), variant); + // doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + } catch (IOException e) { + throw new ResourceException(e); + } + return result; + } + + /** + * Posts a representation to the resource at the target URI reference. This method is only + * invoked if content negotiation has been disabled as indicated by the {@link #isNegotiated()}, + * otherwise the {@link #post(Representation, Variant)} method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. + * + * @param entity The posted entity. + * @return The optional response entity. + * @throws ResourceException + * @see #get(Variant) + * @see HTTP POST + * method + */ + protected Representation post(Representation entity) throws ResourceException { + return doHandle(Method.POST, getQuery(), entity); + } + + /** + * Posts a representation to the resource at the target URI reference. A variant parameter is + * passed to indicate which representation should be returned, if any.
+ *
+ * This method is only invoked if content negotiation has been enabled as indicated by the + * {@link #isNegotiated()}, otherwise the {@link #post(Representation)} method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
+ * + * @param entity The posted entity. + * @param variant The variant of the response entity. + * @return The optional result entity. + * @throws ResourceException + * @see HTTP POST + * method + */ + protected Representation post(Representation entity, Variant variant) throws ResourceException { + Representation result = null; + + if (variant instanceof VariantInfo) { + result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); + } else { + doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + return result; + } + + /** + * Creates or updates a resource with the given representation as a new state to be stored. This + * method is only invoked if content negotiation has been disabled as indicated by the {@link + * #isNegotiated()}, otherwise the {@link #put(Representation, Variant)} method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}. + * + * @param entity The representation to store. + * @return The optional result entity. + * @throws ResourceException + * @see HTTP PUT + * method + */ + protected Representation put(Representation entity) throws ResourceException { + return doHandle(Method.PUT, getQuery(), entity); + } + + /** + * Creates or updates a resource with the given representation as a new state to be stored. A + * variant parameter is passed to indicate which representation should be returned, if any.
+ *
+ * This method is only invoked if content negotiation has been enabled as indicated by the + * {@link #isNegotiated()}, otherwise the {@link #put(Representation)} method is invoked.
+ *
+ * The default behavior is to set the response status to {@link + * org.restlet.data.Status#CLIENT_ERROR_METHOD_NOT_ALLOWED}.
+ * + * @param representation The representation to store. + * @param variant The variant of the response entity. + * @return The optional result entity. + * @throws ResourceException + * @see #get(Variant) + * @see HTTP PUT + * method + */ + protected Representation put(Representation representation, Variant variant) + throws ResourceException { + Representation result = null; + + if (variant instanceof VariantInfo) { + result = doHandle(((VariantInfo) variant).getAnnotationInfo(), variant); + } else { + doError(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + + return result; + } + + /** + * Permanently redirects the client to a target URI. The client is expected to reuse the same + * method for the new request. + * + * @param targetRef The target URI reference. + */ + public void redirectPermanent(Reference targetRef) { + if (getResponse() != null) { + getResponse().redirectPermanent(targetRef); + } + } + + /** + * Permanently redirects the client to a target URI. The client is expected to reuse the same + * method for the new request.
+ *
+ * If you pass a relative target URI, it will be resolved with the current base reference of the + * request's resource reference (see {@link Request#getResourceRef()} and {@link + * Reference#getBaseRef()}. + * + * @param targetUri The target URI. + */ + public void redirectPermanent(String targetUri) { + if (getResponse() != null) { + getResponse().redirectPermanent(targetUri); + } + } + + /** + * Redirects the client to a different URI that SHOULD be retrieved using a GET method on that + * resource. This method exists primarily to allow the output of a POST-activated script to + * redirect the user agent to a selected resource. The new URI is not a substitute reference for + * the originally requested resource. + * + * @param targetRef The target reference. + */ + public void redirectSeeOther(Reference targetRef) { + if (getResponse() != null) { + getResponse().redirectSeeOther(targetRef); + } + } + + /** + * Redirects the client to a different URI that SHOULD be retrieved using a GET method on that + * resource. This method exists primarily to allow the output of a POST-activated script to + * redirect the user agent to a selected resource. The new URI is not a substitute reference for + * the originally requested resource.
+ *
+ * If you pass a relative target URI, it will be resolved with the current base reference of the + * request's resource reference (see {@link Request#getResourceRef()} and {@link + * Reference#getBaseRef()}. + * + * @param targetUri The target URI. + */ + public void redirectSeeOther(String targetUri) { + if (getResponse() != null) { + getResponse().redirectSeeOther(targetUri); + } + } + + /** + * Temporarily redirects the client to a target URI. The client is expected to reuse the same + * method for the new request. + * + * @param targetRef The target reference. + */ + public void redirectTemporary(Reference targetRef) { + if (getResponse() != null) { + getResponse().redirectTemporary(targetRef); + } + } + + /** + * Temporarily redirects the client to a target URI. The client is expected to reuse the same + * method for the new request.
+ *
+ * If you pass a relative target URI, it will be resolved with the current base reference of the + * request's resource reference (see {@link Request#getResourceRef()} and {@link + * Reference#getBaseRef()}. + * + * @param targetUri The target URI. + */ + public void redirectTemporary(String targetUri) { + if (getResponse() != null) { + getResponse().redirectTemporary(targetUri); + } + } + + /** + * Sets the set of methods allowed on the requested resource. The set instance set must be + * thread-safe (use {@link CopyOnWriteArraySet} for example. + * + * @param allowedMethods The set of methods allowed on the requested resource. + * @see Response#setAllowedMethods(Set) + */ + public void setAllowedMethods(Set allowedMethods) { + if (getResponse() != null) { + getResponse().setAllowedMethods(allowedMethods); + } + } + + /** + * Indicates if annotations are supported. The default value is true. + * + * @param annotated Indicates if annotations are supported. + */ + public void setAnnotated(boolean annotated) { + this.annotated = annotated; + } + + /** + * Sets the response attribute value. + * + * @param name The attribute name. + * @param value The attribute to set. + */ + public void setAttribute(String name, Object value) { + getResponseAttributes().put(name, value); + } + + /** + * Indicates if the response should be automatically committed. + * + * @param autoCommitting True if the response should be automatically committed + */ + public void setAutoCommitting(boolean autoCommitting) { + getResponse().setAutoCommitting(autoCommitting); + } + + /** + * Sets the list of authentication requests sent by an origin server to a client. The list + * instance set must be thread-safe (use {@link CopyOnWriteArrayList} for example. + * + * @param requests The list of authentication requests sent by an origin server to a client. + * @see Response#setChallengeRequests(List) + */ + public void setChallengeRequests(List requests) { + if (getResponse() != null) { + getResponse().setChallengeRequests(requests); + } + } + + /** + * Indicates if the response has already been committed. + * + * @param committed True if the response has already been committed. + */ + public void setCommitted(boolean committed) { + getResponse().setCommitted(committed); + } + + /** + * Indicates if conditional handling is enabled. The default value is true. + * + * @param conditional True if conditional handling is enabled. + */ + public void setConditional(boolean conditional) { + this.conditional = conditional; + } + + /** + * Sets the cookie settings provided by the server. + * + * @param cookieSettings The cookie settings provided by the server. + * @see Response#setCookieSettings(Series) + */ + public void setCookieSettings(Series cookieSettings) { + if (getResponse() != null) { + getResponse().setCookieSettings(cookieSettings); + } + } + + /** + * Sets the description. + * + * @param description The description. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Sets the set of dimensions on which the response entity may vary. The set instance set must + * be thread-safe (use {@link CopyOnWriteArraySet} for example. + * + * @param dimensions The set of dimensions on which the response entity may vary. + * @see Response#setDimensions(Set) + */ + public void setDimensions(Set dimensions) { + if (getResponse() != null) { + getResponse().setDimensions(dimensions); + } + } + + /** + * Indicates if the identified resource exists. The default value is true. + * + * @param exists Indicates if the identified resource exists. + */ + public void setExisting(boolean exists) { + this.existing = exists; + } + + /** + * Sets the reference that the client should follow for redirections or resource creations. + * + * @param locationRef The reference to set. + * @see Response#setLocationRef(Reference) + */ + public void setLocationRef(Reference locationRef) { + if (getResponse() != null) { + getResponse().setLocationRef(locationRef); + } + } + + /** + * Sets the reference that the client should follow for redirections or resource creations. If + * you pass a relative location URI, it will be resolved with the current base reference of the + * request's resource reference (see {@link Request#getResourceRef()} and {@link + * Reference#getBaseRef()}). + * + * @param locationUri The URI to set. + * @see Response#setLocationRef(String) + */ + public void setLocationRef(String locationUri) { + if (getResponse() != null) { + getResponse().setLocationRef(locationUri); + } + } + + /** + * Sets the display name. + * + * @param name The display name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Indicates if content negotiation of response entities is enabled. The default value is true. + * + * @param negotiateContent True if content negotiation of response entities is enabled. + */ + public void setNegotiated(boolean negotiateContent) { + this.negotiated = negotiateContent; + } + + /** + * Sets the callback invoked after sending the response. + * + * @param onSentCallback The callback invoked after sending the response. + */ + public void setOnSent(Uniform onSentCallback) { + getResponse().setOnSent(onSentCallback); + } + + /** + * Sets the list of proxy authentication requests sent by an origin server to a client. The list + * instance set must be thread-safe (use {@link CopyOnWriteArrayList} for example. + * + * @param requests The list of proxy authentication requests sent by an origin server to a + * client. + * @see Response#setProxyChallengeRequests(List) + */ + public void setProxyChallengeRequests(List requests) { + if (getResponse() != null) { + getResponse().setProxyChallengeRequests(requests); + } + } + + /** + * Sets the server-specific information. + * + * @param serverInfo The server-specific information. + * @see Response#setServerInfo(ServerInfo) + */ + public void setServerInfo(ServerInfo serverInfo) { + if (getResponse() != null) { + getResponse().setServerInfo(serverInfo); + } + } + + /** + * Sets the status. + * + * @param status The status to set. + * @see Response#setStatus(Status) + */ + public void setStatus(Status status) { + if (getResponse() != null) { + getResponse().setStatus(status); + } + } + + /** + * Sets the status. + * + * @param status The status to set. + * @param message The status message. + * @see Response#setStatus(Status, String) + */ + public void setStatus(Status status, String message) { + if (getResponse() != null) { + getResponse().setStatus(status, message); + } + } + + /** + * Sets the status. + * + * @param status The status to set. + * @param throwable The related error or exception. + * @see Response#setStatus(Status, Throwable) + */ + public void setStatus(Status status, Throwable throwable) { + if (getResponse() != null) { + getResponse().setStatus(status, throwable); + } + } + + /** + * Sets the status. + * + * @param status The status to set. + * @param throwable The related error or exception. + * @param message The status message. + * @see Response#setStatus(Status, Throwable, String) + */ + public void setStatus(Status status, Throwable throwable, String message) { + if (getResponse() != null) { + getResponse().setStatus(status, throwable, message); + } + } + + /** + * Invoked when the list of allowed methods needs to be updated. The {@link + * #getAllowedMethods()} or the {@link #setAllowedMethods(Set)} methods should be used. The + * default implementation lists the annotated methods. + */ + public void updateAllowedMethods() { + getAllowedMethods().clear(); + List annotations = getAnnotations(); + + if (annotations != null) { + for (AnnotationInfo annotationInfo : annotations) { + if (annotationInfo instanceof final MethodAnnotationInfo methodAnnotationInfo) { + getAllowedMethods().add(methodAnnotationInfo.getRestletMethod()); + } + } + } + } + + /** + * Update the dimensions that were used for content negotiation. By default, it adds the {@link + * Dimension#CHARACTER_SET}, {@link Dimension#ENCODING}, {@link Dimension#LANGUAGE}and {@link + * Dimension#MEDIA_TYPE} constants. + */ + protected void updateDimensions() { + getDimensions().add(Dimension.CHARACTER_SET); + getDimensions().add(Dimension.ENCODING); + getDimensions().add(Dimension.LANGUAGE); + getDimensions().add(Dimension.MEDIA_TYPE); + } } diff --git a/org.restlet/src/main/java/org/restlet/resource/Status.java b/org.restlet/src/main/java/org/restlet/resource/Status.java index 4fcd3ef539..922458139e 100644 --- a/org.restlet/src/main/java/org/restlet/resource/Status.java +++ b/org.restlet/src/main/java/org/restlet/resource/Status.java @@ -1,37 +1,39 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** - * Annotation for {@link Throwable} that map to HTTP error statuses. Its - * semantics is equivalent to an HTTP status line plus a related HTTP entity for - * errors.
+ * Annotation for {@link Throwable} that map to HTTP error statuses. Its semantics are equivalent to + * an HTTP status line plus a related HTTP entity for errors.
*
* Example: - * + * *

  * @Get
  * public MyBean represent() throws MyServerError, MyNotFoundError;
- * 
+ *
  * @Status(500)
  * public class MyServerError implements Throwable{
  *    ...
  * }
- * 
+ *
  * @Status(404, serialize = false)
  * public class MyNotFoundError extends RuntimeException{
  *    ...
  * }
- * 
+ *
  * @Status(value = 400)
  * public class MyBadParameterError extends RuntimeException{
  *    public String getParameterName() {
@@ -40,7 +42,7 @@
  *    ...
  * }
  * 
- * + * * @author Jerome Louvel */ @Documented @@ -48,21 +50,19 @@ @Target(ElementType.TYPE) public @interface Status { - /** - * Specifies the HTTP status code associated to the annotated {@link Throwable}. - * Default is 500. - * - * @return The result HTTP status code. - */ - int value() default 500; - - /** - * Indicates if the annotated {@link Throwable} should be serialized in the HTTP - * response entity. - * - * @return True if {@link Throwable} should be serialized in the HTTP response - * entity. - */ - boolean serialize() default true; + /** + * Specifies the HTTP status code associated with the annotated {@link Throwable}. Default is + * 500. + * + * @return The result HTTP status code. + */ + int value() default 500; + /** + * Indicates if the annotated {@link Throwable} should be serialized in the HTTP response + * entity. + * + * @return True if {@link Throwable} should be serialized in the HTTP response entity. + */ + boolean serialize() default true; } diff --git a/org.restlet/src/main/java/org/restlet/resource/package-info.java b/org.restlet/src/main/java/org/restlet/resource/package-info.java new file mode 100644 index 0000000000..99cdbeda80 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/resource/package-info.java @@ -0,0 +1,9 @@ +/** + * Client and server resource classes. + * + * @since Restlet 1.0 + * @see User + * Guide - Resource package + */ +package org.restlet.resource; diff --git a/org.restlet/src/main/java/org/restlet/resource/package.html b/org.restlet/src/main/java/org/restlet/resource/package.html deleted file mode 100644 index 226f879878..0000000000 --- a/org.restlet/src/main/java/org/restlet/resource/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - - Client and server resource classes. -

- @since Restlet 1.0 - @see User Guide - Resource package - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/routing/Extractor.java b/org.restlet/src/main/java/org/restlet/routing/Extractor.java index 0c23b7007d..d9287e9aca 100644 --- a/org.restlet/src/main/java/org/restlet/routing/Extractor.java +++ b/org.restlet/src/main/java/org/restlet/routing/Extractor.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -18,246 +19,239 @@ import org.restlet.representation.Representation; import org.restlet.util.Series; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - /** - * Filter extracting attributes from a call. Multiple extractions can be - * defined, based on the query string of the resource reference, on the request - * form (ex: posted from a browser) or on cookies.
+ * Filter extracting attributes from a call. Multiple extractions can be defined, based on the query + * string of the resource reference, on the request form (ex: posted from a browser) or on cookies. *
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + *
+ * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class Extractor extends Filter { - /** Internal class holding extraction information. */ - private static final class ExtractInfo { - /** Target attribute name. */ - protected String attribute; - - /** Indicates how to handle repeating values. */ - protected boolean first; + /** Internal class holding extraction information. */ + private static final class ExtractInfo { + /** Target attribute name. */ + protected String attribute; - /** Name of the parameter to look for. */ - protected String parameter; + /** Indicates how to handle repeating values. */ + protected boolean first; - /** - * Constructor. - * - * @param attribute Target attribute name. - * @param parameter Name of the parameter to look for. - * @param first Indicates how to handle repeating values. - */ - public ExtractInfo(String attribute, String parameter, boolean first) { - this.attribute = attribute; - this.parameter = parameter; - this.first = first; - } - } + /** Name of the parameter to look for. */ + protected String parameter; - /** The list of cookies to extract. */ - private volatile List cookieExtracts; + /** + * Constructor. + * + * @param attribute Target attribute name. + * @param parameter Name of the parameter to look for. + * @param first Indicates how to handle repeating values. + */ + public ExtractInfo(String attribute, String parameter, boolean first) { + this.attribute = attribute; + this.parameter = parameter; + this.first = first; + } + } - /** The list of request entity parameters to extract. */ - private volatile List entityExtracts; + /** The list of cookies to extract. */ + private volatile List cookieExtracts; - /** The list of query parameters to extract. */ - private volatile List queryExtracts; + /** The list of request entity parameters to extract. */ + private volatile List entityExtracts; - /** - * Constructor. - */ - public Extractor() { - this(null); - } + /** The list of query parameters to extract. */ + private volatile List queryExtracts; - /** - * Constructor. - * - * @param context The context. - */ - public Extractor(Context context) { - this(context, null); - } + /** Constructor. */ + public Extractor() { + this(null); + } - /** - * Constructor. - * - * @param context The context. - * @param next The next Restlet. - */ - public Extractor(Context context, Restlet next) { - super(context, next); - } + /** + * Constructor. + * + * @param context The context. + */ + public Extractor(Context context) { + this(context, null); + } - /** - * Allows filtering before its handling by the target Restlet. By default it - * extracts the attributes from form parameters (query, cookies, entity) and - * finally puts them in the request's attributes ( - * {@link Request#getAttributes()}). - * - * @param request The request to filter. - * @param response The response to filter. - * @return The continuation status. - */ - @Override - protected int beforeHandle(Request request, Response response) { - // Extract the query parameters - if (!getQueryExtracts().isEmpty()) { - Form form = request.getResourceRef().getQueryAsForm(); + /** + * Constructor. + * + * @param context The context. + * @param next The next Restlet. + */ + public Extractor(Context context, Restlet next) { + super(context, next); + } - if (form != null) { - for (ExtractInfo ei : getQueryExtracts()) { - if (ei.first) { - String value = form.getFirstValue(ei.parameter); + /** + * Allows filtering before its handling by the target Restlet. By default, it extracts the + * attributes from form parameters (query, cookies, entity) and finally puts them in the + * request's attributes ( {@link Request#getAttributes()}). + * + * @param request The request to filter. + * @param response The response to filter. + * @return The continuation status. + */ + @Override + protected int beforeHandle(Request request, Response response) { + // Extract the query parameters + if (!getQueryExtracts().isEmpty()) { + Form form = request.getResourceRef().getQueryAsForm(); - if (value != null) { - request.getAttributes().put(ei.attribute, value); - } - } else { - request.getAttributes().put(ei.attribute, form.subList(ei.parameter)); - } - } - } - } + if (form != null) { + for (ExtractInfo ei : getQueryExtracts()) { + if (ei.first) { + String value = form.getFirstValue(ei.parameter); - // Extract the request entity parameters - if (!getEntityExtracts().isEmpty()) { - Representation entity = request.getEntity(); - if (entity != null) { - Form form = new Form(entity); + if (value != null) { + request.getAttributes().put(ei.attribute, value); + } + } else { + request.getAttributes().put(ei.attribute, form.subList(ei.parameter)); + } + } + } + } - for (ExtractInfo ei : getEntityExtracts()) { - if (ei.first) { - String value = form.getFirstValue(ei.parameter); + // Extract the request entity parameters + if (!getEntityExtracts().isEmpty()) { + Representation entity = request.getEntity(); + if (entity != null) { + Form form = new Form(entity); - if (value != null) { - request.getAttributes().put(ei.attribute, value); - } - } else { - request.getAttributes().put(ei.attribute, form.subList(ei.parameter)); - } - } - } - } + for (ExtractInfo ei : getEntityExtracts()) { + if (ei.first) { + String value = form.getFirstValue(ei.parameter); - // Extract the cookie parameters - if (!getCookieExtracts().isEmpty()) { - Series cookies = request.getCookies(); + if (value != null) { + request.getAttributes().put(ei.attribute, value); + } + } else { + request.getAttributes().put(ei.attribute, form.subList(ei.parameter)); + } + } + } + } - if (cookies != null) { - for (ExtractInfo ei : getCookieExtracts()) { - if (ei.first) { - String value = cookies.getFirstValue(ei.parameter); + // Extract the cookie parameters + if (!getCookieExtracts().isEmpty()) { + Series cookies = request.getCookies(); - if (value != null) { - request.getAttributes().put(ei.attribute, value); - } - } else { - request.getAttributes().put(ei.attribute, cookies.subList(ei.parameter)); - } - } - } - } + if (cookies != null) { + for (ExtractInfo ei : getCookieExtracts()) { + if (ei.first) { + String value = cookies.getFirstValue(ei.parameter); - return CONTINUE; - } + if (value != null) { + request.getAttributes().put(ei.attribute, value); + } + } else { + request.getAttributes().put(ei.attribute, cookies.subList(ei.parameter)); + } + } + } + } - /** - * Extracts an attribute from the request cookies. - * - * @param attribute The name of the request attribute to set. - * @param cookieName The name of the cookies to extract. - * @param first Indicates if only the first cookie should be set. Otherwise - * as a List instance might be set in the attribute value. - */ - public void extractFromCookie(String attribute, String cookieName, boolean first) { - getCookieExtracts().add(new ExtractInfo(attribute, cookieName, first)); - } + return CONTINUE; + } - /** - * Extracts an attribute from the request entity form. - * - * @param attribute The name of the request attribute to set. - * @param parameter The name of the entity form parameter to extract. - * @param first Indicates if only the first cookie should be set. Otherwise - * as a List instance might be set in the attribute value. - */ - public void extractFromEntity(String attribute, String parameter, boolean first) { - getEntityExtracts().add(new ExtractInfo(attribute, parameter, first)); - } + /** + * Extracts an attribute from the request cookies. + * + * @param attribute The name of the request attribute to set. + * @param cookieName The name of the cookies to extract. + * @param first Indicates if only the first cookie should be set. Otherwise, as a List instance + * might be set in the attribute value. + */ + public void extractFromCookie(String attribute, String cookieName, boolean first) { + getCookieExtracts().add(new ExtractInfo(attribute, cookieName, first)); + } - /** - * Extracts an attribute from the query string of the resource reference. - * - * @param attribute The name of the request attribute to set. - * @param parameter The name of the query string parameter to extract. - * @param first Indicates if only the first cookie should be set. Otherwise - * as a List instance might be set in the attribute value. - */ - public void extractFromQuery(String attribute, String parameter, boolean first) { - getQueryExtracts().add(new ExtractInfo(attribute, parameter, first)); - } + /** + * Extracts an attribute from the request entity form. + * + * @param attribute The name of the request attribute to set. + * @param parameter The name of the entity form parameter to extract. + * @param first Indicates if only the first cookie should be set. Otherwise, as a List instance + * might be set in the attribute value. + */ + public void extractFromEntity(String attribute, String parameter, boolean first) { + getEntityExtracts().add(new ExtractInfo(attribute, parameter, first)); + } - /** - * Returns the list of query extracts. - * - * @return The list of query extracts. - */ - private List getCookieExtracts() { - // Lazy initialization with double-check. - List ce = this.cookieExtracts; - if (ce == null) { - synchronized (this) { - ce = this.cookieExtracts; - if (ce == null) { - this.cookieExtracts = ce = new CopyOnWriteArrayList(); - } - } - } - return ce; - } + /** + * Extracts an attribute from the query string of the resource reference. + * + * @param attribute The name of the request attribute to set. + * @param parameter The name of the query string parameter to extract. + * @param first Indicates if only the first cookie should be set. Otherwise, as a List instance + * might be set in the attribute value. + */ + public void extractFromQuery(String attribute, String parameter, boolean first) { + getQueryExtracts().add(new ExtractInfo(attribute, parameter, first)); + } - /** - * Returns the list of query extracts. - * - * @return The list of query extracts. - */ - private List getEntityExtracts() { - // Lazy initialization with double-check. - List ee = this.entityExtracts; - if (ee == null) { - synchronized (this) { - ee = this.entityExtracts; - if (ee == null) { - this.entityExtracts = ee = new CopyOnWriteArrayList(); - } - } - } - return ee; - } + /** + * Returns the list of query extracts. + * + * @return The list of query extracts. + */ + private List getCookieExtracts() { + // Lazy initialization with double-check. + List ce = this.cookieExtracts; + if (ce == null) { + synchronized (this) { + ce = this.cookieExtracts; + if (ce == null) { + this.cookieExtracts = ce = new CopyOnWriteArrayList<>(); + } + } + } + return ce; + } - /** - * Returns the list of query extracts. - * - * @return The list of query extracts. - */ - private List getQueryExtracts() { - // Lazy initialization with double-check. - List qe = this.queryExtracts; - if (qe == null) { - synchronized (this) { - qe = this.queryExtracts; - if (qe == null) { - this.queryExtracts = qe = new CopyOnWriteArrayList(); - } - } - } - return qe; - } + /** + * Returns the list of query extracts. + * + * @return The list of query extracts. + */ + private List getEntityExtracts() { + // Lazy initialization with double-check. + List ee = this.entityExtracts; + if (ee == null) { + synchronized (this) { + ee = this.entityExtracts; + if (ee == null) { + this.entityExtracts = ee = new CopyOnWriteArrayList<>(); + } + } + } + return ee; + } + /** + * Returns the list of query extracts. + * + * @return The list of query extracts. + */ + private List getQueryExtracts() { + // Lazy initialization with double-check. + List qe = this.queryExtracts; + if (qe == null) { + synchronized (this) { + qe = this.queryExtracts; + if (qe == null) { + this.queryExtracts = qe = new CopyOnWriteArrayList<>(); + } + } + } + return qe; + } } diff --git a/org.restlet/src/main/java/org/restlet/routing/Filter.java b/org.restlet/src/main/java/org/restlet/routing/Filter.java index 500bec7cb3..45aa680c82 100644 --- a/org.restlet/src/main/java/org/restlet/routing/Filter.java +++ b/org.restlet/src/main/java/org/restlet/routing/Filter.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; import org.restlet.Context; @@ -18,232 +17,220 @@ import org.restlet.resource.ServerResource; /** - * Restlet filtering calls before passing them to an attached Restlet. The - * purpose is to do some pre-processing or post-processing on the calls going - * through it before or after they are actually handled by an attached Restlet. - * Also note that you can attach and detach targets while handling incoming - * calls as the filter is ensured to be thread-safe.
+ * Restlet filtering calls before passing them to an attached Restlet. The purpose is to do some + * pre-processing or post-processing on the calls going through it before or after they are actually + * handled by an attached Restlet. Also note that you can attach and detach targets while handling + * incoming calls as the filter is ensured to be thread-safe.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public abstract class Filter extends Restlet { - /** - * Indicates that the request processing should continue normally. If returned - * from the {@link #beforeHandle(Request, Response)} method, the filter then - * invokes the {@link #doHandle(Request, Response)} method. If returned from the - * {@link #doHandle(Request, Response)} method, the filter then invokes the - * {@link #afterHandle(Request, Response)} method. - */ - public static final int CONTINUE = 0; - - /** - * Indicates that after the {@link #beforeHandle(Request, Response)} method, the - * request processing should skip the {@link #doHandle(Request, Response)} - * method to continue with the {@link #afterHandle(Request, Response)} method. - */ - public static final int SKIP = 1; - - /** - * Indicates that the request processing should stop and return the current - * response from the filter. - */ - public static final int STOP = 2; - - /** The next Restlet. */ - private volatile Restlet next; - - /** - * Constructor. - */ - public Filter() { - this(null); - } - - /** - * Constructor. - * - * @param context The context. - */ - public Filter(Context context) { - this(context, null); - } - - /** - * Constructor. - * - * @param context The context. - * @param next The next Restlet. - */ - public Filter(Context context, Restlet next) { - super(context); - this.next = next; - } - - /** - * Allows filtering after processing by the next Restlet. Does nothing by - * default. - * - * @param request The request to handle. - * @param response The response to update. - */ - protected void afterHandle(Request request, Response response) { - // To be overriden - } - - /** - * Allows filtering before processing by the next Restlet. Returns - * {@link #CONTINUE} by default. - * - * @param request The request to handle. - * @param response The response to update. - * @return The continuation status. Either {@link #CONTINUE} or {@link #SKIP} or - * {@link #STOP}. - */ - protected int beforeHandle(Request request, Response response) { - return CONTINUE; - } - - /** - * Handles the call by distributing it to the next Restlet. If no Restlet is - * attached, then a {@link Status#SERVER_ERROR_INTERNAL} status is returned. - * Returns {@link #CONTINUE} by default. - * - * @param request The request to handle. - * @param response The response to update. - * @return The continuation status. Either {@link #CONTINUE} or {@link #STOP}. - */ - protected int doHandle(Request request, Response response) { - final int result = CONTINUE; - - if (getNext() != null) { - getNext().handle(request, response); - - // Re-associate the response to the current thread - Response.setCurrent(response); - - // Associate the context to the current thread - if (getContext() != null) { - Context.setCurrent(getContext()); - } - } else { - response.setStatus(Status.SERVER_ERROR_INTERNAL); - getLogger().warning("The filter " + getName() + " was executed without a next Restlet attached to it."); - } - - return result; - } - - /** - * Returns the next Restlet. - * - * @return The next Restlet or null. - */ - public Restlet getNext() { - return this.next; - } - - /** - * Handles a call by first invoking the beforeHandle() method for pre-filtering, - * then distributing the call to the next Restlet via the doHandle() method. - * When the handling is completed, it finally invokes the afterHandle() method - * for post-filtering. - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - public final void handle(Request request, Response response) { - super.handle(request, response); - - switch (beforeHandle(request, response)) { - case CONTINUE: - // Stop the processing - if (doHandle(request, response) == CONTINUE) { + /** + * Indicates that the request processing should continue normally. If returned from the {@link + * #beforeHandle(Request, Response)} method, the filter then invokes the {@link + * #doHandle(Request, Response)} method. If returned from the {@link #doHandle(Request, + * Response)} method, the filter then invokes the {@link #afterHandle(Request, Response)} + * method. + */ + public static final int CONTINUE = 0; + + /** + * Indicates that after the {@link #beforeHandle(Request, Response)} method, the request + * processing should skip the {@link #doHandle(Request, Response)} method to continue with the + * {@link #afterHandle(Request, Response)} method. + */ + public static final int SKIP = 1; + + /** + * Indicates that the request processing should stop and return the current response from the + * filter. + */ + public static final int STOP = 2; + + /** The next Restlet. */ + private volatile Restlet next; + + /** Constructor. */ + protected Filter() { + this(null); + } + + /** + * Constructor. + * + * @param context The context. + */ + protected Filter(Context context) { + this(context, null); + } + + /** + * Constructor. + * + * @param context The context. + * @param next The next Restlet. + */ + protected Filter(Context context, Restlet next) { + super(context); + this.next = next; + } + + /** + * Allows filtering after processing by the next Restlet. Does nothing by default. + * + * @param request The request to handle. + * @param response The response to update. + */ + protected void afterHandle(Request request, Response response) { + // To be overriden + } + + /** + * Allows filtering before processing by the next Restlet. Returns {@link #CONTINUE} by default. + * + * @param request The request to handle. + * @param response The response to update. + * @return The continuation status. Either {@link #CONTINUE} or {@link #SKIP} or {@link #STOP}. + */ + protected int beforeHandle(Request request, Response response) { + return CONTINUE; + } + + /** + * Handles the call by distributing it to the next Restlet. If no Restlet is attached, then a + * {@link Status#SERVER_ERROR_INTERNAL} status is returned. Returns {@link #CONTINUE} by + * default. + * + * @param request The request to handle. + * @param response The response to update. + * @return The continuation status. Either {@link #CONTINUE} or {@link #STOP}. + */ + protected int doHandle(Request request, Response response) { + if (getNext() != null) { + getNext().handle(request, response); + + // Re-associate the response to the current thread + Response.setCurrent(response); + + // Associate the context to the current thread + if (getContext() != null) { + Context.setCurrent(getContext()); + } + } else { + response.setStatus(Status.SERVER_ERROR_INTERNAL); + getLogger() + .warning( + "The filter " + + getName() + + " was executed without a next Restlet attached to it."); + } + + return CONTINUE; + } + + /** + * Returns the next Restlet. + * + * @return The next Restlet or null. + */ + public Restlet getNext() { + return this.next; + } + + /** + * Handles a call by first invoking the beforeHandle() method for pre-filtering, then + * distributing the call to the next Restlet via the doHandle() method. When the handling is + * completed, it finally invokes the afterHandle() method for post-filtering. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public final void handle(Request request, Response response) { + super.handle(request, response); + + switch (beforeHandle(request, response)) { + case CONTINUE: + // Stop the processing + if (doHandle(request, response) == CONTINUE) { + afterHandle(request, response); + } + break; + + case SKIP: afterHandle(request, response); + break; + + default: + // Stop the processing + break; + } + } + + /** + * Indicates if there is a next Restlet. + * + * @return True if there is a next Restlet. + */ + public boolean hasNext() { + return getNext() != null; + } + + /** + * Sets the next {@link Restlet} as a {@link Finder} for a given {@link ServerResource} class. + * When the call is delegated to the {@link Finder} instance, a new instance of the resource + * class will be created and will actually handle the request. + * + * @param targetClass The target resource class to attach. + */ + public void setNext(Class targetClass) { + setNext(createFinder(targetClass)); + } + + /** + * Sets the next Restlet. + * + *

In addition, this method will set the context of the next Restlet if it is null by passing + * a reference to its own context. + * + * @param next The next Restlet. + */ + public void setNext(Restlet next) { + if ((next != null) && (next.getContext() == null)) { + next.setContext(getContext()); + } + + this.next = next; + } + + /** Starts the filter and the next Restlet if attached. */ + @Override + public synchronized void start() throws Exception { + if (isStopped()) { + if (getNext() != null) { + getNext().start(); } - break; - - case SKIP: - afterHandle(request, response); - break; - - default: - // Stop the processing - break; - } - - } - - /** - * Indicates if there is a next Restlet. - * - * @return True if there is a next Restlet. - */ - public boolean hasNext() { - return getNext() != null; - } - - /** - * Sets the next {@link Restlet} as a {@link Finder} for a given - * {@link ServerResource} class. When the call is delegated to the - * {@link Finder} instance, a new instance of the resource class will be created - * and will actually handle the request. - * - * @param targetClass The target resource class to attach. - */ - public void setNext(Class targetClass) { - setNext(createFinder(targetClass)); - } - - /** - * Sets the next Restlet. - * - * In addition, this method will set the context of the next Restlet if it is - * null by passing a reference to its own context. - * - * @param next The next Restlet. - */ - public void setNext(Restlet next) { - if ((next != null) && (next.getContext() == null)) { - next.setContext(getContext()); - } - - this.next = next; - } - - /** - * Starts the filter and the next Restlet if attached. - */ - @Override - public synchronized void start() throws Exception { - if (isStopped()) { - if (getNext() != null) { - getNext().start(); - } - - // Must be invoked as a last step - super.start(); - } - } - - /** - * Stops the filter and the next Restlet if attached. - */ - @Override - public synchronized void stop() throws Exception { - if (isStarted()) { - // Must be invoked as a first step - super.stop(); - - if (getNext() != null) { - getNext().stop(); - } - } - } + // Must be invoked as a last step + super.start(); + } + } + + /** Stops the filter and the next Restlet if attached. */ + @Override + public synchronized void stop() throws Exception { + if (isStarted()) { + // Must be invoked as a first step + super.stop(); + + if (getNext() != null) { + getNext().stop(); + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/routing/Redirector.java b/org.restlet/src/main/java/org/restlet/routing/Redirector.java index 1fcf7257bf..b9d9319f22 100644 --- a/org.restlet/src/main/java/org/restlet/routing/Redirector.java +++ b/org.restlet/src/main/java/org/restlet/routing/Redirector.java @@ -1,15 +1,19 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; -import org.restlet.*; +import org.restlet.Application; +import org.restlet.Component; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.data.Reference; import org.restlet.data.Status; import org.restlet.engine.header.HeaderConstants; @@ -17,469 +21,460 @@ import org.restlet.representation.Representation; import org.restlet.util.Resolver; -import java.util.logging.Level; - /** - * Rewrites URIs then redirects the call or the client to a new destination. - * There are various redirection modes that you can choose from: client-side - * redirections ({@link #MODE_CLIENT_FOUND}, {@link #MODE_CLIENT_PERMANENT}, - * {@link #MODE_CLIENT_SEE_OTHER}, {@link #MODE_CLIENT_TEMPORARY}) or - * server-side redirections, similar to a reverse proxy ( - * {@link #MODE_SERVER_OUTBOUND} and {@link #MODE_SERVER_INBOUND}).
+ * Rewrites URIs then redirects the call or the client to a new destination. There are various + * redirection modes that you can choose from: client-side redirections ({@link #MODE_CLIENT_FOUND}, + * {@link #MODE_CLIENT_PERMANENT}, {@link #MODE_CLIENT_SEE_OTHER}, {@link #MODE_CLIENT_TEMPORARY}) + * or server-side redirections, similar to a reverse proxy ( {@link #MODE_SERVER_OUTBOUND} and + * {@link #MODE_SERVER_INBOUND}).
*
- * When setting the redirection URIs, you can also used special URI variables to - * reuse most properties from the original request as well as URI template - * variables. For a complete list of properties, please see the {@link Resolver} - * class. For example "/target?referer={fi}" would redirect to the relative URI, - * inserting the referrer URI as a query parameter.
+ * When setting the redirection URIs, you can also use special URI variables to reuse most + * properties from the original request as well as URI template variables. For a complete list of + * properties, please see the {@link Resolver} class. For example, "/target?referer={fi}" would + * redirect to the relative URI, inserting the referrer URI as a query parameter.
*
- * To create a reverse proxy, a typically configuration will use the - * {@link #MODE_SERVER_OUTBOUND} constant and a target URI like - * "http://targetHost/targetRootPath/{rr}" to ensure that all child URIs are - * properly redirected as well, "rr" appending the remaining part of the current - * request URI that hasn't been routed yet.
+ * To create a reverse proxy, a typical configuration will use the {@link #MODE_SERVER_OUTBOUND} + * constant and a target URI like "http://targetHost/targetRootPath/{rr}" to ensure that all child + * URIs are properly redirected as well, "rr" appending the remaining part of the current request + * URI that hasn't been routed yet.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @see org.restlet.routing.Template * @author Jerome Louvel */ public class Redirector extends Restlet { - /** - * In this mode, the client is simply redirected to the URI generated from the - * target URI pattern using the {@link Status#REDIRECTION_FOUND} status. Note: - * this is a client-side redirection.
- * - * @see Status#REDIRECTION_FOUND - */ - public static final int MODE_CLIENT_FOUND = 2; - - /** - * In this mode, the client is permanently redirected to the URI generated from - * the target URI pattern, using the {@link Status#REDIRECTION_PERMANENT} - * status. Note: this is a client-side redirection.
- * - * @see Status#REDIRECTION_PERMANENT - */ - public static final int MODE_CLIENT_PERMANENT = 1; - - /** - * In this mode, the client is simply redirected to the URI generated from the - * target URI pattern using the {@link Status#REDIRECTION_SEE_OTHER} status. - * Note: this is a client-side redirection.
- * - * @see Status#REDIRECTION_SEE_OTHER - */ - public static final int MODE_CLIENT_SEE_OTHER = 3; - - /** - * In this mode, the client is temporarily redirected to the URI generated from - * the target URI pattern using the {@link Status#REDIRECTION_TEMPORARY} status. - * Note: this is a client-side redirection.
- * - * @see Status#REDIRECTION_TEMPORARY - */ - public static final int MODE_CLIENT_TEMPORARY = 4; - - /** - * In this mode, the call is sent to {@link Context#getServerDispatcher()}. Once - * the selected client connector has completed the request handling, the - * response is normally returned to the client. In this case, you can view the - * Redirector as acting as a transparent proxy Restlet. Note: this is a - * server-side redirection.
- *
- * Warning: remember to add the required connectors to the parent - * {@link Component} and to declare them in the list of required connectors on - * the {@link Application#getConnectorService()} property.
- *
- * Note that in this mode, the headers of HTTP requests, stored in the request's - * attributes, are removed before dispatching. Also, when a HTTP response comes - * back the headers are also removed. You can control this behavior by setting - * the {@link #headersCleaning} attribute or by overriding the - * {@link #rewrite(Request)} or {@link #rewrite(Response)}. - * - * @see Context#getServerDispatcher() - */ - public static final int MODE_SERVER_INBOUND = 7; - - /** - * In this mode, the call is sent to {@link Application#getOutboundRoot()} or if - * null to {@link Context#getClientDispatcher()}. Once the selected client - * connector has completed the request handling, the response is normally - * returned to the client. In this case, you can view the {@link Redirector} as - * acting as a transparent server-side proxy. Note: this is a server-side - * redirection.
- *
- * Warning: remember to add the required connectors to the parent - * {@link Component} and to declare them in the list of required connectors on - * the {@link Application#getConnectorService()} property.
- *
- * Note that in this mode, the headers of HTTP requests, stored in the request's - * attributes, are removed before dispatching. Also, when a HTTP response comes - * back the headers are also removed. You can control this behavior by setting - * the {@link #headersCleaning} attribute or by overriding the - * {@link #rewrite(Request)} or {@link #rewrite(Response)}. - * - * @see Application#getOutboundRoot() - * @see Context#getClientDispatcher() - */ - public static final int MODE_SERVER_OUTBOUND = 6; - - /** - * Indicates if all headers of HTTP requests stored in the request's attributes, - * must be removed before the redirection. If set to true, it removes all - * headers, otherwise it keeps only the extension (or non HTTP standard) headers - */ - protected volatile boolean headersCleaning; - - /** The redirection mode. */ - protected volatile int mode; - - /** The target URI pattern. */ - protected volatile String targetTemplate; - - /** - * Constructor for the client dispatcher mode. - * - * @param context The context. - * @param targetTemplate The template to build the target URI. - * @see org.restlet.routing.Template - */ - public Redirector(Context context, String targetTemplate) { - this(context, targetTemplate, MODE_SERVER_OUTBOUND); - } - - /** - * Constructor. - * - * @param context The context. - * @param targetPattern The pattern to build the target URI (using - * StringTemplate syntax and the CallModel for variables). - * @param mode The redirection mode. - */ - public Redirector(Context context, String targetPattern, int mode) { - super(context); - this.targetTemplate = targetPattern; - this.mode = mode; - this.headersCleaning = true; - } - - /** - * Computes the new location of the given reference, after applying the - * redirection template. Returns null in case it cannot compute the new - * reference. - * - * @param locationRef The reference to translate. - * @param request The current request. - * @return The new location of the given reference. - */ - private String getLocation(Reference locationRef, Request request) { - Reference resourceRef = request.getResourceRef(); - Reference baseRef = resourceRef.getBaseRef(); - - Template rt = new Template(this.targetTemplate); - rt.setLogger(getLogger()); - int matched = rt.parse(locationRef.toString(), request); - - if (matched > 0) { - String remainingPart = (String) request.getAttributes().get("rr"); - - if (remainingPart != null) { - return baseRef.toString() + remainingPart; - } - } - - return null; - } - - /** - * Returns the redirection mode. - * - * @return The redirection mode. - */ - public int getMode() { - return this.mode; - } - - /** - * Returns the target reference to redirect to by automatically resolving URI - * template variables found using the {@link Template} class using the request - * and response as data models. - * - * @param request The request to handle. - * @param response The response to update. - * @return The target reference to redirect to. - */ - protected Reference getTargetRef(Request request, Response response) { - // Create the template - Template rt = new Template(this.targetTemplate); - rt.setLogger(getLogger()); - - // Return the formatted target URI - if (new Reference(this.targetTemplate).isRelative()) { - // Be sure to keep the resource's base reference. - return new Reference(request.getResourceRef(), rt.format(request, response)); - } - - return new Reference(rt.format(request, response)); - } - - /** - * Returns the target URI pattern. - * - * @return The target URI pattern. - */ - public String getTargetTemplate() { - return this.targetTemplate; - } - - /** - * Handles a call by redirecting using the selected redirection mode. - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - public void handle(Request request, Response response) { - // Generate the target reference - Reference targetRef = getTargetRef(request, response); - - switch (this.mode) { - case MODE_CLIENT_PERMANENT: - if (request.isLoggable()) { - getLogger().log(Level.FINE, "Permanently redirecting client to: " + targetRef); - } - - response.redirectPermanent(targetRef); - break; - - case MODE_CLIENT_FOUND: - if (request.isLoggable()) { - getLogger().log(Level.FINE, "Redirecting client to found location: " + targetRef); - } - - response.setLocationRef(targetRef); - response.setStatus(Status.REDIRECTION_FOUND); - break; - - case MODE_CLIENT_SEE_OTHER: - if (request.isLoggable()) { - getLogger().log(Level.FINE, "Redirecting client to another location: " + targetRef); - } - - response.redirectSeeOther(targetRef); - break; - - case MODE_CLIENT_TEMPORARY: - if (request.isLoggable()) { - getLogger().log(Level.FINE, "Temporarily redirecting client to: " + targetRef); - } - - response.redirectTemporary(targetRef); - break; - - case MODE_SERVER_OUTBOUND: - if (request.isLoggable()) { - getLogger().log(Level.FINE, "Redirecting via client dispatcher to: " + targetRef); - } - - outboundServerRedirect(targetRef, request, response); - break; - - case MODE_SERVER_INBOUND: - if (request.isLoggable()) { - getLogger().log(Level.FINE, "Redirecting via server dispatcher to: " + targetRef); - } - - inboundServerRedirect(targetRef, request, response); - break; - } - } - - /** - * Redirects a given call to a target reference. In the default implementation, - * the request HTTP headers, stored in the request's attributes, are removed - * before dispatching. After dispatching, the response HTTP headers are also - * removed to prevent conflicts with the main call. - * - * @param targetRef The target reference with URI variables resolved. - * @param request The request to handle. - * @param response The response to update. - */ - protected void inboundServerRedirect(Reference targetRef, Request request, Response response) { - serverRedirect(getContext().getServerDispatcher(), targetRef, request, response); - } - - /** - * Indicates if the headers must be cleaned. - * - * @return True if the headers must be cleaned. - */ - public boolean isHeadersCleaning() { - return headersCleaning; - } - - /** - * Redirects a given call to a target reference. In the default implementation, - * the request HTTP headers, stored in the request's attributes, are removed - * before dispatching. After dispatching, the response HTTP headers are also - * removed to prevent conflicts with the main call. - * - * @param targetRef The target reference with URI variables resolved. - * @param request The request to handle. - * @param response The response to update. - */ - protected void outboundServerRedirect(Reference targetRef, Request request, Response response) { - Restlet next = (getApplication() == null) ? null : getApplication().getOutboundRoot(); - - if (next == null) { - next = getContext().getClientDispatcher(); - } - - serverRedirect(next, targetRef, request, response); - if (response.getEntity() != null - && !request.getResourceRef().getScheme().equalsIgnoreCase(targetRef.getScheme())) { - // Distinct protocol, this data cannot be exposed. - response.getEntity().setLocationRef((Reference) null); - } - } - - /** - * Optionally rewrites the response entity returned in the - * {@link #MODE_SERVER_INBOUND} and {@link #MODE_SERVER_OUTBOUND} modes. By - * default, it just returns the initial entity without any modification. - * - * @param initialEntity The initial entity returned. - * @return The rewritten entity. - */ - protected Representation rewrite(Representation initialEntity) { - return initialEntity; - } - - /** - * Optionally updates the request sent in the {@link #MODE_SERVER_INBOUND} and - * {@link #MODE_SERVER_OUTBOUND} modes. By default, it leverages the - * {@link #headersCleaning} attribute in order to clean the headers: if set to - * true, it removes all headers, otherwise it keeps only the extension (or non - * HTTP standard) headers
- * - * @param initialRequest The initial request returned. - */ - protected void rewrite(Request initialRequest) { - if (isHeadersCleaning()) { - initialRequest.getAttributes().remove(HeaderConstants.ATTRIBUTE_HEADERS); - } else { - HeaderUtils.keepExtensionHeadersOnly(initialRequest); - } - } - - /** - * Optionally updates the response sent in the {@link #MODE_SERVER_INBOUND} and - * {@link #MODE_SERVER_OUTBOUND} modes. By default, it leverages the - * {@link #headersCleaning} attribute in order to clean the headers: if set to - * true, it removes all headers, otherwise it keeps only the extension (or non - * HTTP standard) headers
- * - * @param initialResponse The initial response returned. - */ - protected void rewrite(Response initialResponse) { - if (isHeadersCleaning()) { - initialResponse.getAttributes().remove(HeaderConstants.ATTRIBUTE_HEADERS); - } else { - HeaderUtils.keepExtensionHeadersOnly(initialResponse); - } - } - - /** - * Rewrite the location of the response, and the Location of the entity, if any. - * - * @param request The request to handle. - * @param response The response to update. - */ - public void rewriteLocation(Request request, Response response) { - if (response.getLocationRef() != null) { - Reference locationRef = response.getLocationRef(); - - String newLocation = getLocation(locationRef, request); - if (newLocation != null) { - response.setLocationRef(newLocation); - } - } - if (response.getEntity() != null && response.getEntity().getLocationRef() != null) { - Reference locationRef = response.getEntity().getLocationRef(); - - String newLocation = getLocation(locationRef, request); - if (newLocation != null) { - response.getEntity().setLocationRef(newLocation); - } - } - } - - /** - * Redirects a given call on the server-side to a next Restlet with a given - * target reference. In the default implementation, the request HTTP headers, - * stored in the request's attributes, are removed before dispatching. After - * dispatching, the response HTTP headers are also removed to prevent conflicts - * with the main call. - * - * @param next The next Restlet to forward the call to. - * @param targetRef The target reference with URI variables resolved. - * @param request The request to handle. - * @param response The response to update. - */ - protected void serverRedirect(Restlet next, Reference targetRef, Request request, Response response) { - if (next == null) { - getLogger().warning("No next Restlet provided for server redirection to " + targetRef); - } else { - // Save the base URI if it exists as we might need it for - // redirections - Reference resourceRef = request.getResourceRef(); - - // Reset the protocol and let the dispatcher handle the protocol - request.setProtocol(null); - - // Update the request to cleanly go to the target URI - request.setResourceRef(targetRef); - rewrite(request); - next.handle(request, response); - - request.setResourceRef(resourceRef); - // Allow for response rewriting and clean the headers - response.setEntity(rewrite(response.getEntity())); - rewrite(response); - - // In case of redirection, we may have to rewrite the redirect URI - rewriteLocation(request, response); - } - } - - /** - * Indicates if the headers must be cleaned. - * - * @param headersCleaning True if the headers must be cleaned. - */ - public void setHeadersCleaning(boolean headersCleaning) { - this.headersCleaning = headersCleaning; - } - - /** - * Sets the redirection mode. - * - * @param mode The redirection mode. - */ - public void setMode(int mode) { - this.mode = mode; - } - - /** - * Sets the target URI pattern. - * - * @param targetTemplate The target URI pattern. - */ - public void setTargetTemplate(String targetTemplate) { - this.targetTemplate = targetTemplate; - } - -} \ No newline at end of file + /** + * In this mode, the client is simply redirected to the URI generated from the target URI + * pattern using the {@link Status#REDIRECTION_FOUND} status. Note: this is a client-side + * redirection.
+ * + * @see Status#REDIRECTION_FOUND + */ + public static final int MODE_CLIENT_FOUND = 2; + + /** + * In this mode, the client is permanently redirected to the URI generated from the target URI + * pattern, using the {@link Status#REDIRECTION_PERMANENT} status. Note: this is a client-side + * redirection.
+ * + * @see Status#REDIRECTION_PERMANENT + */ + public static final int MODE_CLIENT_PERMANENT = 1; + + /** + * In this mode, the client is simply redirected to the URI generated from the target URI + * pattern using the {@link Status#REDIRECTION_SEE_OTHER} status. Note: this is a client-side + * redirection.
+ * + * @see Status#REDIRECTION_SEE_OTHER + */ + public static final int MODE_CLIENT_SEE_OTHER = 3; + + /** + * In this mode, the client is temporarily redirected to the URI generated from the target URI + * pattern using the {@link Status#REDIRECTION_TEMPORARY} status. Note: this is a client-side + * redirection.
+ * + * @see Status#REDIRECTION_TEMPORARY + */ + public static final int MODE_CLIENT_TEMPORARY = 4; + + /** + * In this mode, the call is sent to {@link Context#getServerDispatcher()}. Once the selected + * client connector has completed the request handling, the response is normally returned to the + * client. In this case, you can view the Redirector as acting as a transparent proxy Restlet. + * Note: this is a server-side redirection.
+ *
+ * Warning: remember to add the required connectors to the parent {@link Component} and to + * declare them in the list of required connectors on the {@link + * Application#getConnectorService()} property.
+ *
+ * Note that in this mode, the headers of HTTP requests, stored in the request's attributes, are + * removed before dispatching. Also, when an HTTP response comes back, the headers are also + * removed. You can control this behavior by setting the {@link #headersCleaning} attribute or + * by overriding the {@link #rewrite(Request)} or {@link #rewrite(Response)}. + * + * @see Context#getServerDispatcher() + */ + public static final int MODE_SERVER_INBOUND = 7; + + /** + * In this mode, the call is sent to {@link Application#getOutboundRoot()} or if null to {@link + * Context#getClientDispatcher()}. Once the selected client connector has completed the request + * handling, the response is normally returned to the client. In this case, you can view the + * {@link Redirector} as acting as a transparent server-side proxy. Note: this is a server-side + * redirection.
+ *
+ * Warning: remember to add the required connectors to the parent {@link Component} and to + * declare them in the list of required connectors on the {@link + * Application#getConnectorService()} property.
+ *
+ * Note that in this mode, the headers of HTTP requests, stored in the request's attributes, are + * removed before dispatching. Also, when an HTTP response comes back, the headers are also + * removed. You can control this behavior by setting the {@link #headersCleaning} attribute or + * by overriding the {@link #rewrite(Request)} or {@link #rewrite(Response)}. + * + * @see Application#getOutboundRoot() + * @see Context#getClientDispatcher() + */ + public static final int MODE_SERVER_OUTBOUND = 6; + + /** + * Indicates if all headers of HTTP requests stored in the request's attributes must be removed + * before the redirection. If set to true, it removes all headers, otherwise it keeps only the + * extension (or non-HTTP standard) headers + */ + protected volatile boolean headersCleaning; + + /** The redirection mode. */ + protected volatile int mode; + + /** The target URI pattern. */ + protected volatile String targetTemplate; + + /** + * Constructor for the client dispatcher mode. + * + * @param context The context. + * @param targetTemplate The template to build the target URI. + * @see org.restlet.routing.Template + */ + public Redirector(Context context, String targetTemplate) { + this(context, targetTemplate, MODE_SERVER_OUTBOUND); + } + + /** + * Constructor. + * + * @param context The context. + * @param targetPattern The pattern to build the target URI (using StringTemplate syntax and the + * CallModel for variables). + * @param mode The redirection mode. + */ + public Redirector(Context context, String targetPattern, int mode) { + super(context); + this.targetTemplate = targetPattern; + this.mode = mode; + this.headersCleaning = true; + } + + /** + * Computes the new location of the given reference after applying the redirection template. + * Returns null in case it cannot compute the new reference. + * + * @param locationRef The reference to translate. + * @param request The current request. + * @return The new location of the given reference. + */ + private String getLocation(Reference locationRef, Request request) { + Reference resourceRef = request.getResourceRef(); + Reference baseRef = resourceRef.getBaseRef(); + + Template rt = new Template(this.targetTemplate); + rt.setLogger(getLogger()); + int matched = rt.parse(locationRef.toString(), request); + + if (matched > 0) { + String remainingPart = (String) request.getAttributes().get("rr"); + + if (remainingPart != null) { + return baseRef.toString() + remainingPart; + } + } + + return null; + } + + /** + * Returns the redirection mode. + * + * @return The redirection mode. + */ + public int getMode() { + return this.mode; + } + + /** + * Returns the target reference to redirect to by automatically resolving URI template variables + * found using the {@link Template} class using the request and response as data models. + * + * @param request The request to handle. + * @param response The response to update. + * @return The target reference to redirect to. + */ + protected Reference getTargetRef(Request request, Response response) { + // Create the template + Template rt = new Template(this.targetTemplate); + rt.setLogger(getLogger()); + + // Return the formatted target URI + if (new Reference(this.targetTemplate).isRelative()) { + // Be sure to keep the resource's base reference. + return new Reference(request.getResourceRef(), rt.format(request, response)); + } + + return new Reference(rt.format(request, response)); + } + + /** + * Returns the target URI pattern. + * + * @return The target URI pattern. + */ + public String getTargetTemplate() { + return this.targetTemplate; + } + + /** + * Handles a call by redirecting using the selected redirection mode. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public void handle(Request request, Response response) { + // Generate the target reference + Reference targetRef = getTargetRef(request, response); + + switch (this.mode) { + case MODE_CLIENT_PERMANENT: + if (request.isLoggable()) { + getLogger().fine(() -> "Permanently redirecting client to: " + targetRef); + } + + response.redirectPermanent(targetRef); + break; + + case MODE_CLIENT_FOUND: + if (request.isLoggable()) { + getLogger().fine(() -> "Redirecting client to found location: " + targetRef); + } + + response.setLocationRef(targetRef); + response.setStatus(Status.REDIRECTION_FOUND); + break; + + case MODE_CLIENT_SEE_OTHER: + if (request.isLoggable()) { + getLogger().fine(() -> "Redirecting client to another location: " + targetRef); + } + + response.redirectSeeOther(targetRef); + break; + + case MODE_CLIENT_TEMPORARY: + if (request.isLoggable()) { + getLogger().fine(() -> "Temporarily redirecting client to: " + targetRef); + } + + response.redirectTemporary(targetRef); + break; + + case MODE_SERVER_OUTBOUND: + if (request.isLoggable()) { + getLogger().fine(() -> "Redirecting via client dispatcher to: " + targetRef); + } + + outboundServerRedirect(targetRef, request, response); + break; + + case MODE_SERVER_INBOUND: + if (request.isLoggable()) { + getLogger().fine(() -> "Redirecting via server dispatcher to: " + targetRef); + } + + inboundServerRedirect(targetRef, request, response); + break; + default: + } + } + + /** + * Redirects a given call to a target reference. In the default implementation, the request HTTP + * headers, stored in the request's attributes, are removed before dispatching. After + * dispatching, the response HTTP headers are also removed to prevent conflicts with the main + * call. + * + * @param targetRef The target reference with URI variables resolved. + * @param request The request to handle. + * @param response The response to update. + */ + protected void inboundServerRedirect(Reference targetRef, Request request, Response response) { + serverRedirect(getContext().getServerDispatcher(), targetRef, request, response); + } + + /** + * Indicates if the headers must be cleaned. + * + * @return True if the headers must be cleaned. + */ + public boolean isHeadersCleaning() { + return headersCleaning; + } + + /** + * Redirects a given call to a target reference. In the default implementation, the request HTTP + * headers, stored in the request's attributes, are removed before dispatching. After + * dispatching, the response HTTP headers are also removed to prevent conflicts with the main + * call. + * + * @param targetRef The target reference with URI variables resolved. + * @param request The request to handle. + * @param response The response to update. + */ + protected void outboundServerRedirect(Reference targetRef, Request request, Response response) { + Restlet next = (getApplication() == null) ? null : getApplication().getOutboundRoot(); + + if (next == null) { + next = getContext().getClientDispatcher(); + } + + serverRedirect(next, targetRef, request, response); + if (response.getEntity() != null + && !request.getResourceRef().getScheme().equalsIgnoreCase(targetRef.getScheme())) { + // Distinct protocol, this data cannot be exposed. + response.getEntity().setLocationRef((Reference) null); + } + } + + /** + * Optionally rewrites the response entity returned in the {@link #MODE_SERVER_INBOUND} and + * {@link #MODE_SERVER_OUTBOUND} modes. By default, it just returns the initial entity without + * any modification. + * + * @param initialEntity The initial entity returned. + * @return The rewritten entity. + */ + protected Representation rewrite(Representation initialEntity) { + return initialEntity; + } + + /** + * Optionally updates the request sent in the {@link #MODE_SERVER_INBOUND} and {@link + * #MODE_SERVER_OUTBOUND} modes. By default, it leverages the {@link #headersCleaning} attribute + * to clean the headers: if set to true, it removes all headers, otherwise it keeps only the + * extension (or non-HTTP standard) headers
+ * + * @param initialRequest The initial request returned. + */ + protected void rewrite(Request initialRequest) { + if (isHeadersCleaning()) { + initialRequest.getAttributes().remove(HeaderConstants.ATTRIBUTE_HEADERS); + } else { + HeaderUtils.keepExtensionHeadersOnly(initialRequest); + } + } + + /** + * Optionally updates the response sent in the {@link #MODE_SERVER_INBOUND} and {@link + * #MODE_SERVER_OUTBOUND} modes. By default, it leverages the {@link #headersCleaning} attribute + * to clean the headers: if set to true, it removes all headers, otherwise it keeps only the + * extension (or non-HTTP standard) headers
+ * + * @param initialResponse The initial response returned. + */ + protected void rewrite(Response initialResponse) { + if (isHeadersCleaning()) { + initialResponse.getAttributes().remove(HeaderConstants.ATTRIBUTE_HEADERS); + } else { + HeaderUtils.keepExtensionHeadersOnly(initialResponse); + } + } + + /** + * Rewrite the location of the response, and the Location of the entity, if any. + * + * @param request The request to handle. + * @param response The response to update. + */ + public void rewriteLocation(Request request, Response response) { + if (response.getLocationRef() != null) { + Reference locationRef = response.getLocationRef(); + + String newLocation = getLocation(locationRef, request); + if (newLocation != null) { + response.setLocationRef(newLocation); + } + } + if (response.getEntity() != null && response.getEntity().getLocationRef() != null) { + Reference locationRef = response.getEntity().getLocationRef(); + + String newLocation = getLocation(locationRef, request); + if (newLocation != null) { + response.getEntity().setLocationRef(newLocation); + } + } + } + + /** + * Redirects a given call on the server-side to a next Restlet with a given target reference. In + * the default implementation, the request HTTP headers, stored in the request's attributes, are + * removed before dispatching. After dispatching, the response HTTP headers are also removed to + * prevent conflicts with the main call. + * + * @param next The next Restlet to forward the call to. + * @param targetRef The target reference with URI variables resolved. + * @param request The request to handle. + * @param response The response to update. + */ + protected void serverRedirect( + Restlet next, Reference targetRef, Request request, Response response) { + if (next == null) { + getLogger() + .warning( + () -> + "No next Restlet provided for server redirection to " + + targetRef); + } else { + // Save the base URI if it exists as we might need it for + // redirections + Reference resourceRef = request.getResourceRef(); + + // Reset the protocol and let the dispatcher handle the protocol + request.setProtocol(null); + + // Update the request to cleanly go to the target URI + request.setResourceRef(targetRef); + rewrite(request); + next.handle(request, response); + + request.setResourceRef(resourceRef); + // Allow for response rewriting and clean the headers + response.setEntity(rewrite(response.getEntity())); + rewrite(response); + + // In case of redirection, we may have to rewrite the redirect URI + rewriteLocation(request, response); + } + } + + /** + * Indicates if the headers must be cleaned. + * + * @param headersCleaning True if the headers must be cleaned. + */ + public void setHeadersCleaning(boolean headersCleaning) { + this.headersCleaning = headersCleaning; + } + + /** + * Sets the redirection mode. + * + * @param mode The redirection mode. + */ + public void setMode(int mode) { + this.mode = mode; + } + + /** + * Sets the target URI pattern. + * + * @param targetTemplate The target URI pattern. + */ + public void setTargetTemplate(String targetTemplate) { + this.targetTemplate = targetTemplate; + } +} diff --git a/org.restlet/src/main/java/org/restlet/routing/Route.java b/org.restlet/src/main/java/org/restlet/routing/Route.java index 93665ad0eb..f54b07ad0e 100644 --- a/org.restlet/src/main/java/org/restlet/routing/Route.java +++ b/org.restlet/src/main/java/org/restlet/routing/Route.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; import org.restlet.Request; @@ -14,67 +13,67 @@ import org.restlet.Restlet; /** - * Filter scoring the affinity of calls with the attached Restlet. The score is - * used by an associated Router in order to determine the most appropriate - * Restlet for a given call.
+ * Filter scoring the affinity of calls with the attached Restlet. The score is used by an + * associated Router to determine the most appropriate Restlet for a given call.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @see org.restlet.routing.Template * @author Jerome Louvel */ public abstract class Route extends Filter { - /** The parent router. */ - private volatile Router router; - - /** - * Constructor behaving as a simple extractor filter. - * - * @param next The next Restlet. - */ - public Route(Restlet next) { - this(null, next); - } + /** The parent router. */ + private volatile Router router; - /** - * Constructor. - * - * @param router The parent router. - * @param next The next Restlet. - */ - public Route(Router router, Restlet next) { - super((router != null) ? router.getContext() : (next != null) ? next.getContext() : null, next); - this.router = router; - } + /** + * Constructor behaving as a simple extractor filter. + * + * @param next The next Restlet. + */ + protected Route(Restlet next) { + this(null, next); + } - /** - * Returns the parent router. - * - * @return The parent router. - */ - public Router getRouter() { - return this.router; - } + /** + * Constructor. + * + * @param router The parent router. + * @param next The next Restlet. + */ + protected Route(Router router, Restlet next) { + super( + (router != null) ? router.getContext() : (next != null) ? next.getContext() : null, + next); + this.router = router; + } - /** - * Returns the score for a given call (between 0 and 1.0). - * - * @param request The request to score. - * @param response The response to score. - * @return The score for a given call (between 0 and 1.0). - */ - public abstract float score(Request request, Response response); + /** + * Returns the parent router. + * + * @return The parent router. + */ + public Router getRouter() { + return this.router; + } - /** - * Sets the parent router. - * - * @param router The parent router. - */ - public void setRouter(Router router) { - this.router = router; - } + /** + * Returns the score for a given call (between 0 and 1.0). + * + * @param request The request to score. + * @param response The response to score. + * @return The score for a given call (between 0 and 1.0). + */ + public abstract float score(Request request, Response response); + /** + * Sets the parent router. + * + * @param router The parent router. + */ + public void setRouter(Router router) { + this.router = router; + } } diff --git a/org.restlet/src/main/java/org/restlet/routing/Router.java b/org.restlet/src/main/java/org/restlet/routing/Router.java index bdcdab3ff8..f71c566d1c 100644 --- a/org.restlet/src/main/java/org/restlet/routing/Router.java +++ b/org.restlet/src/main/java/org/restlet/routing/Router.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -19,739 +19,703 @@ import org.restlet.resource.ServerResource; import org.restlet.util.RouteList; -import java.util.logging.Level; - /** - * Restlet routing calls to one of the attached routes. Each route can compute - * an affinity score for each call depending on various criteria. The attach() - * method allow the creation of routes based on URI patterns matching the - * beginning of a the resource reference's remaining part.
+ * Restlet routing calls to one of the attached routes. Each route can compute an affinity score for + * each call depending on various criteria. The attach() method allows the creation of routes based + * on URI patterns matching the beginning of a resource reference's remaining part.
*
- * In addition, several routing modes are supported, implementing various - * algorithms: + * In addition, several routing modes are supported, implementing various algorithms: + * *

    - *
  • Best match
  • - *
  • First match (default)
  • - *
  • Last match
  • - *
  • Random match
  • - *
  • Round-robin
  • - *
  • Custom
  • + *
  • Best match + *
  • First match (default) + *
  • Last match + *
  • Random match + *
  • Round-robin + *
  • Custom *
+ * *
- * Note that for routes using URI patterns will update the resource reference's - * base reference during the routing if they are selected. It is also important - * to know that the routing is very strict about path separators in your URI - * patterns. Finally, you can modify the list of routes while handling incoming - * calls as the delegation code is ensured to be thread-safe.
+ * Note that for routes using URI patterns will update the resource reference's base reference + * during the routing if they are selected. It is also important to know that the routing is very + * strict about path separators in your URI patterns. Finally, you can modify the list of routes + * while handling incoming calls as the delegation code is ensured to be thread-safe.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel */ public class Router extends Restlet { - /** - * Each call will be routed to the route with the best score if the required - * score is reached. See {@link RouteList#getBest(Request, Response, float)} - * method for implementation details. - */ - public static final int MODE_BEST_MATCH = 1; - - /** - * Each call will be routed according to a custom mode. Override the - * {@link #getCustom(Request, Response)} method to provide your own logic. - */ - public static final int MODE_CUSTOM = 6; - - /** - * Each call is routed to the first route if the required score is reached. If - * the required score is not reached, then the route is skipped and the next one - * is considered. See {@link RouteList#getFirst(Request, Response, float)} - * method for implementation details. - */ - public static final int MODE_FIRST_MATCH = 2; - - /** - * Each call will be routed to the last route if the required score is reached. - * If the required score is not reached, then the route is skipped and the - * previous one is considered. See - * {@link RouteList#getLast(Request, Response, float)} method for implementation - * details. - */ - public static final int MODE_LAST_MATCH = 3; - - /** - * Each call is routed to the next route target if the required score is - * reached. The next route is relative to the previous call routed (round robin - * mode). If the required score is not reached, then the route is skipped and - * the next one is considered. If the last route is reached, the first route - * will be considered. See {@link RouteList#getNext(Request, Response, float)} - * method for implementation details. - */ - public static final int MODE_NEXT_MATCH = 4; - - /** - * Each call will be randomly routed to one of the routes that reached the - * required score. If the random route selected is not a match, then the - * immediate next route is evaluated until one matching route is found. If we - * get back to the initial random route selected with no match, then we return - * null. Unless all the routes score above the required score, this mode will - * result in non-uniform distribution of calls. See - * {@link RouteList#getRandom(Request, Response, float)} method for - * implementation details. - */ - public static final int MODE_RANDOM_MATCH = 5; - - /** The default matching mode to use when selecting routes based on URIs. */ - private volatile int defaultMatchingMode; - - /** - * The default setting for whether the routing should be done on URIs with or - * without taking into account query string. - */ - private volatile boolean defaultMatchingQuery; - - /** The default route tested if no other one was available. */ - private volatile Route defaultRoute; - - /** - * The maximum number of attempts if no attachment could be matched on the first - * attempt. - */ - private volatile int maxAttempts; - - /** The minimum score required to have a match. */ - private volatile float requiredScore; - - /** The delay (in milliseconds) before a new attempt. */ - private volatile long retryDelay; - - /** The modifiable list of routes. */ - private volatile RouteList routes; - - /** The routing mode. */ - private volatile int routingMode; - - /** - * Constructor. Note that usage of this constructor is not recommended as the - * Router won't have a proper context set. In general you will prefer to use the - * other constructor and pass it the parent application's context or eventually - * the parent component's context if you don't use applications. - */ - public Router() { - this(null); - } - - /** - * Constructor. - * - * @param context The context. - */ - public Router(Context context) { - super(context); - this.routes = new RouteList(); - this.defaultMatchingMode = Template.MODE_EQUALS; - this.defaultMatchingQuery = false; - this.defaultRoute = null; - this.routingMode = MODE_FIRST_MATCH; - this.requiredScore = 0.5F; - this.maxAttempts = 1; - this.retryDelay = 500L; - } - - /** - * Attaches a target Restlet to this router with an empty URI pattern. A new - * route using the matching mode returned by {@link #getMatchingMode(Restlet)} - * will be added routing to the target when any call is received. - * - * @param target The target Restlet to attach. - * @return The created route. - */ - public TemplateRoute attach(Restlet target) { - return attach(target, getMatchingMode(target)); - } - - /** - * Attaches a target Restlet to this router with an empty URI pattern. A new - * route will be added routing to the target when any call is received. - * - * @param target The target Restlet to attach. - * @param matchingMode The matching mode. - * @return The created route. - */ - public TemplateRoute attach(Restlet target, int matchingMode) { - return attach("", target, matchingMode); - } - - /** - * Attaches a target Resource class to this router based on a given URI pattern. - * A new route using the matching mode returned by - * {@link #getMatchingMode(Restlet)} will be added routing to the target when - * calls with a URI matching the pattern will be received. - * - * @param pathTemplate The URI path template that must match the relative part - * of the resource URI. - * @param targetClass The target Resource class to attach. - * @return The created route. - */ - public TemplateRoute attach(String pathTemplate, Class targetClass) { - return attach(pathTemplate, createFinder(targetClass)); - } - - /** - * Attaches a target Resource class to this router based on a given URI pattern. - * A new route will be added routing to the target when calls with a URI - * matching the pattern will be received. - * - * @param pathTemplate The URI path template that must match the relative part - * of the resource URI. - * @param targetClass The target Resource class to attach. - * @param matchingMode The matching mode. - * @return The created route. - */ - public TemplateRoute attach(String pathTemplate, Class targetClass, int matchingMode) { - return attach(pathTemplate, createFinder(targetClass), matchingMode); - } - - /** - * Attaches a target Restlet to this router based on a given URI pattern. A new - * route using the matching mode returned by {@link #getMatchingMode(Restlet)} - * will be added routing to the target when calls with a URI matching the - * pattern will be received. - * - * @param pathTemplate The URI path template that must match the relative part - * of the resource URI. - * @param target The target Restlet to attach. - * @return The created route. - */ - public TemplateRoute attach(String pathTemplate, Restlet target) { - return attach(pathTemplate, target, getMatchingMode(target)); - } - - /** - * Attaches a target Restlet to this router based on a given URI pattern. A new - * route will be added routing to the target when calls with a URI matching the - * pattern will be received. - * - * @param pathTemplate The URI path template that must match the relative part - * of the resource URI. - * @param target The target Restlet to attach. - * @param matchingMode The matching mode. - * @return The created route. - */ - public TemplateRoute attach(String pathTemplate, Restlet target, int matchingMode) { - TemplateRoute result = createRoute(pathTemplate, target, matchingMode); - getRoutes().add(result); - return result; - } - - /** - * Attaches a Resource class to this router as the default target to invoke when - * no route matches. It actually sets a default route that scores all calls to - * 1.0. - * - * @param defaultTargetClass The target Resource class to attach. - * @return The created route. - */ - public TemplateRoute attachDefault(Class defaultTargetClass) { - return attachDefault(createFinder(defaultTargetClass)); - } - - /** - * Attaches a Restlet to this router as the default target to invoke when no - * route matches. It actually sets a default route that scores all calls to 1.0. - * - * @param defaultTarget The Restlet to use as the default target. - * @return The created route. - */ - public TemplateRoute attachDefault(Restlet defaultTarget) { - TemplateRoute result = createRoute("", defaultTarget); - result.setMatchingMode(Template.MODE_STARTS_WITH); - setDefaultRoute(result); - return result; - } - - /** - * Creates a new route for the given URI pattern and target. The route will - * match the URI query string depending on the result of - * {@link #getDefaultMatchingQuery()} and the matching mode will be given by - * {@link #getMatchingMode(Restlet)}. - * - * @param uriPattern The URI pattern that must match the relative part of the - * resource URI. - * @param target The target Restlet to attach. - * @return The created route. - */ - protected TemplateRoute createRoute(String uriPattern, Restlet target) { - return createRoute(uriPattern, target, getMatchingMode(target)); - } - - /** - * Creates a new route for the given URI pattern, target and matching mode. The - * route will match the URI query string depending on the result of - * {@link #getDefaultMatchingQuery()}. - * - * @param uriPattern The URI pattern that must match the relative part of the - * resource URI. - * @param target The target Restlet to attach. - * @param matchingMode The matching mode. - * @return The created route. - */ - protected TemplateRoute createRoute(String uriPattern, Restlet target, int matchingMode) { - TemplateRoute result = new TemplateRoute(this, uriPattern, target); - result.getTemplate().setMatchingMode(matchingMode); - result.setMatchingQuery(getDefaultMatchingQuery()); - return result; - } - - /** - * Detaches the target from this router. All routes routing to this target - * Restlet are removed from the list of routes and the default route is set to - * null. - * - * @param targetClass The target class to detach. - */ - public void detach(Class targetClass) { - for (int i = getRoutes().size() - 1; i >= 0; i--) { - Restlet target = getRoutes().get(i).getNext(); - - if (target != null && Finder.class.isAssignableFrom(target.getClass())) { - Finder finder = (Finder) target; - - if (finder.getTargetClass().equals(targetClass)) { - getRoutes().remove(i); - } - } - } - - if (getDefaultRoute() != null) { - Restlet target = getDefaultRoute().getNext(); - - if (target != null && Finder.class.isAssignableFrom(target.getClass())) { - Finder finder = (Finder) target; - - if (finder.getTargetClass().equals(targetClass)) { - setDefaultRoute(null); - } - } - } - } - - /** - * Detaches the target from this router. All routes routing to this target - * Restlet are removed from the list of routes and the default route is set to - * null. - * - * @param target The target Restlet to detach. - */ - public void detach(Restlet target) { - getRoutes().removeAll(target); - if ((getDefaultRoute() != null) && (getDefaultRoute().getNext() == target)) { - setDefaultRoute(null); - } - } - - /** - * Effectively handles the call using the selected next {@link Restlet}, - * typically the selected {@link Route}. By default, it just invokes the next - * Restlet. - * - * @param next The next Restlet to invoke. - * @param request The request. - * @param response The response. - */ - protected void doHandle(Restlet next, Request request, Response response) { - next.handle(request, response); - } - - /** - * Returns the matched route according to a custom algorithm. To use in - * combination of the {@link #MODE_CUSTOM} option. The default implementation - * (to be overridden), returns null. - * - * @param request The request to handle. - * @param response The response to update. - * @return The matched route if available or null. - */ - protected Route getCustom(Request request, Response response) { - return null; - } - - /** - * Returns the default matching mode to use when selecting routes based on URIs. - * By default it returns {@link Template#MODE_EQUALS}. - * - * @return The default matching mode. - */ - public int getDefaultMatchingMode() { - return this.defaultMatchingMode; - } - - /** - * Returns the default setting for whether the routing should be done on URIs - * with or without taking into account query string. By default, it returns - * false. - * - * @return the default setting for whether the routing should be done on URIs - * with or without taking into account query string. - */ - public boolean getDefaultMatchingQuery() { - return this.defaultMatchingQuery; - } - - /** - * Returns the default route to test if no other one was available after - * retrying the maximum number of attempts. - * - * @return The default route tested if no other one was available. - */ - public Route getDefaultRoute() { - return this.defaultRoute; - } - - /** - * Returns the matching mode for the target Restlet. By default it returns - * {@link #getDefaultMatchingMode()}. If the target is an instance of - * {@link Directory} or {@link Router} then the mode returned is - * {@link Template#MODE_STARTS_WITH} to allow further routing by those objects. - * If the target is an instance of {@link Filter}, then it returns the matching - * mode for the {@link Filter#getNext()} Restlet recursively. - * - * @param target The target Restlet. - * @return The preferred matching mode. - */ - protected int getMatchingMode(Restlet target) { - int result = getDefaultMatchingMode(); - - if ((target instanceof Directory) || (target instanceof Router)) { - result = Template.MODE_STARTS_WITH; - } else if (target instanceof Filter) { - result = getMatchingMode(((Filter) target).getNext()); - } - - return result; - } - - /** - * Returns the maximum number of attempts if no attachment could be matched on - * the first attempt. This is useful when the attachment scoring is dynamic and - * therefore could change on a retry. The default value is set to 1. - * - * @return The maximum number of attempts if no attachment could be matched on - * the first attempt. - */ - public int getMaxAttempts() { - return this.maxAttempts; - } - - /** - * Returns the next Restlet if available. - * - * @param request The request to handle. - * @param response The response to update. - * @return The next Restlet if available or null. - */ - public Restlet getNext(Request request, Response response) { - Route result = null; - - for (int i = 0; (result == null) && (i < getMaxAttempts()); i++) { - if (i > 0) { - // Before attempting another time, let's - // sleep during the "retryDelay" set. - try { - Thread.sleep(getRetryDelay()); - } catch (InterruptedException e) { - // MITRE, CWE-391 - Unchecked Error Condition - Thread.currentThread().interrupt(); - } - } - - if (this.routes != null) { - // Select the routing mode - switch (getRoutingMode()) { - case MODE_BEST_MATCH: - result = getRoutes().getBest(request, response, getRequiredScore()); - break; - - case MODE_FIRST_MATCH: - result = getRoutes().getFirst(request, response, getRequiredScore()); - break; - - case MODE_LAST_MATCH: - result = getRoutes().getLast(request, response, getRequiredScore()); - break; - - case MODE_NEXT_MATCH: - result = getRoutes().getNext(request, response, getRequiredScore()); - break; - - case MODE_RANDOM_MATCH: - result = getRoutes().getRandom(request, response, getRequiredScore()); - break; - - case MODE_CUSTOM: - result = getCustom(request, response); - break; - } - } - } - - if (result == null) { - // If nothing matched in the routes list, - // check the default route - if ((getDefaultRoute() != null) && (getDefaultRoute().score(request, response) >= getRequiredScore())) { - result = getDefaultRoute(); - } else { - // No route could be found - response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); - } - } - - if (request.isLoggable()) { - logRoute(result); - } - - return result; - } - - /** - * Returns the minimum score required to have a match. By default, it returns - * {@code 0.5}. - * - * @return The minimum score required to have a match. - */ - public float getRequiredScore() { - return this.requiredScore; - } - - /** - * Returns the delay in milliseconds before a new attempt is made. The default - * value is {@code 500}. - * - * @return The delay in milliseconds before a new attempt is made. - */ - public long getRetryDelay() { - return this.retryDelay; - } - - /** - * Returns the modifiable list of routes. Creates a new instance if no one has - * been set. - * - * @return The modifiable list of routes. - */ - public RouteList getRoutes() { - return this.routes; - } - - /** - * Returns the routing mode. By default, it returns the - * {@link #MODE_FIRST_MATCH} mode. - * - * @return The routing mode. - */ - public int getRoutingMode() { - return this.routingMode; - } - - /** - * Handles a call by invoking the next Restlet if it is available. - * - * @param request The request to handle. - * @param response The response to update. - */ - @Override - public void handle(Request request, Response response) { - super.handle(request, response); - Restlet next = getNext(request, response); - - if (next != null) { - doHandle(next, request, response); - } else { - response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); - } - } - - /** - * Logs the route selected. - * - * @param route The route selected. - */ - protected void logRoute(Route route) { - if (getLogger().isLoggable(Level.FINE)) { - if (getDefaultRoute() == route) { - getLogger().fine("The default route was selected"); - } else { - getLogger().fine("Selected route: " + route); - } - } - } - - /** - * Attaches a permanent redirection to this router based on a given URI pattern. - * The client is expected to reuse the same method for the new request. - * - * @param pathTemplate The URI path template that must match the relative part - * of the resource URI. - * @param targetUri The target URI. - * @return The created route. - */ - public TemplateRoute redirectPermanent(String pathTemplate, String targetUri) { - return attach(pathTemplate, new Redirector(getContext(), targetUri, Redirector.MODE_CLIENT_PERMANENT)); - } - - /** - * Attaches a redirection to this router based on a given URI pattern. It - * redirects the client to a different URI that SHOULD be retrieved using a GET - * method on that resource. This method exists primarily to allow the output of - * a POST-activated script to redirect the user agent to a selected resource. - * The new URI is not a substitute reference for the originally requested - * resource. - * - * @param pathTemplate The URI path template that must match the relative part - * of the resource URI. - * @param targetUri The target URI. - * @return The created route. - */ - - public TemplateRoute redirectSeeOther(String pathTemplate, String targetUri) { - return attach(pathTemplate, new Redirector(getContext(), targetUri, Redirector.MODE_CLIENT_SEE_OTHER)); - } - - /** - * Attaches a temporary redirection to this router based on a given URI pattern. - * The client is expected to reuse the same method for the new request. - * - * @param pathTemplate The URI path template that must match the relative part - * of the resource URI. - * @param targetUri The target URI. - * @return The created route. - */ - - public TemplateRoute redirectTemporary(String pathTemplate, String targetUri) { - return attach(pathTemplate, new Redirector(getContext(), targetUri, Redirector.MODE_CLIENT_TEMPORARY)); - } - - /** - * Sets the default matching mode to use when selecting routes based on URIs. By - * default it is set to {@link Template#MODE_EQUALS}. - * - * @param defaultMatchingMode The default matching mode. - */ - public void setDefaultMatchingMode(int defaultMatchingMode) { - this.defaultMatchingMode = defaultMatchingMode; - } - - /** - * Sets the default setting for whether the routing should be done on URIs with - * or without taking into account query string. By default, it is set to false. - * - * @param defaultMatchingQuery The default setting for whether the routing - * should be done on URIs with or without taking - * into account query string. - * - */ - public void setDefaultMatchingQuery(boolean defaultMatchingQuery) { - this.defaultMatchingQuery = defaultMatchingQuery; - } - - /** - * Sets the default route tested if no other one was available. - * - * @param defaultRoute The default route tested if no other one was available. - */ - public void setDefaultRoute(Route defaultRoute) { - this.defaultRoute = defaultRoute; - } - - /** - * Sets the maximum number of attempts if no attachment could be matched on the - * first attempt. This is useful when the attachment scoring is dynamic and - * therefore could change on a retry. - * - * @param maxAttempts The maximum number of attempts. - */ - public void setMaxAttempts(int maxAttempts) { - this.maxAttempts = maxAttempts; - } - - /** - * Sets the score required to have a match. By default, it is set to - * {@code 0.5}. - * - * @param score The score required to have a match. - */ - public void setRequiredScore(float score) { - this.requiredScore = score; - } - - /** - * Sets the delay in milliseconds before a new attempt is made. By default, it - * is set to {@code 500}. - * - * @param retryDelay The delay in milliseconds before a new attempt is made. - */ - public void setRetryDelay(long retryDelay) { - this.retryDelay = retryDelay; - } - - /** - * Sets the modifiable list of routes. - * - * @param routes The modifiable list of routes. - */ - public void setRoutes(RouteList routes) { - this.routes = routes; - } - - /** - * Sets the routing mode. By default, it is set to the {@link #MODE_FIRST_MATCH} - * mode. - * - * @param routingMode The routing mode. - */ - public void setRoutingMode(int routingMode) { - this.routingMode = routingMode; - } - - /** - * Starts the filter and the attached routes. - */ - @Override - public synchronized void start() throws Exception { - if (isStopped()) { - for (Route route : getRoutes()) { - route.start(); - } - - if (getDefaultRoute() != null) { - getDefaultRoute().start(); - } - - // Must be invoked as a last step - super.start(); - } - } - - /** - * Stops the filter and the attached routes. - */ - @Override - public synchronized void stop() throws Exception { - if (isStarted()) { - // Must be invoked as a first step - super.stop(); - - if (getDefaultRoute() != null) { - getDefaultRoute().stop(); - } - - for (Route route : getRoutes()) { - route.stop(); - } - } - } - + /** + * Each call will be routed to the route with the best score if the required score is reached. + * See {@link RouteList#getBest(Request, Response, float)} method for implementation details. + */ + public static final int MODE_BEST_MATCH = 1; + + /** + * Each call will be routed according to a custom mode. Override the {@link #getCustom(Request, + * Response)} method to provide your own logic. + */ + public static final int MODE_CUSTOM = 6; + + /** + * Each call is routed to the first route if the required score is reached. If the required + * score is not reached, then the route is skipped and the next one is considered. See {@link + * RouteList#getFirst(Request, Response, float)} method for implementation details. + */ + public static final int MODE_FIRST_MATCH = 2; + + /** + * Each call will be routed to the last route if the required score is reached. If the required + * score is not reached, then the route is skipped and the previous one is considered. See + * {@link RouteList#getLast(Request, Response, float)} method for implementation details. + */ + public static final int MODE_LAST_MATCH = 3; + + /** + * Each call is routed to the next route target if the required score is reached. The next route + * is relative to the previous call routed (round-robin mode). If the required score is not + * reached, then the route is skipped and the next one is considered. If the last route is + * reached, the first route will be considered. See {@link RouteList#getNext(Request, Response, + * float)} method for implementation details. + */ + public static final int MODE_NEXT_MATCH = 4; + + /** + * Each call will be randomly routed to one of the routes that reached the required score. If + * the random route selected is not a match, then the immediate next route is evaluated until + * one matching route is found. If we get back to the initial random route selected with no + * match, then we return null. Unless all the routes score above the required score, this mode + * will result in non-uniform distribution of calls. See {@link RouteList#getRandom(Request, + * Response, float)} method for implementation details. + */ + public static final int MODE_RANDOM_MATCH = 5; + + /** The default matching mode to use when selecting routes based on URIs. */ + private volatile int defaultMatchingMode; + + /** + * The default setting for whether the routing should be done on URIs with or without taking + * into account query string. + */ + private volatile boolean defaultMatchingQuery; + + /** The default route tested if no other one was available. */ + private volatile Route defaultRoute; + + /** The maximum number of attempts if no attachment could be matched on the first attempt. */ + private volatile int maxAttempts; + + /** The minimum score required to have a match. */ + private volatile float requiredScore; + + /** The delay (in milliseconds) before a new attempt. */ + private volatile long retryDelay; + + /** The modifiable list of routes. */ + private volatile RouteList routes; + + /** The routing mode. */ + private volatile int routingMode; + + /** + * Constructor. Note that usage of this constructor is not recommended as the Router won't have + * a proper context set. In general, you will prefer to use the other constructor and pass it + * the parent application's context or eventually the parent component's context if you don't + * use applications. + */ + public Router() { + this(null); + } + + /** + * Constructor. + * + * @param context The context. + */ + public Router(Context context) { + super(context); + this.routes = new RouteList(); + this.defaultMatchingMode = Template.MODE_EQUALS; + this.defaultMatchingQuery = false; + this.defaultRoute = null; + this.routingMode = MODE_FIRST_MATCH; + this.requiredScore = 0.5F; + this.maxAttempts = 1; + this.retryDelay = 500L; + } + + /** + * Attaches a target Restlet to this router with an empty URI pattern. A new route using the + * matching mode returned by {@link #getMatchingMode(Restlet)} will be added routing to the + * target when any call is received. + * + * @param target The target Restlet to attach. + * @return The created route. + */ + public TemplateRoute attach(Restlet target) { + return attach(target, getMatchingMode(target)); + } + + /** + * Attaches a target Restlet to this router with an empty URI pattern. A new route will be added + * routing to the target when any call is received. + * + * @param target The target Restlet to attach. + * @param matchingMode The matching mode. + * @return The created route. + */ + public TemplateRoute attach(Restlet target, int matchingMode) { + return attach("", target, matchingMode); + } + + /** + * Attaches a target Resource class to this router based on a given URI pattern. A new route + * using the matching mode returned by {@link #getMatchingMode(Restlet)} will be added routing + * to the target when calls with a URI matching the pattern will be received. + * + * @param pathTemplate The URI path template that must match the relative part of the resource + * URI. + * @param targetClass The target Resource class to attach. + * @return The created route. + */ + public TemplateRoute attach(String pathTemplate, Class targetClass) { + return attach(pathTemplate, createFinder(targetClass)); + } + + /** + * Attaches a target Resource class to this router based on a given URI pattern. A new route + * will be added routing to the target when calls with a URI matching the pattern are received. + * + * @param pathTemplate The URI path template that must match the relative part of the resource + * URI. + * @param targetClass The target Resource class to attach. + * @param matchingMode The matching mode. + * @return The created route. + */ + public TemplateRoute attach( + String pathTemplate, Class targetClass, int matchingMode) { + return attach(pathTemplate, createFinder(targetClass), matchingMode); + } + + /** + * Attaches a target Restlet to this router based on a given URI pattern. A new route using the + * matching mode returned by {@link #getMatchingMode(Restlet)} will be added routing to the + * target when calls with a URI matching the pattern will be received. + * + * @param pathTemplate The URI path template that must match the relative part of the resource + * URI. + * @param target The target Restlet to attach. + * @return The created route. + */ + public TemplateRoute attach(String pathTemplate, Restlet target) { + return attach(pathTemplate, target, getMatchingMode(target)); + } + + /** + * Attaches a target Restlet to this router based on a given URI pattern. A new route will be + * added routing to the target when calls with a URI matching the pattern are received. + * + * @param pathTemplate The URI path template that must match the relative part of the resource + * URI. + * @param target The target Restlet to attach. + * @param matchingMode The matching mode. + * @return The created route. + */ + public TemplateRoute attach(String pathTemplate, Restlet target, int matchingMode) { + TemplateRoute result = createRoute(pathTemplate, target, matchingMode); + getRoutes().add(result); + return result; + } + + /** + * Attaches a Resource class to this router as the default target to invoke when no route + * matches. It actually sets a default route that scores all calls to 1.0. + * + * @param defaultTargetClass The target Resource class to attach. + * @return The created route. + */ + public TemplateRoute attachDefault(Class defaultTargetClass) { + return attachDefault(createFinder(defaultTargetClass)); + } + + /** + * Attaches a Restlet to this router as the default target to invoke when no route matches. It + * actually sets a default route that scores all calls to 1.0. + * + * @param defaultTarget The Restlet to use as the default target. + * @return The created route. + */ + public TemplateRoute attachDefault(Restlet defaultTarget) { + TemplateRoute result = createRoute("", defaultTarget); + result.setMatchingMode(Template.MODE_STARTS_WITH); + setDefaultRoute(result); + return result; + } + + /** + * Creates a new route for the given URI pattern and target. The route will match the URI query + * string depending on the result of {@link #getDefaultMatchingQuery()} and the matching mode + * will be given by {@link #getMatchingMode(Restlet)}. + * + * @param uriPattern The URI pattern that must match the relative part of the resource URI. + * @param target The target Restlet to attach. + * @return The created route. + */ + protected TemplateRoute createRoute(String uriPattern, Restlet target) { + return createRoute(uriPattern, target, getMatchingMode(target)); + } + + /** + * Creates a new route for the given URI pattern, target, and matching mode. The route will + * match the URI query string depending on the result of {@link #getDefaultMatchingQuery()}. + * + * @param uriPattern The URI pattern that must match the relative part of the resource URI. + * @param target The target Restlet to attach. + * @param matchingMode The matching mode. + * @return The created route. + */ + protected TemplateRoute createRoute(String uriPattern, Restlet target, int matchingMode) { + TemplateRoute result = new TemplateRoute(this, uriPattern, target); + result.getTemplate().setMatchingMode(matchingMode); + result.setMatchingQuery(getDefaultMatchingQuery()); + return result; + } + + /** + * Detaches the target from this router. All routes to this target Restlet are removed from the + * list of routes, and the default route is set to null. + * + * @param targetClass The target class to detach. + */ + public void detach(Class targetClass) { + for (int i = getRoutes().size() - 1; i >= 0; i--) { + Restlet target = getRoutes().get(i).getNext(); + + if (target != null && Finder.class.isAssignableFrom(target.getClass())) { + Finder finder = (Finder) target; + + if (finder.getTargetClass().equals(targetClass)) { + getRoutes().remove(i); + } + } + } + + if (getDefaultRoute() != null) { + Restlet target = getDefaultRoute().getNext(); + + if (target != null && Finder.class.isAssignableFrom(target.getClass())) { + Finder finder = (Finder) target; + + if (finder.getTargetClass().equals(targetClass)) { + setDefaultRoute(null); + } + } + } + } + + /** + * Detaches the target from this router. All routes to this target Restlet are removed from the + * list of routes, and the default route is set to null. + * + * @param target The target Restlet to detach. + */ + public void detach(Restlet target) { + getRoutes().removeAll(target); + if ((getDefaultRoute() != null) && (getDefaultRoute().getNext() == target)) { + setDefaultRoute(null); + } + } + + /** + * Effectively handles the call using the selected next {@link Restlet}, typically the selected + * {@link Route}. By default, it just invokes the next Restlet. + * + * @param next The next Restlet to invoke. + * @param request The request. + * @param response The response. + */ + protected void doHandle(Restlet next, Request request, Response response) { + next.handle(request, response); + } + + /** + * Returns the matched route according to a custom algorithm. To use in combination of the + * {@link #MODE_CUSTOM} option. The default implementation (to be overridden), returns null. + * + * @param request The request to handle. + * @param response The response to update. + * @return The matched route if available or null. + */ + protected Route getCustom(Request request, Response response) { + return null; + } + + /** + * Returns the default matching mode to use when selecting routes based on URIs. By default, it + * returns {@link Template#MODE_EQUALS}. + * + * @return The default matching mode. + */ + public int getDefaultMatchingMode() { + return this.defaultMatchingMode; + } + + /** + * Returns the default setting for whether the routing should be done on URIs with or without + * taking into account the query string. By default, it returns false. + * + * @return the default setting for whether the routing should be done on URIs with or without + * taking into account query string. + */ + public boolean getDefaultMatchingQuery() { + return this.defaultMatchingQuery; + } + + /** + * Returns the default route to test if no other one was available after retrying the maximum + * number of attempts. + * + * @return The default route tested if no other one was available. + */ + public Route getDefaultRoute() { + return this.defaultRoute; + } + + /** + * Returns the matching mode for the target Restlet. By default, it returns {@link + * #getDefaultMatchingMode()}. If the target is an instance of {@link Directory} or {@link + * Router} then the mode returned is {@link Template#MODE_STARTS_WITH} to allow further routing + * by those objects. If the target is an instance of {@link Filter}, then it returns the + * matching mode for the {@link Filter#getNext()} Restlet recursively. + * + * @param target The target Restlet. + * @return The preferred matching mode. + */ + protected int getMatchingMode(Restlet target) { + int result = getDefaultMatchingMode(); + + if ((target instanceof Directory) || (target instanceof Router)) { + result = Template.MODE_STARTS_WITH; + } else if (target instanceof Filter) { + result = getMatchingMode(((Filter) target).getNext()); + } + + return result; + } + + /** + * Returns the maximum number of attempts if no attachment could be matched on the first + * attempt. This is useful when the attachment scoring is dynamic and therefore could change on + * a retry. The default value is set to 1. + * + * @return The maximum number of attempts if no attachment could be matched on the first + * attempt. + */ + public int getMaxAttempts() { + return this.maxAttempts; + } + + /** + * Returns the next Restlet if available. + * + * @param request The request to handle. + * @param response The response to update. + * @return The next Restlet if available or null. + */ + public Restlet getNext(Request request, Response response) { + Route result = null; + + for (int i = 0; (result == null) && (i < getMaxAttempts()); i++) { + if (i > 0) { + // Before attempting another time, let's + // sleep during the "retryDelay" set. + try { + Thread.sleep(getRetryDelay()); + } catch (InterruptedException e) { + // MITRE, CWE-391 - Unchecked Error Condition + Thread.currentThread().interrupt(); + } + } + + if (this.routes != null) { + // Select the routing mode + switch (getRoutingMode()) { + case MODE_BEST_MATCH: + result = getRoutes().getBest(request, response, getRequiredScore()); + break; + + case MODE_FIRST_MATCH: + result = getRoutes().getFirst(request, response, getRequiredScore()); + break; + + case MODE_LAST_MATCH: + result = getRoutes().getLast(request, response, getRequiredScore()); + break; + + case MODE_NEXT_MATCH: + result = getRoutes().getNext(request, response, getRequiredScore()); + break; + + case MODE_RANDOM_MATCH: + result = getRoutes().getRandom(request, response, getRequiredScore()); + break; + + case MODE_CUSTOM: + result = getCustom(request, response); + break; + } + } + } + + if (result == null) { + // If nothing matched in the routes list, + // check the default route + if ((getDefaultRoute() != null) + && (getDefaultRoute().score(request, response) >= getRequiredScore())) { + result = getDefaultRoute(); + } else { + // No route could be found + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + } + } + + if (request.isLoggable()) { + logRoute(result); + } + + return result; + } + + /** + * Returns the minimum score required to have a match. By default, it returns {@code 0.5}. + * + * @return The minimum score required to have a match. + */ + public float getRequiredScore() { + return this.requiredScore; + } + + /** + * Returns the delay in milliseconds before a new attempt is made. The default value is {@code + * 500}. + * + * @return The delay in milliseconds before a new attempt is made. + */ + public long getRetryDelay() { + return this.retryDelay; + } + + /** + * Returns the modifiable list of routes. Creates a new instance if no one has been set. + * + * @return The modifiable list of routes. + */ + public RouteList getRoutes() { + return this.routes; + } + + /** + * Returns the routing mode. By default, it returns the {@link #MODE_FIRST_MATCH} mode. + * + * @return The routing mode. + */ + public int getRoutingMode() { + return this.routingMode; + } + + /** + * Handles a call by invoking the next Restlet if it is available. + * + * @param request The request to handle. + * @param response The response to update. + */ + @Override + public void handle(Request request, Response response) { + super.handle(request, response); + Restlet next = getNext(request, response); + + if (next != null) { + doHandle(next, request, response); + } else { + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + } + } + + /** + * Logs the route selected. + * + * @param route The route selected. + */ + protected void logRoute(Route route) { + if (getLogger().isLoggable(Level.FINE)) { + if (getDefaultRoute() == route) { + getLogger().fine("The default route was selected"); + } else { + getLogger().fine("Selected route: " + route); + } + } + } + + /** + * Attaches a permanent redirection to this router based on a given URI pattern. The client is + * expected to reuse the same method for the new request. + * + * @param pathTemplate The URI path template that must match the relative part of the resource + * URI. + * @param targetUri The target URI. + * @return The created route. + */ + public TemplateRoute redirectPermanent(String pathTemplate, String targetUri) { + return attach( + pathTemplate, + new Redirector(getContext(), targetUri, Redirector.MODE_CLIENT_PERMANENT)); + } + + /** + * Attaches a redirection to this router based on a given URI pattern. It redirects the client + * to a different URI that SHOULD be retrieved using a GET method on that resource. This method + * exists primarily to allow the output of a POST-activated script to redirect the user agent to + * a selected resource. The new URI is not a substitute reference for the originally requested + * resource. + * + * @param pathTemplate The URI path template that must match the relative part of the resource + * URI. + * @param targetUri The target URI. + * @return The created route. + */ + public TemplateRoute redirectSeeOther(String pathTemplate, String targetUri) { + return attach( + pathTemplate, + new Redirector(getContext(), targetUri, Redirector.MODE_CLIENT_SEE_OTHER)); + } + + /** + * Attaches a temporary redirection to this router based on a given URI pattern. The client is + * expected to reuse the same method for the new request. + * + * @param pathTemplate The URI path template that must match the relative part of the resource + * URI. + * @param targetUri The target URI. + * @return The created route. + */ + public TemplateRoute redirectTemporary(String pathTemplate, String targetUri) { + return attach( + pathTemplate, + new Redirector(getContext(), targetUri, Redirector.MODE_CLIENT_TEMPORARY)); + } + + /** + * Sets the default matching mode to use when selecting routes based on URIs. By default it is + * set to {@link Template#MODE_EQUALS}. + * + * @param defaultMatchingMode The default matching mode. + */ + public void setDefaultMatchingMode(int defaultMatchingMode) { + this.defaultMatchingMode = defaultMatchingMode; + } + + /** + * Sets the default setting for whether the routing should be done on URIs with or without + * taking into account the query string. By default, it is set to false. + * + * @param defaultMatchingQuery The default setting for whether the routing should be done on + * URIs with or without taking into account query string. + */ + public void setDefaultMatchingQuery(boolean defaultMatchingQuery) { + this.defaultMatchingQuery = defaultMatchingQuery; + } + + /** + * Sets the default route tested if no other one was available. + * + * @param defaultRoute The default route tested if no other one was available. + */ + public void setDefaultRoute(Route defaultRoute) { + this.defaultRoute = defaultRoute; + } + + /** + * Sets the maximum number of attempts if no attachment could be matched on the first attempt. + * This is useful when the attachment scoring is dynamic and therefore could change on a retry. + * + * @param maxAttempts The maximum number of attempts. + */ + public void setMaxAttempts(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + /** + * Sets the score required to have a match. By default, it is set to {@code 0.5}. + * + * @param score The score required to have a match. + */ + public void setRequiredScore(float score) { + this.requiredScore = score; + } + + /** + * Sets the delay in milliseconds before a new attempt is made. By default, it is set to {@code + * 500}. + * + * @param retryDelay The delay in milliseconds before a new attempt is made. + */ + public void setRetryDelay(long retryDelay) { + this.retryDelay = retryDelay; + } + + /** + * Sets the modifiable list of routes. + * + * @param routes The modifiable list of routes. + */ + public void setRoutes(RouteList routes) { + this.routes = routes; + } + + /** + * Sets the routing mode. By default, it is set to the {@link #MODE_FIRST_MATCH} mode. + * + * @param routingMode The routing mode. + */ + public void setRoutingMode(int routingMode) { + this.routingMode = routingMode; + } + + /** Starts the filter and the attached routes. */ + @Override + public synchronized void start() throws Exception { + if (isStopped()) { + for (Route route : getRoutes()) { + route.start(); + } + + if (getDefaultRoute() != null) { + getDefaultRoute().start(); + } + + // Must be invoked as a last step + super.start(); + } + } + + /** Stops the filter and the attached routes. */ + @Override + public synchronized void stop() throws Exception { + if (isStarted()) { + // Must be invoked as a first step + super.stop(); + + if (getDefaultRoute() != null) { + getDefaultRoute().stop(); + } + + for (Route route : getRoutes()) { + route.stop(); + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/routing/Template.java b/org.restlet/src/main/java/org/restlet/routing/Template.java index b04a207776..1d0698081e 100644 --- a/org.restlet/src/main/java/org/restlet/routing/Template.java +++ b/org.restlet/src/main/java/org/restlet/routing/Template.java @@ -1,832 +1,845 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; -import org.restlet.Context; -import org.restlet.Request; -import org.restlet.Response; -import org.restlet.data.Reference; -import org.restlet.util.Resolver; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Reference; +import org.restlet.util.Resolver; /** - * String template with a pluggable model. Supports both formatting and parsing. - * The template variables can be inserted using the "{name}" syntax and - * described using the modifiable map of variable descriptors. When no - * descriptor is found for a given variable, the template logic uses its default - * variable property initialized using the default {@link Variable} - * constructor.
+ * String template with a pluggable model. Supports both formatting and parsing. The template + * variables can be inserted using the "{name}" syntax and described using the modifiable map of + * variable descriptors. When no descriptor is found for a given variable, the template logic uses + * its default variable property initialized using the default {@link Variable} constructor.
*
- * Note that the variable descriptors can be changed before the first parsing or - * matching call. After that point, changes won't be taken into account.
+ * Note that the variable descriptors can be changed before the first parsing or matching call. + * After that point, changes won't be taken into account.
*
- * Format and parsing methods are specially available to deal with requests and - * response. See {@link #format(Request, Response)} and - * {@link #parse(String, Request)}. - * + * Format and parsing methods are especially available to deal with requests and response. See + * {@link #format(Request, Response)} and {@link #parse(String, Request)}. + * * @see Resolver - * @see URI Template - * specification + * @see URI Template specification * @author Jerome Louvel */ public class Template { - /** Mode where all characters must match the template and size be identical. */ - public static final int MODE_EQUALS = 2; - - /** Mode where characters at the beginning must match the template. */ - public static final int MODE_STARTS_WITH = 1; - - /** - * Appends to a pattern a repeating group of a given content based on a class of - * characters. - * - * @param pattern The pattern to append to. - * @param content The content of the group. - * @param required Indicates if the group is required. - */ - private static void appendClass(StringBuilder pattern, String content, boolean required) { - - pattern.append("("); - - if (content.equals(".")) { - // Special case for the TYPE_ALL variable type because the - // dot looses its meaning inside a character class - pattern.append(content); - } else { - pattern.append("[").append(content).append(']'); - } - - if (required) { - pattern.append("+"); - } else { - pattern.append("*"); - } - - pattern.append(")"); - } - - /** - * Appends to a pattern a repeating group of a given content based on a - * non-capturing group. - * - * @param pattern The pattern to append to. - * @param content The content of the group. - * @param required Indicates if the group is required. - */ - private static void appendGroup(StringBuilder pattern, String content, boolean required) { - pattern.append("((?:").append(content).append(')'); - - if (required) { - pattern.append("+"); - } else { - pattern.append("*"); - } - - pattern.append(")"); - } - - /** - * Returns the Regex pattern string corresponding to a variable. - * - * @param variable The variable. - * @return The Regex pattern string corresponding to a variable. - */ - private static String getVariableRegex(Variable variable) { - String result = null; - - if (variable.isFixed()) { - result = "(" + Pattern.quote(variable.getDefaultValue()) + ")"; - } else { - // Expressions to create character classes - final String ALL = "."; - final String ALPHA = "a-zA-Z"; - final String DIGIT = "\\d"; - final String ALPHA_DIGIT = ALPHA + DIGIT; - final String HEXA = DIGIT + "ABCDEFabcdef"; - final String URI_UNRESERVED = ALPHA_DIGIT + "\\-\\.\\_\\~"; - final String URI_GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"; - final String URI_SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="; - final String URI_RESERVED = URI_GEN_DELIMS + URI_SUB_DELIMS; - final String WORD = "\\w"; - - // Basic rules expressed by the HTTP rfc. - final String CRLF = "\\r\\n"; - final String CTL = "\\p{Cntrl}"; - final String LWS = CRLF + "\\ \\t"; - final String SEPARATOR = "\\(\\)\\<\\>\\@\\,\\;\\:\\[\\]\"\\/\\\\?\\=\\{\\}\\ \\t"; - final String TOKEN = "[^" + SEPARATOR + "]"; - final String COMMENT = "[^" + CTL + "]" + "[^\\(\\)]" + LWS; - final String COMMENT_ATTRIBUTE = "[^\\;\\(\\)]"; - - // Expressions to create non-capturing groups - final String PCT_ENCODED = "\\%[" + HEXA + "][" + HEXA + "]"; - // final String PCHAR = "[" + URI_UNRESERVED + "]|(?:" + PCT_ENCODED - // + ")|[" + URI_SUB_DELIMS + "]|\\:|\\@"; - final String PCHAR = "[" + URI_UNRESERVED + URI_SUB_DELIMS + "\\:\\@]|(?:" + PCT_ENCODED + ")"; - final String QUERY = PCHAR + "|\\/|\\?"; - final String FRAGMENT = QUERY; - final String URI_PATH = PCHAR + "|\\/"; - final String URI_ALL = "[" + URI_RESERVED + URI_UNRESERVED + "]|(?:" + PCT_ENCODED + ")"; - - // Special case of query parameter characters - final String QUERY_PARAM_DELIMS = "\\!\\$\\'\\(\\)\\*\\+\\,\\;"; - final String QUERY_PARAM_CHAR = "[" + URI_UNRESERVED + QUERY_PARAM_DELIMS + "\\:\\@]|(?:" + PCT_ENCODED - + ")"; - final String QUERY_PARAM = QUERY_PARAM_CHAR + "|\\/|\\?"; - - final StringBuilder coreRegex = new StringBuilder(); - - switch (variable.getType()) { - case Variable.TYPE_ALL: - appendClass(coreRegex, ALL, variable.isRequired()); - break; - case Variable.TYPE_ALPHA: - appendClass(coreRegex, ALPHA, variable.isRequired()); - break; - case Variable.TYPE_DIGIT: - appendClass(coreRegex, DIGIT, variable.isRequired()); - break; - case Variable.TYPE_ALPHA_DIGIT: - appendClass(coreRegex, ALPHA_DIGIT, variable.isRequired()); - break; - case Variable.TYPE_URI_ALL: - appendGroup(coreRegex, URI_ALL, variable.isRequired()); - break; - case Variable.TYPE_URI_UNRESERVED: - appendClass(coreRegex, URI_UNRESERVED, variable.isRequired()); - break; - case Variable.TYPE_WORD: - appendClass(coreRegex, WORD, variable.isRequired()); - break; - case Variable.TYPE_URI_FRAGMENT: - appendGroup(coreRegex, FRAGMENT, variable.isRequired()); - break; - case Variable.TYPE_URI_PATH: - appendGroup(coreRegex, URI_PATH, variable.isRequired()); - break; - case Variable.TYPE_URI_QUERY: - appendGroup(coreRegex, QUERY, variable.isRequired()); - break; - case Variable.TYPE_URI_QUERY_PARAM: - appendGroup(coreRegex, QUERY_PARAM, variable.isRequired()); - break; - case Variable.TYPE_URI_SEGMENT: - appendGroup(coreRegex, PCHAR, variable.isRequired()); - break; - case Variable.TYPE_TOKEN: - appendClass(coreRegex, TOKEN, variable.isRequired()); - break; - case Variable.TYPE_COMMENT: - appendClass(coreRegex, COMMENT, variable.isRequired()); - break; - case Variable.TYPE_COMMENT_ATTRIBUTE: - appendClass(coreRegex, COMMENT_ATTRIBUTE, variable.isRequired()); - break; - } - - result = coreRegex.toString(); - } - - return result; - } - - /** The default variable to use when no matching variable descriptor exists. */ - private volatile Variable defaultVariable; - - /** True if the variables must be encoded when formatting the template. */ - private volatile boolean encodingVariables; - - /** The logger to use. */ - private volatile Logger logger; - - /** The matching mode to use when parsing a formatted reference. */ - private volatile int matchingMode; - - /** The pattern to use for formatting or parsing. */ - private volatile String pattern; - - /** The internal Regex pattern. */ - private volatile Pattern regexPattern; - - /** The sequence of Regex variable names as found in the pattern string. */ - private volatile List regexVariables; - - /** The map of variables associated to the route's template. */ - private final Map variables; - - /** - * Default constructor. Each variable matches any sequence of characters by - * default. When parsing, the template will attempt to match the whole template. - * When formatting, the variable are replaced by an empty string if they don't - * exist in the model. - * - * @param pattern The pattern to use for formatting or parsing. - */ - public Template(String pattern) { - this(pattern, MODE_EQUALS, Variable.TYPE_ALL, "", true, false); - } - - /** - * Constructor. - * - * @param pattern The pattern to use for formatting or parsing. - * @param matchingMode The matching mode to use when parsing a formatted - * reference. - */ - public Template(String pattern, int matchingMode) { - this(pattern, matchingMode, Variable.TYPE_ALL, "", true, false); - } - - /** - * Constructor. - * - * @param pattern The pattern to use for formatting or parsing. - * @param matchingMode The matching mode to use when parsing a formatted - * reference. - * @param defaultType The default type of variables with no descriptor. - * @param defaultDefaultValue The default value for null variables with no - * descriptor. - * @param defaultRequired The default required flag for variables with no - * descriptor. - * @param defaultFixed The default fixed value for variables with no - * descriptor. - */ - public Template(String pattern, int matchingMode, int defaultType, String defaultDefaultValue, - boolean defaultRequired, boolean defaultFixed) { - this(pattern, matchingMode, defaultType, defaultDefaultValue, defaultRequired, defaultFixed, false); - } - - /** - * Constructor. - * - * @param pattern The pattern to use for formatting or parsing. - * @param matchingMode The matching mode to use when parsing a formatted - * reference. - * @param defaultType The default type of variables with no descriptor. - * @param defaultDefaultValue The default value for null variables with no - * descriptor. - * @param defaultRequired The default required flag for variables with no - * descriptor. - * @param defaultFixed The default fixed value for variables with no - * descriptor. - * @param encodingVariables True if the variables must be encoded when - * formatting the template. - */ - public Template(String pattern, int matchingMode, int defaultType, String defaultDefaultValue, - boolean defaultRequired, boolean defaultFixed, boolean encodingVariables) { - this.logger = (logger == null) ? Context.getCurrentLogger() : logger; - this.pattern = pattern; - this.defaultVariable = new Variable(defaultType, defaultDefaultValue, defaultRequired, defaultFixed); - this.matchingMode = matchingMode; - this.variables = new ConcurrentHashMap(); - this.regexPattern = null; - this.encodingVariables = encodingVariables; - } - - /** - * Creates a formatted string based on the given map of values. - * - * @param values The values to use when formatting. - * @return The formatted string. - * @see Resolver#createResolver(Map) - */ - public String format(Map values) { - return format(Resolver.createResolver(values)); - } - - /** - * Creates a formatted string based on the given request and response. - * - * @param request The request to use as a model. - * @param response The response to use as a model. - * @return The formatted string. - * @see Resolver#createResolver(Request, Response) - */ - public String format(Request request, Response response) { - return format(Resolver.createResolver(request, response)); - } - - /** - * Creates a formatted string based on the given variable resolver. - * - * @param resolver The variable resolver to use. - * @return The formatted string. - */ - public String format(Resolver resolver) { - final StringBuilder result = new StringBuilder(); - StringBuilder varBuffer = null; - char next; - boolean inVariable = false; - final int patternLength = getPattern().length(); - for (int i = 0; i < patternLength; i++) { - next = getPattern().charAt(i); - - if (inVariable) { - if (Reference.isUnreserved(next)) { - // Append to the variable name - varBuffer.append(next); - } else if (next == '}') { - // End of variable detected - if (varBuffer.length() == 0) { - getLogger().warning("Empty pattern variables are not allowed : " + this.regexPattern); - } else { - final String varName = varBuffer.toString(); - Object varValue = resolver.resolve(varName); - - Variable var = getVariables().get(varName); - - // Use the default values instead - if (varValue == null) { - if (var == null) { - var = getDefaultVariable(); - } - - if (var != null) { - varValue = var.getDefaultValue(); - } - } - - String varValueString = (varValue == null) ? null : varValue.toString(); - - if (this.encodingVariables) { - // In case the values must be encoded. - if (var != null) { - result.append(var.encode(varValueString)); - } else { - result.append(Reference.encode(varValueString)); - } - } else { - if ((var != null) && var.isEncodingOnFormat()) { - result.append(Reference.encode(varValueString)); - } else { - result.append(varValueString); - } - } - - // Reset the variable name buffer - varBuffer = new StringBuilder(); - } - inVariable = false; - } else { - getLogger().warning( - "An invalid character was detected inside a pattern variable : " + this.regexPattern); - } - } else { - if (next == '{') { - inVariable = true; - varBuffer = new StringBuilder(); - } else if (next == '}') { - getLogger().warning( - "An invalid character was detected inside a pattern variable : " + this.regexPattern); - } else { - result.append(next); - } - } - } - return result.toString(); - } - - /** - * Returns the default variable. - * - * @return The default variable. - */ - public Variable getDefaultVariable() { - return this.defaultVariable; - } - - /** - * Returns the logger to use. - * - * @return The logger to use. - */ - public Logger getLogger() { - return this.logger; - } - - /** - * Returns the matching mode to use when parsing a formatted reference. - * - * @return The matching mode to use when parsing a formatted reference. - */ - public int getMatchingMode() { - return this.matchingMode; - } - - /** - * Returns the pattern to use for formatting or parsing. - * - * @return The pattern to use for formatting or parsing. - */ - public String getPattern() { - return this.pattern; - } - - /** - * Compiles the URI pattern into a Regex pattern. - * - * @return The Regex pattern. - */ - private Pattern getRegexPattern() { - if (this.regexPattern == null) { - synchronized (this) { - if (this.regexPattern == null) { - getRegexVariables().clear(); - final StringBuilder patternBuffer = new StringBuilder(); - StringBuilder varBuffer = null; - char next; - boolean inVariable = false; - for (int i = 0; i < getPattern().length(); i++) { - next = getPattern().charAt(i); - - if (inVariable) { - if (Reference.isUnreserved(next)) { - // Append to the variable name - varBuffer.append(next); - } else if (next == '}') { - // End of variable detected - if (varBuffer.length() == 0) { - getLogger() - .warning("Empty pattern variables are not allowed : " + this.regexPattern); - } else { - final String varName = varBuffer.toString(); - final int varIndex = getRegexVariables().indexOf(varName); - - if (varIndex != -1) { - // The variable is used several times in - // the pattern, ensure that this - // constraint is enforced when parsing. - patternBuffer.append("\\").append(varIndex + 1); - } else { - // New variable detected. Insert a - // capturing group. - getRegexVariables().add(varName); - Variable var = getVariables().get(varName); - if (var == null) { - var = getDefaultVariable(); - } - patternBuffer.append(getVariableRegex(var)); - } - - // Reset the variable name buffer - varBuffer = new StringBuilder(); - } - inVariable = false; - - } else { - getLogger().warning("An invalid character was detected inside a pattern variable : " - + this.regexPattern); - } - } else { - if (next == '{') { - inVariable = true; - varBuffer = new StringBuilder(); - } else if (next == '}') { - getLogger().warning("An invalid character was detected inside a pattern variable : " - + this.regexPattern); - } else { - patternBuffer.append(quote(next)); - } - } - } - - this.regexPattern = Pattern.compile(patternBuffer.toString()); - } - } - } - - return this.regexPattern; - } - - /** - * Returns the sequence of Regex variable names as found in the pattern string. - * - * @return The sequence of Regex variable names as found in the pattern string. - */ - private List getRegexVariables() { - // Lazy initialization with double-check. - List rv = this.regexVariables; - if (rv == null) { - synchronized (this) { - rv = this.regexVariables; - if (rv == null) { - this.regexVariables = rv = new CopyOnWriteArrayList(); - } - } - } - return rv; - } - - /** - * Returns the list of variable names in the template. - * - * @return The list of variable names. - */ - public List getVariableNames() { - final List result = new ArrayList(); - StringBuilder varBuffer = null; - char next; - boolean inVariable = false; - final String pattern = getPattern(); - - for (int i = 0; i < pattern.length(); i++) { - next = pattern.charAt(i); - - if (inVariable) { - if (Reference.isUnreserved(next)) { - // Append to the variable name - varBuffer.append(next); - } else if (next == '}') { - // End of variable detected - if (varBuffer.length() == 0) { - getLogger().warning("Empty pattern variables are not allowed : " + this.pattern); - } else { - result.add(varBuffer.toString()); - - // Reset the variable name buffer - varBuffer = new StringBuilder(); - } - - inVariable = false; - } else { - getLogger() - .warning("An invalid character was detected inside a pattern variable : " + this.pattern); - } - } else { - if (next == '{') { - inVariable = true; - varBuffer = new StringBuilder(); - } else if (next == '}') { - getLogger() - .warning("An invalid character was detected inside a pattern variable : " + this.pattern); - } - } - } - - return result; - } - - /** - * Returns the modifiable map of variable descriptors. Creates a new instance if - * no one has been set. Note that those variables are only descriptors that can - * influence the way parsing and formatting is done, they don't contain the - * actual value parsed. - * - * @return The modifiable map of variables. - */ - public synchronized Map getVariables() { - return this.variables; - } - - /** - * Indicates if the variables must be encoded when formatting the template. - * - * @return True if the variables must be encoded when formatting the template, - * false otherwise. - */ - public boolean isEncodingVariables() { - return this.encodingVariables; - } - - /** - * Indicates if the current pattern matches the given formatted string. - * - * @param formattedString The formatted string to match. - * @return The number of matched characters or -1 if the match failed. - */ - public int match(String formattedString) { - int result = -1; - - try { - if (formattedString != null) { - final Matcher matcher = getRegexPattern().matcher(formattedString); - - if ((getMatchingMode() == MODE_EQUALS) && matcher.matches()) { - result = matcher.end(); - } else if ((getMatchingMode() == MODE_STARTS_WITH) && matcher.lookingAt()) { - result = matcher.end(); - } - } - } catch (StackOverflowError soe) { - getLogger().warning( - "StackOverflowError exception encountered while matching this string : " + formattedString); - } - - return result; - } - - /** - * Attempts to parse a formatted reference. If the parsing succeeds, the given - * request's attributes are updated.
- * Note that the values parsed are directly extracted from the formatted - * reference and are therefore not percent-decoded. - * - * @see Reference#decode(String) - * - * @param formattedString The string to parse. - * @param variables The map of variables to update. - * @return The number of matched characters or -1 if no character matched. - */ - public int parse(String formattedString, Map variables) { - return parse(formattedString, variables, true); - } - - /** - * Attempts to parse a formatted reference. If the parsing succeeds, the given - * request's attributes are updated.
- * Note that the values parsed are directly extracted from the formatted - * reference and are therefore not percent-decoded. - * - * @see Reference#decode(String) - * - * @param formattedString The string to parse. - * @param variables The map of variables to update. - * @param loggable True if the parsing should be logged. - * @return The number of matched characters or -1 if no character matched. - */ - public int parse(String formattedString, Map variables, boolean loggable) { - int result = -1; - - if (formattedString != null) { - try { - Matcher matcher = getRegexPattern().matcher(formattedString); - boolean matched = ((getMatchingMode() == MODE_EQUALS) && matcher.matches()) - || ((getMatchingMode() == MODE_STARTS_WITH) && matcher.lookingAt()); - - if (matched) { - // Update the number of matched characters - result = matcher.end(); - - // Update the attributes with the variables value - String attributeName = null; - String attributeValue = null; - - for (int i = 0; i < getRegexVariables().size(); i++) { - attributeName = getRegexVariables().get(i); - attributeValue = matcher.group(i + 1); - Variable var = getVariables().get(attributeName); - - if ((var != null) && var.isDecodingOnParse()) { - attributeValue = Reference.decode(attributeValue); - } - - if (loggable) { - getLogger().fine("Template variable \"" + attributeName + "\" matched with value \"" - + attributeValue + "\""); - } - - variables.put(attributeName, attributeValue); - } - } - } catch (StackOverflowError soe) { - getLogger().warning( - "StackOverflowError exception encountered while matching this string : " + formattedString); - } - } - - return result; - } - - /** - * Attempts to parse a formatted reference. If the parsing succeeds, the given - * request's attributes are updated.
- * Note that the values parsed are directly extracted from the formatted - * reference and are therefore not percent-decoded. - * - * @see Reference#decode(String) - * - * @param formattedString The string to parse. - * @param request The request to update. - * @return The number of matched characters or -1 if no character matched. - */ - public int parse(String formattedString, Request request) { - return parse(formattedString, request.getAttributes(), request.isLoggable()); - } - - /** - * Quotes special characters that could be taken for special Regex characters. - * - * @param character The character to quote if necessary. - * @return The quoted character. - */ - private String quote(char character) { - switch (character) { - case '[': - return "\\["; - case ']': - return "\\]"; - case '.': - return "\\."; - case '\\': - return "\\\\"; - case '$': - return "\\$"; - case '^': - return "\\^"; - case '?': - return "\\?"; - case '*': - return "\\*"; - case '|': - return "\\|"; - case '(': - return "\\("; - case ')': - return "\\)"; - case ':': - return "\\:"; - case '-': - return "\\-"; - case '!': - return "\\!"; - case '<': - return "\\<"; - case '>': - return "\\>"; - default: - return Character.toString(character); - } - } - - /** - * Sets the variable to use, if no variable is given. - * - * @param defaultVariable - */ - public void setDefaultVariable(Variable defaultVariable) { - this.defaultVariable = defaultVariable; - } - - /** - * Indicates if the variables must be encoded when formatting the template. - * - * @param encodingVariables True if the variables must be encoded when - * formatting the template. - */ - public void setEncodingVariables(boolean encodingVariables) { - this.encodingVariables = encodingVariables; - } - - /** - * Sets the logger to use. - * - * @param logger The logger to use. - */ - public void setLogger(Logger logger) { - this.logger = logger; - } - - /** - * Sets the matching mode to use when parsing a formatted reference. - * - * @param matchingMode The matching mode to use when parsing a formatted - * reference. - */ - public void setMatchingMode(int matchingMode) { - this.matchingMode = matchingMode; - } - - /** - * Sets the pattern to use for formatting or parsing. - * - * @param pattern The pattern to use for formatting or parsing. - */ - public void setPattern(String pattern) { - this.pattern = pattern; - this.regexPattern = null; - } - - /** - * Sets the modifiable map of variables. - * - * @param variables The modifiable map of variables. - */ - public void setVariables(Map variables) { - synchronized (this.variables) { - if (variables != this.variables) { - this.variables.clear(); - - if (variables != null) { - this.variables.putAll(variables); - } - } - } - } - + /** Mode where all characters must match the template and size be identical. */ + public static final int MODE_EQUALS = 2; + + /** Mode where characters at the beginning must match the template. */ + public static final int MODE_STARTS_WITH = 1; + + /** + * Appends to a pattern a repeating group of a given content based on a class of characters. + * + * @param pattern The pattern to append to. + * @param content The content of the group. + * @param required Indicates if the group is required. + */ + private static void appendClass(StringBuilder pattern, String content, boolean required) { + + pattern.append("("); + + if (content.equals(".")) { + // Special case for the TYPE_ALL variable type because the + // dot loses its meaning inside a character class + pattern.append(content); + } else { + pattern.append("[").append(content).append(']'); + } + + if (required) { + pattern.append("+"); + } else { + pattern.append("*"); + } + + pattern.append(")"); + } + + /** + * Appends to a pattern a repeating group of a given content based on a non-capturing group. + * + * @param pattern The pattern to append to. + * @param content The content of the group. + * @param required Indicates if the group is required. + */ + private static void appendGroup(StringBuilder pattern, String content, boolean required) { + pattern.append("((?:").append(content).append(')'); + + if (required) { + pattern.append("+"); + } else { + pattern.append("*"); + } + + pattern.append(")"); + } + + /** + * Returns the Regex pattern string corresponding to a variable. + * + * @param variable The variable. + * @return The Regex pattern string corresponding to a variable. + */ + private static String getVariableRegex(Variable variable) { + final String result; + + if (variable.isFixed()) { + result = "(" + Pattern.quote(variable.getDefaultValue()) + ")"; + } else { + // Expressions to create character classes + final String ALL = "."; + final String ALPHA = "a-zA-Z"; + final String DIGIT = "\\d"; + final String ALPHA_DIGIT = ALPHA + DIGIT; + final String HEXA = DIGIT + "ABCDEFabcdef"; + final String URI_UNRESERVED = ALPHA_DIGIT + "\\-\\.\\_\\~"; + final String URI_GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"; + final String URI_SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="; + final String URI_RESERVED = URI_GEN_DELIMS + URI_SUB_DELIMS; + final String WORD = "\\w"; + + // Basic rules expressed by the HTTP rfc. + final String CRLF = "\\r\\n"; + final String CTL = "\\p{Cntrl}"; + final String LWS = CRLF + "\\ \\t"; + final String SEPARATOR = "\\(\\)\\<\\>\\@\\,\\;\\:\\[\\]\"\\/\\\\?\\=\\{\\}\\ \\t"; + final String TOKEN = "[^" + SEPARATOR + "]"; + final String COMMENT = "[^" + CTL + "]" + "[^\\(\\)]" + LWS; + final String COMMENT_ATTRIBUTE = "[^\\;\\(\\)]"; + + // Expressions to create non-capturing groups + final String PCT_ENCODED = "\\%[" + HEXA + "][" + HEXA + "]"; + // final String PCHAR = "[" + URI_UNRESERVED + "]|(?:" + PCT_ENCODED + // + ")|[" + URI_SUB_DELIMS + "]|\\:|\\@"; + final String PCHAR = + "[" + URI_UNRESERVED + URI_SUB_DELIMS + "\\:\\@]|(?:" + PCT_ENCODED + ")"; + final String QUERY = PCHAR + "|\\/|\\?"; + final String FRAGMENT = QUERY; + final String URI_PATH = PCHAR + "|\\/"; + final String URI_ALL = + "[" + URI_RESERVED + URI_UNRESERVED + "]|(?:" + PCT_ENCODED + ")"; + + // Special case of query parameter characters + final String QUERY_PARAM_DELIMS = "\\!\\$\\'\\(\\)\\*\\+\\,\\;"; + final String QUERY_PARAM_CHAR = + "[" + URI_UNRESERVED + QUERY_PARAM_DELIMS + "\\:\\@]|(?:" + PCT_ENCODED + ")"; + final String QUERY_PARAM = QUERY_PARAM_CHAR + "|\\/|\\?"; + + final StringBuilder coreRegex = new StringBuilder(); + + switch (variable.getType()) { + case Variable.TYPE_ALL: + appendClass(coreRegex, ALL, variable.isRequired()); + break; + case Variable.TYPE_ALPHA: + appendClass(coreRegex, ALPHA, variable.isRequired()); + break; + case Variable.TYPE_DIGIT: + appendClass(coreRegex, DIGIT, variable.isRequired()); + break; + case Variable.TYPE_ALPHA_DIGIT: + appendClass(coreRegex, ALPHA_DIGIT, variable.isRequired()); + break; + case Variable.TYPE_URI_ALL: + appendGroup(coreRegex, URI_ALL, variable.isRequired()); + break; + case Variable.TYPE_URI_UNRESERVED: + appendClass(coreRegex, URI_UNRESERVED, variable.isRequired()); + break; + case Variable.TYPE_WORD: + appendClass(coreRegex, WORD, variable.isRequired()); + break; + case Variable.TYPE_URI_FRAGMENT: + appendGroup(coreRegex, FRAGMENT, variable.isRequired()); + break; + case Variable.TYPE_URI_PATH: + appendGroup(coreRegex, URI_PATH, variable.isRequired()); + break; + case Variable.TYPE_URI_QUERY: + appendGroup(coreRegex, QUERY, variable.isRequired()); + break; + case Variable.TYPE_URI_QUERY_PARAM: + appendGroup(coreRegex, QUERY_PARAM, variable.isRequired()); + break; + case Variable.TYPE_URI_SEGMENT: + appendGroup(coreRegex, PCHAR, variable.isRequired()); + break; + case Variable.TYPE_TOKEN: + appendClass(coreRegex, TOKEN, variable.isRequired()); + break; + case Variable.TYPE_COMMENT: + appendClass(coreRegex, COMMENT, variable.isRequired()); + break; + case Variable.TYPE_COMMENT_ATTRIBUTE: + appendClass(coreRegex, COMMENT_ATTRIBUTE, variable.isRequired()); + break; + default: + } + + result = coreRegex.toString(); + } + + return result; + } + + /** The default variable to use when no matching variable descriptor exists. */ + private volatile Variable defaultVariable; + + /** True if the variables must be encoded when formatting the template. */ + private volatile boolean encodingVariables; + + /** The logger to use. */ + private volatile Logger logger; + + /** The matching mode to use when parsing a formatted reference. */ + private volatile int matchingMode; + + /** The pattern to use for formatting or parsing. */ + private volatile String pattern; + + /** The internal Regex pattern. */ + private volatile Pattern regexPattern; + + /** The sequence of Regex variable names as found in the pattern string. */ + private volatile List regexVariables; + + /** The map of variables associated with the route's template. */ + private final Map variables; + + /** + * Default constructor. Each variable matches any sequence of characters by default. When + * parsing, the template will attempt to match the whole template. When formatting, the variable + * is replaced by an empty string if they don't exist in the model. + * + * @param pattern The pattern to use for formatting or parsing. + */ + public Template(String pattern) { + this(pattern, MODE_EQUALS, Variable.TYPE_ALL, "", true, false); + } + + /** + * Constructor. + * + * @param pattern The pattern to use for formatting or parsing. + * @param matchingMode The matching mode to use when parsing a formatted reference. + */ + public Template(String pattern, int matchingMode) { + this(pattern, matchingMode, Variable.TYPE_ALL, "", true, false); + } + + /** + * Constructor. + * + * @param pattern The pattern to use for formatting or parsing. + * @param matchingMode The matching mode to use when parsing a formatted reference. + * @param defaultType The default type of variables with no descriptor. + * @param defaultDefaultValue The default value for null variables with no descriptor. + * @param defaultRequired The default required flag for variables with no descriptor. + * @param defaultFixed The default fixed value for variables with no descriptor. + */ + public Template( + String pattern, + int matchingMode, + int defaultType, + String defaultDefaultValue, + boolean defaultRequired, + boolean defaultFixed) { + this( + pattern, + matchingMode, + defaultType, + defaultDefaultValue, + defaultRequired, + defaultFixed, + false); + } + + /** + * Constructor. + * + * @param pattern The pattern to use for formatting or parsing. + * @param matchingMode The matching mode to use when parsing a formatted reference. + * @param defaultType The default type of variables with no descriptor. + * @param defaultDefaultValue The default value for null variables with no descriptor. + * @param defaultRequired The default required flag for variables with no descriptor. + * @param defaultFixed The default fixed value for variables with no descriptor. + * @param encodingVariables True if the variables must be encoded when formatting the template. + */ + public Template( + String pattern, + int matchingMode, + int defaultType, + String defaultDefaultValue, + boolean defaultRequired, + boolean defaultFixed, + boolean encodingVariables) { + this.logger = (logger == null) ? Context.getCurrentLogger() : logger; + this.pattern = pattern; + this.defaultVariable = + new Variable(defaultType, defaultDefaultValue, defaultRequired, defaultFixed); + this.matchingMode = matchingMode; + this.variables = new ConcurrentHashMap<>(); + this.regexPattern = null; + this.encodingVariables = encodingVariables; + } + + /** + * Creates a formatted string based on the given map of values. + * + * @param values The values to use when formatting. + * @return The formatted string. + * @see Resolver#createResolver(Map) + */ + public String format(Map values) { + return format(Resolver.createResolver(values)); + } + + /** + * Creates a formatted string based on the given request and response. + * + * @param request The request to use as a model. + * @param response The response to use as a model. + * @return The formatted string. + * @see Resolver#createResolver(Request, Response) + */ + public String format(Request request, Response response) { + return format(Resolver.createResolver(request, response)); + } + + /** + * Creates a formatted string based on the given variable resolver. + * + * @param resolver The variable resolver to use. + * @return The formatted string. + */ + public String format(Resolver resolver) { + final StringBuilder result = new StringBuilder(); + StringBuilder varBuffer = null; + char next; + boolean inVariable = false; + final int patternLength = getPattern().length(); + for (int i = 0; i < patternLength; i++) { + next = getPattern().charAt(i); + + if (inVariable) { + if (Reference.isUnreserved(next)) { + // Append to the variable name + varBuffer.append(next); + } else if (next == '}') { + // End of variable detected + if (varBuffer.isEmpty()) { + logEmptyVariablesMessage(); + } else { + final String varName = varBuffer.toString(); + Object varValue = resolver.resolve(varName); + + Variable variable = getVariables().get(varName); + + // Use the default values instead + if (varValue == null) { + if (variable == null) { + variable = getDefaultVariable(); + } + + if (variable != null) { + varValue = variable.getDefaultValue(); + } + } + + String varValueString = (varValue == null) ? null : varValue.toString(); + + if (this.encodingVariables) { + // In case the values must be encoded. + if (variable != null) { + result.append(variable.encode(varValueString)); + } else { + result.append(Reference.encode(varValueString)); + } + } else { + if ((variable != null) && variable.isEncodingOnFormat()) { + result.append(Reference.encode(varValueString)); + } else { + result.append(varValueString); + } + } + + // Reset the variable name buffer + varBuffer = new StringBuilder(); + } + inVariable = false; + } else { + logInvalidCharacter(this.regexPattern); + } + } else { + if (next == '{') { + inVariable = true; + varBuffer = new StringBuilder(); + } else if (next == '}') { + logInvalidCharacter(this.regexPattern); + } else { + result.append(next); + } + } + } + return result.toString(); + } + + private void logInvalidCharacter(Object pattern) { + getLogger() + .warning( + () -> + "An invalid character was detected inside a pattern variable : " + + pattern); + } + + private void logEmptyVariablesMessage() { + getLogger().warning(() -> "Empty pattern variables are not allowed : " + this.regexPattern); + } + + /** + * Returns the default variable. + * + * @return The default variable. + */ + public Variable getDefaultVariable() { + return this.defaultVariable; + } + + /** + * Returns the logger to use. + * + * @return The logger to use. + */ + public Logger getLogger() { + return this.logger; + } + + /** + * Returns the matching mode to use when parsing a formatted reference. + * + * @return The matching mode to use when parsing a formatted reference. + */ + public int getMatchingMode() { + return this.matchingMode; + } + + /** + * Returns the pattern to use for formatting or parsing. + * + * @return The pattern to use for formatting or parsing. + */ + public String getPattern() { + return this.pattern; + } + + /** + * Compiles the URI pattern into a Regex pattern. + * + * @return The Regex pattern. + */ + private Pattern getRegexPattern() { + if (this.regexPattern == null) { + synchronized (this) { + if (this.regexPattern == null) { + getRegexVariables().clear(); + final StringBuilder patternBuffer = new StringBuilder(); + StringBuilder varBuffer = null; + char next; + boolean inVariable = false; + for (int i = 0; i < getPattern().length(); i++) { + next = getPattern().charAt(i); + + if (inVariable) { + if (Reference.isUnreserved(next)) { + // Append to the variable name + varBuffer.append(next); + } else if (next == '}') { + // End of variable detected + if (varBuffer.isEmpty()) { + logEmptyVariablesMessage(); + } else { + final String varName = varBuffer.toString(); + final int varIndex = getRegexVariables().indexOf(varName); + + if (varIndex != -1) { + // The variable is used several times in + // the pattern, ensure that this + // constraint is enforced when parsing. + patternBuffer.append("\\").append(varIndex + 1); + } else { + // New variable detected. Insert a + // capturing group. + getRegexVariables().add(varName); + Variable variable = getVariables().get(varName); + if (variable == null) { + variable = getDefaultVariable(); + } + patternBuffer.append(getVariableRegex(variable)); + } + + // Reset the variable name buffer + varBuffer = new StringBuilder(); + } + inVariable = false; + + } else { + logInvalidCharacter(this.regexPattern); + } + } else { + if (next == '{') { + inVariable = true; + varBuffer = new StringBuilder(); + } else if (next == '}') { + logInvalidCharacter(this.regexPattern); + } else { + patternBuffer.append(quote(next)); + } + } + } + + this.regexPattern = Pattern.compile(patternBuffer.toString()); + } + } + } + + return this.regexPattern; + } + + /** + * Returns the sequence of Regex variable names as found in the pattern string. + * + * @return The sequence of Regex variable names as found in the pattern string. + */ + private List getRegexVariables() { + // Lazy initialization with double-check. + List rv = this.regexVariables; + if (rv == null) { + synchronized (this) { + rv = this.regexVariables; + if (rv == null) { + this.regexVariables = rv = new CopyOnWriteArrayList<>(); + } + } + } + return rv; + } + + /** + * Returns the list of variable names in the template. + * + * @return The list of variable names. + */ + public List getVariableNames() { + final List result = new ArrayList<>(); + StringBuilder varBuffer = null; + char next; + boolean inVariable = false; + final String currentPattern = getPattern(); + + for (int i = 0; i < currentPattern.length(); i++) { + next = currentPattern.charAt(i); + + if (inVariable) { + if (Reference.isUnreserved(next)) { + // Append to the variable name + varBuffer.append(next); + } else if (next == '}') { + // End of variable detected + if (varBuffer.isEmpty()) { + getLogger() + .warning( + () -> + "Empty pattern variables are not allowed : " + + this.pattern); + } else { + result.add(varBuffer.toString()); + + // Reset the variable name buffer + varBuffer = new StringBuilder(); + } + + inVariable = false; + } else { + logInvalidCharacter(this.pattern); + } + } else { + if (next == '{') { + inVariable = true; + varBuffer = new StringBuilder(); + } else if (next == '}') { + logInvalidCharacter(this.pattern); + } + } + } + + return result; + } + + /** + * Returns the modifiable map of variable descriptors. Creates a new instance if no one has been + * set. Note that those variables are only descriptors that can influence the way parsing and + * formatting are done; they don't contain the actual value parsed. + * + * @return The modifiable map of variables. + */ + public synchronized Map getVariables() { + return this.variables; + } + + /** + * Indicates if the variables must be encoded when formatting the template. + * + * @return True if the variables must be encoded when formatting the template, false otherwise. + */ + public boolean isEncodingVariables() { + return this.encodingVariables; + } + + /** + * Indicates if the current pattern matches the given formatted string. + * + * @param formattedString The formatted string to match. + * @return The number of matched characters or -1 if the match failed. + */ + public int match(String formattedString) { + int result = -1; + + try { + if (formattedString != null) { + final Matcher matcher = getRegexPattern().matcher(formattedString); + + if (((getMatchingMode() == MODE_EQUALS) && matcher.matches()) + || ((getMatchingMode() == MODE_STARTS_WITH) && matcher.lookingAt())) { + result = matcher.end(); + } + } + } catch (StackOverflowError soe) { + getLogger() + .warning( + "StackOverflowError exception encountered while matching this string : " + + formattedString); + } + + return result; + } + + /** + * Attempts to parse a formatted reference. If the parsing succeeds, the given request's + * attributes are updated.
+ * Note that the values parsed are directly extracted from the formatted reference and are + * therefore not percent-decoded. + * + * @see Reference#decode(String) + * @param formattedString The string to parse. + * @param variables The map of variables to update. + * @return The number of matched characters or -1 if no character matched. + */ + public int parse(String formattedString, Map variables) { + return parse(formattedString, variables, true); + } + + /** + * Attempts to parse a formatted reference. If the parsing succeeds, the given request's + * attributes are updated.
+ * Note that the values parsed are directly extracted from the formatted reference and are + * therefore not percent-decoded. + * + * @see Reference#decode(String) + * @param formattedString The string to parse. + * @param variables The map of variables to update. + * @param loggable True if the parsing should be logged. + * @return The number of matched characters or -1 if no character matched. + */ + public int parse(String formattedString, Map variables, boolean loggable) { + int result = -1; + + if (formattedString != null) { + try { + Matcher matcher = getRegexPattern().matcher(formattedString); + boolean matched = + ((getMatchingMode() == MODE_EQUALS) && matcher.matches()) + || ((getMatchingMode() == MODE_STARTS_WITH) && matcher.lookingAt()); + + if (matched) { + // Update the number of matched characters + result = matcher.end(); + + // Update the attributes with the variables value + String attributeName; + String attributeValue; + + for (int i = 0; i < getRegexVariables().size(); i++) { + attributeName = getRegexVariables().get(i); + attributeValue = matcher.group(i + 1); + Variable variable = getVariables().get(attributeName); + + if ((variable != null) && variable.isDecodingOnParse()) { + attributeValue = Reference.decode(attributeValue); + } + + if (loggable) { + getLogger() + .log( + Level.FINE, + "Template variable \"{0}\" matched with value \"{1}\"", + new Object[] {attributeName, attributeValue}); + } + + variables.put(attributeName, attributeValue); + } + } + } catch (StackOverflowError soe) { + getLogger() + .warning( + () -> + "StackOverflowError exception encountered while matching this string : " + + formattedString); + } + } + + return result; + } + + /** + * Attempts to parse a formatted reference. If the parsing succeeds, the given request's + * attributes are updated.
+ * Note that the values parsed are directly extracted from the formatted reference and are + * therefore not percent-decoded. + * + * @see Reference#decode(String) + * @param formattedString The string to parse. + * @param request The request to update. + * @return The number of matched characters or -1 if no character matched. + */ + public int parse(String formattedString, Request request) { + return parse(formattedString, request.getAttributes(), request.isLoggable()); + } + + /** + * Quotes special characters that could be taken for special Regex characters. + * + * @param character The character to quote if necessary. + * @return The quoted character. + */ + private String quote(char character) { + switch (character) { + case '[': + return "\\["; + case ']': + return "\\]"; + case '.': + return "\\."; + case '\\': + return "\\\\"; + case '$': + return "\\$"; + case '^': + return "\\^"; + case '?': + return "\\?"; + case '*': + return "\\*"; + case '|': + return "\\|"; + case '(': + return "\\("; + case ')': + return "\\)"; + case ':': + return "\\:"; + case '-': + return "\\-"; + case '!': + return "\\!"; + case '<': + return "\\<"; + case '>': + return "\\>"; + default: + return Character.toString(character); + } + } + + /** + * Sets the variable to use if no variable is given. + * + * @param defaultVariable + */ + public void setDefaultVariable(Variable defaultVariable) { + this.defaultVariable = defaultVariable; + } + + /** + * Indicates if the variables must be encoded when formatting the template. + * + * @param encodingVariables True if the variables must be encoded when formatting the template. + */ + public void setEncodingVariables(boolean encodingVariables) { + this.encodingVariables = encodingVariables; + } + + /** + * Sets the logger to use. + * + * @param logger The logger to use. + */ + public void setLogger(Logger logger) { + this.logger = logger; + } + + /** + * Sets the matching mode to use when parsing a formatted reference. + * + * @param matchingMode The matching mode to use when parsing a formatted reference. + */ + public void setMatchingMode(int matchingMode) { + this.matchingMode = matchingMode; + } + + /** + * Sets the pattern to use for formatting or parsing. + * + * @param pattern The pattern to use for formatting or parsing. + */ + public void setPattern(String pattern) { + this.pattern = pattern; + this.regexPattern = null; + } + + /** + * Sets the modifiable map of variables. + * + * @param variables The modifiable map of variables. + */ + public void setVariables(Map variables) { + synchronized (this.variables) { + if (variables != this.variables) { + this.variables.clear(); + + if (variables != null) { + this.variables.putAll(variables); + } + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/routing/TemplateRoute.java b/org.restlet/src/main/java/org/restlet/routing/TemplateRoute.java index 0ed7857070..3860b33430 100644 --- a/org.restlet/src/main/java/org/restlet/routing/TemplateRoute.java +++ b/org.restlet/src/main/java/org/restlet/routing/TemplateRoute.java @@ -1,245 +1,269 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; +import java.util.logging.Level; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.data.Reference; import org.restlet.data.Status; -import java.util.logging.Level; - /** - * Filter scoring the affinity of calls with the attached Restlet. The score is - * used by an associated Router to determine the most appropriate - * Restlet for a given call. The routing is based on a reference template.
+ * Filter scoring the affinity of calls with the attached Restlet. The score is used by an + * associated Router to determine the most appropriate Restlet for a given call. The routing is + * based on a reference template.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @see org.restlet.routing.Template * @author Jerome Louvel */ public class TemplateRoute extends Route { - /** - * Indicates whether the query part should be taken into account when matching a - * reference with the template. - */ - private volatile boolean matchingQuery; - - /** The reference template to match. */ - private volatile Template template; - - /** - * Constructor behaving as a simple extractor filter. - * - * @param next The next Restlet. - */ - public TemplateRoute(Restlet next) { - this(null, (Template) null, next); - } - - /** - * Constructor. The URIs will be matched against the template using the - * {@link Template#MODE_STARTS_WITH} matching mode. This can be changed by - * getting the template and calling {@link Template#setMatchingMode(int)} with - * {@link Template#MODE_EQUALS} for exact matching. - * - * @param router The parent router. - * @param uriTemplate The URI template. - * @param next The next Restlet. - */ - public TemplateRoute(Router router, String uriTemplate, Restlet next) { - this(router, new Template(uriTemplate, Template.MODE_STARTS_WITH, Variable.TYPE_URI_SEGMENT, "", true, false), - next); - } - - /** - * Constructor. - * - * @param router The parent router. - * @param template The URI template. - * @param next The next Restlet. - */ - public TemplateRoute(Router router, Template template, Restlet next) { - super(router, next); - this.matchingQuery = router == null || router.getDefaultMatchingQuery(); - this.template = template; - } - - /** - * Allows filtering before its handling by the target Restlet. By default, it - * parses the template variable, adjusts the base reference of the target - * resource's reference. - * - * @param request The request to filter. - * @param response The response to filter. - * @return The continuation status. - */ - @Override - protected int beforeHandle(Request request, Response response) { - // 1 - Parse the template variables and adjust the base reference - if (getTemplate() != null) { - String remainingPart = request.getResourceRef().getRemainingPart(false, isMatchingQuery()); - int matchedLength = getTemplate().parse(remainingPart, request); - - if (matchedLength == 0) { - if (request.isLoggable() && getLogger().isLoggable(Level.FINER)) { - getLogger().finer("No characters were matched"); - } - } else if (matchedLength > 0) { - if (request.isLoggable() && getLogger().isLoggable(Level.FINER)) { - getLogger().finer(matchedLength + " characters were matched"); - } - - // Updates the context - String matchedPart = remainingPart.substring(0, matchedLength); - Reference baseRef = request.getResourceRef().getBaseRef(); - - if (baseRef == null) { - baseRef = new Reference(matchedPart); - } else { - baseRef = new Reference(baseRef.toString(false, false) + matchedPart); - } - - request.getResourceRef().setBaseRef(baseRef); - - if (request.isLoggable()) { - if (getLogger().isLoggable(Level.FINE)) { - remainingPart = request.getResourceRef().getRemainingPart(false, isMatchingQuery()); + /** + * Indicates whether the query part should be taken into account when matching a reference with + * the template. + */ + private volatile boolean matchingQuery; + + /** The reference template to match. */ + private volatile Template template; + + /** + * Constructor behaving as a simple extractor filter. + * + * @param next The next Restlet. + */ + public TemplateRoute(Restlet next) { + this(null, (Template) null, next); + } + + /** + * Constructor. The URIs will be matched against the template using the {@link + * Template#MODE_STARTS_WITH} matching mode. This can be changed by getting the template and + * calling {@link Template#setMatchingMode(int)} with {@link Template#MODE_EQUALS} for exact + * matching. + * + * @param router The parent router. + * @param uriTemplate The URI template. + * @param next The next Restlet. + */ + public TemplateRoute(Router router, String uriTemplate, Restlet next) { + this( + router, + new Template( + uriTemplate, + Template.MODE_STARTS_WITH, + Variable.TYPE_URI_SEGMENT, + "", + true, + false), + next); + } + + /** + * Constructor. + * + * @param router The parent router. + * @param template The URI template. + * @param next The next Restlet. + */ + public TemplateRoute(Router router, Template template, Restlet next) { + super(router, next); + this.matchingQuery = router == null || router.getDefaultMatchingQuery(); + this.template = template; + } + + /** + * Allows filtering before its handling by the target Restlet. By default, it parses the + * template variable, adjusts the base reference of the target resource's reference. + * + * @param request The request to filter. + * @param response The response to filter. + * @return The continuation status. + */ + @Override + protected int beforeHandle(Request request, Response response) { + // 1 - Parse the template variables and adjust the base reference + if (getTemplate() != null) { + String remainingPart = + request.getResourceRef().getRemainingPart(false, isMatchingQuery()); + int matchedLength = getTemplate().parse(remainingPart, request); + + if (matchedLength == 0) { + if (request.isLoggable() && getLogger().isLoggable(Level.FINER)) { + getLogger().finer("No characters were matched"); + } + } else if (matchedLength > 0) { + if (request.isLoggable() && getLogger().isLoggable(Level.FINER)) { + getLogger().finer(matchedLength + " characters were matched"); + } + + // Updates the context + String matchedPart = remainingPart.substring(0, matchedLength); + Reference baseRef = request.getResourceRef().getBaseRef(); + + if (baseRef == null) { + baseRef = new Reference(matchedPart); + } else { + baseRef = new Reference(baseRef.toString(false, false) + matchedPart); + } + + request.getResourceRef().setBaseRef(baseRef); + + if (request.isLoggable()) { + if (getLogger().isLoggable(Level.FINE)) { + remainingPart = + request.getResourceRef().getRemainingPart(false, isMatchingQuery()); if (remainingPart == null || remainingPart.isEmpty()) { - getLogger().fine("New base URI: \"" + request.getResourceRef().getBaseRef() - + "\". No remaining part to match"); + getLogger() + .fine( + "New base URI: \"" + + request.getResourceRef().getBaseRef() + + "\". No remaining part to match"); } else { - getLogger().fine("New base URI: \"" + request.getResourceRef().getBaseRef() - + "\". New remaining part: \"" + remainingPart + "\""); + getLogger() + .fine( + "New base URI: \"" + + request.getResourceRef().getBaseRef() + + "\". New remaining part: \"" + + remainingPart + + "\""); } } - if (getLogger().isLoggable(Level.FINER)) { - getLogger().finer("Delegating the call to the target Restlet"); - } - } - } else { - if (request.isLoggable() && getLogger().isLoggable(Level.FINE)) { - getLogger().fine("Unable to match this pattern: " + getTemplate().getPattern()); - } - - response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); - } - } - - return CONTINUE; - } - - /** - * Returns the matching mode to use on the template when parsing a formatted - * reference. - * - * @return The matching mode to use. - */ - public int getMatchingMode() { - return getTemplate().getMatchingMode(); - } - - /** - * Returns the reference template to match. - * - * @return The reference template to match. - */ - public Template getTemplate() { - return this.template; - } - - /** - * Indicates whether the query part should be taken into account when matching a - * reference with the template. - * - * @return True if the query part of the reference should be taken into account, - * false otherwise. - */ - public boolean isMatchingQuery() { - return this.matchingQuery; - } - - /** - * Returns the score for a given call (between 0 and 1.0). - * - * @param request The request to score. - * @param response The response to score. - * @return The score for a given call (between 0 and 1.0). - */ - public float score(Request request, Response response) { - float result = 0F; - - if ((getRouter() != null) && (request.getResourceRef() != null) && (getTemplate() != null)) { - final String remainingPart = request.getResourceRef().getRemainingPart(false, isMatchingQuery()); - if (remainingPart != null) { - final int matchedLength = getTemplate().match(remainingPart); - - if (matchedLength != -1) { - final float totalLength = remainingPart.length(); - - if (totalLength > 0.0F) { - result = getRouter().getRequiredScore() - + (1.0F - getRouter().getRequiredScore()) * (matchedLength / totalLength); - } else { - result = 1.0F; - } - } - } - - if (request.isLoggable() && getLogger().isLoggable(Level.FINER)) { - getLogger().finer("Call score for the \"" + getTemplate().getPattern() + "\" URI pattern: " + result); - } - } - - return result; - } - - /** - * Sets the matching mode to use on the template when parsing a formatted - * reference. - * - * @param matchingMode The matching mode to use. - */ - public void setMatchingMode(int matchingMode) { - getTemplate().setMatchingMode(matchingMode); - } - - /** - * Sets whether the matching should be done on the URI with or without query - * string. - * - * @param matchingQuery True if the matching should be done with the query - * string, false otherwise. - */ - public void setMatchingQuery(boolean matchingQuery) { - this.matchingQuery = matchingQuery; - } - - /** - * Sets the reference template to match. - * - * @param template The reference template to match. - */ - public void setTemplate(Template template) { - this.template = template; - } - - @Override - public String toString() { - return "\"" + ((getTemplate() == null) ? super.toString() : getTemplate().getPattern()) + "\" -> " - + ((getNext() == null) ? "null" : getNext().toString()); - } + if (getLogger().isLoggable(Level.FINER)) { + getLogger().finer("Delegating the call to the target Restlet"); + } + } + } else { + if (request.isLoggable() && getLogger().isLoggable(Level.FINE)) { + getLogger().fine("Unable to match this pattern: " + getTemplate().getPattern()); + } + + response.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + } + } + + return CONTINUE; + } + + /** + * Returns the matching mode to use on the template when parsing a formatted reference. + * + * @return The matching mode to use. + */ + public int getMatchingMode() { + return getTemplate().getMatchingMode(); + } + + /** + * Returns the reference template to match. + * + * @return The reference template to match. + */ + public Template getTemplate() { + return this.template; + } + + /** + * Indicates whether the query part should be taken into account when matching a reference with + * the template. + * + * @return True if the query part of the reference should be taken into account, false + * otherwise. + */ + public boolean isMatchingQuery() { + return this.matchingQuery; + } + + /** + * Returns the score for a given call (between 0 and 1.0). + * + * @param request The request to score. + * @param response The response to score. + * @return The score for a given call (between 0 and 1.0). + */ + public float score(Request request, Response response) { + float result = 0F; + + if ((getRouter() != null) + && (request.getResourceRef() != null) + && (getTemplate() != null)) { + final String remainingPart = + request.getResourceRef().getRemainingPart(false, isMatchingQuery()); + if (remainingPart != null) { + final int matchedLength = getTemplate().match(remainingPart); + + if (matchedLength != -1) { + final float totalLength = remainingPart.length(); + + if (totalLength > 0.0F) { + result = + getRouter().getRequiredScore() + + (1.0F - getRouter().getRequiredScore()) + * (matchedLength / totalLength); + } else { + result = 1.0F; + } + } + } + + if (request.isLoggable() && getLogger().isLoggable(Level.FINER)) { + getLogger() + .finer( + "Call score for the \"" + + getTemplate().getPattern() + + "\" URI pattern: " + + result); + } + } + + return result; + } + + /** + * Sets the matching mode to use on the template when parsing a formatted reference. + * + * @param matchingMode The matching mode to use. + */ + public void setMatchingMode(int matchingMode) { + getTemplate().setMatchingMode(matchingMode); + } + + /** + * Sets whether the matching should be done on the URI with or without query string. + * + * @param matchingQuery True if the matching should be done with the query string, false + * otherwise. + */ + public void setMatchingQuery(boolean matchingQuery) { + this.matchingQuery = matchingQuery; + } + + /** + * Sets the reference template to match. + * + * @param template The reference template to match. + */ + public void setTemplate(Template template) { + this.template = template; + } + + @Override + public String toString() { + return "\"" + + ((getTemplate() == null) ? super.toString() : getTemplate().getPattern()) + + "\" -> " + + ((getNext() == null) ? "null" : getNext().toString()); + } } diff --git a/org.restlet/src/main/java/org/restlet/routing/Validator.java b/org.restlet/src/main/java/org/restlet/routing/Validator.java index bf824d981f..8170978255 100644 --- a/org.restlet/src/main/java/org/restlet/routing/Validator.java +++ b/org.restlet/src/main/java/org/restlet/routing/Validator.java @@ -1,177 +1,175 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.regex.Pattern; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.data.Status; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.regex.Pattern; - /** - * Filter validating attributes from a call. Validation is verified based on - * regex pattern matching.
+ * Filter validating attributes from a call. Validation is verified based on regex pattern matching. + *
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @author Jerome Louvel * @see Pattern */ public class Validator extends Filter { - /** Internal class holding validation information. */ - private static final class ValidateInfo { - /** Name of the attribute to look for. */ - protected String attribute; - - /** Format of the attribute value, using Regex pattern syntax. */ - protected String format; - - /** Indicates if the attribute presence is required. */ - protected boolean required; - - /** - * Constructor. - * - * @param attribute Name of the attribute to look for. - * @param required Indicates if the attribute presence is required. - * @param format Format of the attribute value, using Regex pattern syntax. - */ - public ValidateInfo(String attribute, boolean required, String format) { - this.attribute = attribute; - this.required = required; - this.format = format; - } - } - - /** The list of attribute validations. */ - private volatile List validations; - - /** - * Constructor. - */ - public Validator() { - this(null); - } - - /** - * Constructor. - * - * @param context The context. - */ - public Validator(Context context) { - this(context, null); - } - - /** - * Constructor. - * - * @param context The context. - * @param next The next Restlet. - */ - public Validator(Context context, Restlet next) { - super(context, next); - } - - /** - * Allows filtering before its handling by the target Restlet. By default it - * parses the template variable, adjust the base reference, then extracts the - * attributes from form parameters (query, cookies, entity) and finally tries to - * validate the variables as indicated by the - * {@link #validate(String, boolean, String)} method. - * - * @param request The request to filter. - * @param response The response to filter. - * @return The {@link Filter#CONTINUE} status. - */ - @Override - protected int beforeHandle(Request request, Response response) { - if (this.validations != null) { - for (ValidateInfo validate : getValidations()) { - if (validate.required && !request.getAttributes().containsKey(validate.attribute)) { - response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "Unable to find the \"" + validate.attribute - + "\" attribute in the request. Please check your request."); - } else if (validate.format != null) { - Object value = request.getAttributes().get(validate.attribute); - - if ((value != null) && !Pattern.matches(validate.format, value.toString())) { - response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, - "Unable to validate the value of the \"" + validate.attribute - + "\" attribute. The expected format is: " + validate.format - + " (Java Regex). Please check your request."); - } - } - } - } - - return CONTINUE; - } - - /** - * Returns the list of attribute validations. - * - * @return The list of attribute validations. - */ - private List getValidations() { - // Lazy initialization with double-check. - List v = this.validations; - if (v == null) { - synchronized (this) { - v = this.validations; - if (v == null) { - this.validations = v = new CopyOnWriteArrayList(); - } - } - } - return v; - } - - /** - * Checks the request attributes for presence or format. If the check fails, - * then a response status CLIENT_ERROR_BAD_REQUEST is returned with the proper - * status description. - * - * @param attribute Name of the attribute to look for. - * @param required Indicates if the attribute presence is required. - * @param format Format of the attribute value, using Regex pattern syntax. - */ - public void validate(String attribute, boolean required, String format) { - getValidations().add(new ValidateInfo(attribute, required, format)); - } - - /** - * Checks the request attributes for format only. If the check fails, then a - * response status CLIENT_ERROR_BAD_REQUEST is returned with the proper status - * description. - * - * @param attribute Name of the attribute to look for. - * @param format Format of the attribute value, using Regex pattern syntax. - */ - public void validateFormat(String attribute, String format) { - getValidations().add(new ValidateInfo(attribute, false, format)); - } - - /** - * Checks the request attributes for presence only. If the check fails, then a - * response status CLIENT_ERROR_BAD_REQUEST is returned with the proper status - * description. - * - * @param attribute Name of the attribute to look for. - */ - public void validatePresence(String attribute) { - getValidations().add(new ValidateInfo(attribute, true, null)); - } + /** Internal class holding validation information. */ + private static final class ValidateInfo { + /** Name of the attribute to look for. */ + protected String attribute; + + /** Format of the attribute value, using Regex pattern syntax. */ + protected String format; + + /** Indicates if the attribute presence is required. */ + protected boolean required; + + /** + * Constructor. + * + * @param attribute Name of the attribute to look for. + * @param required Indicates if the attribute presence is required. + * @param format Format of the attribute value, using Regex pattern syntax. + */ + public ValidateInfo(String attribute, boolean required, String format) { + this.attribute = attribute; + this.required = required; + this.format = format; + } + } + + /** The list of attribute validations. */ + private volatile List validations; + + /** Constructor. */ + public Validator() { + this(null); + } + + /** + * Constructor. + * + * @param context The context. + */ + public Validator(Context context) { + this(context, null); + } + + /** + * Constructor. + * + * @param context The context. + * @param next The next Restlet. + */ + public Validator(Context context, Restlet next) { + super(context, next); + } + + /** + * Allows filtering before its handling by the target Restlet. By default, it parses the + * template variable, adjust the base reference, then extracts the attributes from form + * parameters (query, cookies, entity) and finally tries to validate the variables as indicated + * by the {@link #validate(String, boolean, String)} method. + * + * @param request The request to filter. + * @param response The response to filter. + * @return The {@link Filter#CONTINUE} status. + */ + @Override + protected int beforeHandle(Request request, Response response) { + if (this.validations != null) { + for (ValidateInfo validate : getValidations()) { + if (validate.required && !request.getAttributes().containsKey(validate.attribute)) { + response.setStatus( + Status.CLIENT_ERROR_BAD_REQUEST, + "Unable to find the \"" + + validate.attribute + + "\" attribute in the request. Please check your request."); + } else if (validate.format != null) { + Object value = request.getAttributes().get(validate.attribute); + + if ((value != null) && !Pattern.matches(validate.format, value.toString())) { + response.setStatus( + Status.CLIENT_ERROR_BAD_REQUEST, + "Unable to validate the value of the \"" + + validate.attribute + + "\" attribute. The expected format is: " + + validate.format + + " (Java Regex). Please check your request."); + } + } + } + } + + return CONTINUE; + } + + /** + * Returns the list of attribute validations. + * + * @return The list of attribute validations. + */ + private List getValidations() { + // Lazy initialization with double-check. + List v = this.validations; + if (v == null) { + synchronized (this) { + v = this.validations; + if (v == null) { + this.validations = v = new CopyOnWriteArrayList<>(); + } + } + } + return v; + } + + /** + * Checks the request attributes for presence or format. If the check fails, then a response + * status CLIENT_ERROR_BAD_REQUEST is returned with the proper status description. + * + * @param attribute Name of the attribute to look for. + * @param required Indicates if the attribute presence is required. + * @param format Format of the attribute value, using Regex pattern syntax. + */ + public void validate(String attribute, boolean required, String format) { + getValidations().add(new ValidateInfo(attribute, required, format)); + } + + /** + * Checks the request attributes for format only. If the check fails, then a response status + * CLIENT_ERROR_BAD_REQUEST is returned with the proper status description. + * + * @param attribute Name of the attribute to look for. + * @param format Format of the attribute value, using Regex pattern syntax. + */ + public void validateFormat(String attribute, String format) { + getValidations().add(new ValidateInfo(attribute, false, format)); + } + + /** + * Checks the request attributes for presence only. If the check fails, then a response status + * CLIENT_ERROR_BAD_REQUEST is returned with the proper status description. + * + * @param attribute Name of the attribute to look for. + */ + public void validatePresence(String attribute) { + getValidations().add(new ValidateInfo(attribute, true, null)); + } } diff --git a/org.restlet/src/main/java/org/restlet/routing/Variable.java b/org.restlet/src/main/java/org/restlet/routing/Variable.java index ae61cf52e8..b2ac36233d 100644 --- a/org.restlet/src/main/java/org/restlet/routing/Variable.java +++ b/org.restlet/src/main/java/org/restlet/routing/Variable.java @@ -1,275 +1,271 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; import org.restlet.data.Reference; /** * Variable descriptor for reference templates. - * + * * @see Template * @author Jerome Louvel */ public final class Variable { - /** Matches all characters. */ - public static final int TYPE_ALL = 1; + /** Matches all characters. */ + public static final int TYPE_ALL = 1; - /** Matches all alphabetical characters. */ - public static final int TYPE_ALPHA = 2; + /** Matches all alphabetical characters. */ + public static final int TYPE_ALPHA = 2; - /** Matches all alphabetical and digital characters. */ - public static final int TYPE_ALPHA_DIGIT = 3; - - /** Matches any TEXT excluding "(" and ")". */ - public static final int TYPE_COMMENT = 4; - - /** Matches any TEXT inside a comment excluding ";". */ - public static final int TYPE_COMMENT_ATTRIBUTE = 5; - - /** Matches all digital characters. */ - public static final int TYPE_DIGIT = 6; - - /** Matches any CHAR except CTLs or separators. */ - public static final int TYPE_TOKEN = 7; - - /** Matches all URI characters. */ - public static final int TYPE_URI_ALL = 8; - - /** Matches URI fragment characters. */ - public static final int TYPE_URI_FRAGMENT = 9; - - /** Matches URI path characters (not the query or the fragment parts). */ - public static final int TYPE_URI_PATH = 10; - - /** Matches URI query characters. */ - public static final int TYPE_URI_QUERY = 11; - - /** Matches URI query parameter characters (name or value). */ - public static final int TYPE_URI_QUERY_PARAM = 12; - - /** Matches URI scheme characters. */ - public static final int TYPE_URI_SCHEME = 13; - - /** Matches URI segment characters. */ - public static final int TYPE_URI_SEGMENT = 14; - - /** Matches unreserved URI characters. */ - public static final int TYPE_URI_UNRESERVED = 15; - - /** Matches all alphabetical and digital characters plus the underscore. */ - public static final int TYPE_WORD = 16; - - /** Indicates if the parsed value must be decoded. */ - private volatile boolean decodingOnParse; - - /** The default value to use if the key couldn't be found in the model. */ - private volatile String defaultValue; - - /** Indicates if the formatted value must be encoded. */ - private volatile boolean encodingOnFormat; - - /** - * Indicates if the value is fixed, in which case the "defaultValue" property is - * always used. - */ - private volatile boolean fixed; - - /** Indicates if the variable is required or optional. */ - private volatile boolean required; - - /** The type of variable. See TYPE_* constants. */ - private volatile int type; - - /** - * Default constructor. Type is TYPE_ALL, default value is "", required is true - * and fixed is false. - */ - public Variable() { - this(Variable.TYPE_ALL, "", true, false); - } - - /** - * Constructor. Default value is "", required is true and fixed is false. - * - * @param type The type of variable. See TYPE_* constants. - */ - public Variable(int type) { - this(type, "", true, false); - } - - /** - * Constructor. - * - * @param type The type of variable. See TYPE_* constants. - * @param defaultValue The default value to use if the key couldn't be found in - * the model. - * @param required Indicates if the variable is required or optional. - * @param fixed Indicates if the value is fixed, in which case the - * "defaultValue" property is always used. - */ - public Variable(int type, String defaultValue, boolean required, boolean fixed) { - this(type, defaultValue, required, fixed, false, false); - } - - /** - * Constructor. - * - * @param type The type of variable. See TYPE_* constants. - * @param defaultValue The default value to use if the key couldn't be found - * in the model. - * @param required Indicates if the variable is required or optional. - * @param fixed Indicates if the value is fixed, in which case the - * "defaultValue" property is always used. - * @param decodingOnParse Indicates if the parsed value must be decoded. - * @param encodingOnFormat Indicates if the formatted value must be encoded. - */ - public Variable(int type, String defaultValue, boolean required, boolean fixed, boolean decodingOnParse, - boolean encodingOnFormat) { - this.type = type; - this.defaultValue = defaultValue; - this.required = required; - this.fixed = fixed; - this.decodingOnParse = decodingOnParse; - this.encodingOnFormat = encodingOnFormat; - } - - /** - * According to the type of the variable, encodes the value given in parameters. - * - * @param value The value to encode. - * @return The encoded value, according to the variable type. - */ - public String encode(String value) { + /** Matches all alphabetical and digital characters. */ + public static final int TYPE_ALPHA_DIGIT = 3; + + /** Matches any TEXT excluding "(" and ")". */ + public static final int TYPE_COMMENT = 4; + + /** Matches any TEXT inside a comment excluding ";". */ + public static final int TYPE_COMMENT_ATTRIBUTE = 5; + + /** Matches all digital characters. */ + public static final int TYPE_DIGIT = 6; + + /** Matches any CHAR except CTLs or separators. */ + public static final int TYPE_TOKEN = 7; + + /** Matches all URI characters. */ + public static final int TYPE_URI_ALL = 8; + + /** Matches URI fragment characters. */ + public static final int TYPE_URI_FRAGMENT = 9; + + /** Matches URI path characters (not the query or the fragment parts). */ + public static final int TYPE_URI_PATH = 10; + + /** Matches URI query characters. */ + public static final int TYPE_URI_QUERY = 11; + + /** Matches URI query parameter characters (name or value). */ + public static final int TYPE_URI_QUERY_PARAM = 12; + + /** Matches URI scheme characters. */ + public static final int TYPE_URI_SCHEME = 13; + + /** Matches URI segment characters. */ + public static final int TYPE_URI_SEGMENT = 14; + + /** Matches unreserved URI characters. */ + public static final int TYPE_URI_UNRESERVED = 15; + + /** Matches all alphabetical and digital characters plus the underscore. */ + public static final int TYPE_WORD = 16; + + /** Indicates if the parsed value must be decoded. */ + private volatile boolean decodingOnParse; + + /** The default value to use if the key couldn't be found in the model. */ + private volatile String defaultValue; + + /** Indicates if the formatted value must be encoded. */ + private volatile boolean encodingOnFormat; + + /** + * Indicates if the value is fixed, in which case the "defaultValue" property is always used. + */ + private volatile boolean fixed; + + /** Indicates if the variable is required or optional. */ + private volatile boolean required; + + /** The type of variable. See TYPE_* constants. */ + private volatile int type; + + /** + * Default constructor. Type is TYPE_ALL, default value is "", required is true, and fixed is + * false. + */ + public Variable() { + this(Variable.TYPE_ALL, "", true, false); + } + + /** + * Constructor. Default value is "", required is true and fixed is false. + * + * @param type The type of variable. See TYPE_* constants. + */ + public Variable(int type) { + this(type, "", true, false); + } + + /** + * Constructor. + * + * @param type The type of variable. See TYPE_* constants. + * @param defaultValue The default value to use if the key couldn't be found in the model. + * @param required Indicates if the variable is required or optional. + * @param fixed Indicates if the value is fixed, in which case the "defaultValue" property is + * always used. + */ + public Variable(int type, String defaultValue, boolean required, boolean fixed) { + this(type, defaultValue, required, fixed, false, false); + } + + /** + * Constructor. + * + * @param type The type of variable. See TYPE_* constants. + * @param defaultValue The default value to use if the key couldn't be found in the model. + * @param required Indicates if the variable is required or optional. + * @param fixed Indicates if the value is fixed, in which case the "defaultValue" property is + * always used. + * @param decodingOnParse Indicates if the parsed value must be decoded. + * @param encodingOnFormat Indicates if the formatted value must be encoded. + */ + public Variable( + int type, + String defaultValue, + boolean required, + boolean fixed, + boolean decodingOnParse, + boolean encodingOnFormat) { + this.type = type; + this.defaultValue = defaultValue; + this.required = required; + this.fixed = fixed; + this.decodingOnParse = decodingOnParse; + this.encodingOnFormat = encodingOnFormat; + } + + /** + * According to the type of the variable, encodes the value given in parameters. + * + * @param value The value to encode. + * @return The encoded value, according to the variable type. + */ + public String encode(String value) { return switch (this.type) { case Variable.TYPE_URI_ALL, - Variable.TYPE_URI_UNRESERVED, - Variable.TYPE_URI_FRAGMENT, - Variable.TYPE_URI_PATH, - Variable.TYPE_URI_QUERY, - Variable.TYPE_URI_SEGMENT -> Reference.encode(value); + Variable.TYPE_URI_UNRESERVED, + Variable.TYPE_URI_FRAGMENT, + Variable.TYPE_URI_PATH, + Variable.TYPE_URI_QUERY, + Variable.TYPE_URI_SEGMENT -> + Reference.encode(value); default -> value; }; - } - - /** - * Returns the default value to use if the key couldn't be found in the model. - * - * @return The default value to use if the key couldn't be found in the model. - */ - public String getDefaultValue() { - return this.defaultValue; - } - - /** - * Returns the type of variable. See TYPE_* constants. - * - * @return The type of variable. See TYPE_* constants. - */ - public int getType() { - return this.type; - } - - /** - * Indicates if the parsed value must be decoded. - * - * @return True if the parsed value must be decoded, false otherwise. - */ - public boolean isDecodingOnParse() { - return this.decodingOnParse; - } - - /** - * Indicates if the formatted value must be encoded. - * - * @return True if the formatted value must be encoded, false otherwise. - */ - public boolean isEncodingOnFormat() { - return this.encodingOnFormat; - } - - /** - * Returns true if the value is fixed, in which case the "defaultValue" property - * is always used. - * - * @return True if the value is fixed, in which case the "defaultValue" property - * is always used. - */ - public boolean isFixed() { - return this.fixed; - } - - /** - * Returns true if the variable is required or optional. - * - * @return True if the variable is required or optional. - */ - public boolean isRequired() { - return this.required; - } - - /** - * Indicates if the parsed value must be decoded. - * - * @param decodingOnParse True if the parsed value must be decoded, false - * otherwise. - */ - public void setDecodingOnParse(boolean decodingOnParse) { - this.decodingOnParse = decodingOnParse; - } - - /** - * Sets the default value to use if the key couldn't be found in the model. - * - * @param defaultValue The default value to use if the key couldn't be found in - * the model. - */ - public void setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue; - } - - /** - * Indicates if the formatted value must be encoded. - * - * @param encodingOnFormat True if the formatted value must be encoded, false - * otherwise. - */ - public void setEncodingOnFormat(boolean encodingOnFormat) { - this.encodingOnFormat = encodingOnFormat; - } - - /** - * Indicates if the value is fixed - * - * @param fixed True if the value is fixed - */ - public void setFixed(boolean fixed) { - this.fixed = fixed; - } - - /** - * Indicates if the variable is required or optional. - * - * @param required True if the variable is required or optional. - */ - public void setRequired(boolean required) { - this.required = required; - } - - /** - * Sets the type of variable. See TYPE_* constants. - * - * @param type The type of variable. - */ - public void setType(int type) { - this.type = type; - } - + } + + /** + * Returns the default value to use if the key couldn't be found in the model. + * + * @return The default value to use if the key couldn't be found in the model. + */ + public String getDefaultValue() { + return this.defaultValue; + } + + /** + * Returns the type of variable. See TYPE_* constants. + * + * @return The type of variable. See TYPE_* constants. + */ + public int getType() { + return this.type; + } + + /** + * Indicates if the parsed value must be decoded. + * + * @return True if the parsed value must be decoded, false otherwise. + */ + public boolean isDecodingOnParse() { + return this.decodingOnParse; + } + + /** + * Indicates if the formatted value must be encoded. + * + * @return True if the formatted value must be encoded, false otherwise. + */ + public boolean isEncodingOnFormat() { + return this.encodingOnFormat; + } + + /** + * Returns true if the value is fixed, in which case the "defaultValue" property is always used. + * + * @return True if the value is fixed, in which case the "defaultValue" property is always used. + */ + public boolean isFixed() { + return this.fixed; + } + + /** + * Returns true if the variable is required or optional. + * + * @return True if the variable is required or optional. + */ + public boolean isRequired() { + return this.required; + } + + /** + * Indicates if the parsed value must be decoded. + * + * @param decodingOnParse True if the parsed value must be decoded, false otherwise. + */ + public void setDecodingOnParse(boolean decodingOnParse) { + this.decodingOnParse = decodingOnParse; + } + + /** + * Sets the default value to use if the key couldn't be found in the model. + * + * @param defaultValue The default value to use if the key couldn't be found in the model. + */ + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + /** + * Indicates if the formatted value must be encoded. + * + * @param encodingOnFormat True if the formatted value must be encoded, false otherwise. + */ + public void setEncodingOnFormat(boolean encodingOnFormat) { + this.encodingOnFormat = encodingOnFormat; + } + + /** + * Indicates if the value is fixed + * + * @param fixed True if the value is fixed + */ + public void setFixed(boolean fixed) { + this.fixed = fixed; + } + + /** + * Indicates if the variable is required or optional. + * + * @param required True if the variable is required or optional. + */ + public void setRequired(boolean required) { + this.required = required; + } + + /** + * Sets the type of variable. See TYPE_* constants. + * + * @param type The type of variable. + */ + public void setType(int type) { + this.type = type; + } } diff --git a/org.restlet/src/main/java/org/restlet/routing/VirtualHost.java b/org.restlet/src/main/java/org/restlet/routing/VirtualHost.java index 0587f371ff..00504e8ee4 100644 --- a/org.restlet/src/main/java/org/restlet/routing/VirtualHost.java +++ b/org.restlet/src/main/java/org/restlet/routing/VirtualHost.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; +import java.net.InetAddress; +import java.net.UnknownHostException; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -16,467 +17,465 @@ import org.restlet.resource.Finder; import org.restlet.resource.ServerResource; -import java.net.InetAddress; -import java.net.UnknownHostException; - /** - * Router of calls from Server connectors to Restlets. The attached Restlets are - * typically Applications.
+ * Router of calls from Server connectors to Restlets. The attached Restlets are typically + * Applications.
*
* A virtual host is defined along three properties: + * *

    - *
  • request's {@link Request#getHostRef()}: the URI of the host that received - * the request. Note that the same IP address can correspond to multiple domain - * names and therefore receive request with different "hostRef" URIs.
  • - *
  • request's {@link Request#getResourceRef()}: the URI of the target - * resource of the request. If this reference is relative, then it is based on - * the "hostRef", otherwise it is maintained as received. This difference is - * useful for resources identified by URNs or for Web proxies or Web - * caches.
  • - *
  • response's {@link Response#getServerInfo()}: the information about the - * server connector receiving the requests such as it IP address and port - * number.
  • + *
  • request's {@link Request#getHostRef()}: the URI of the host that received the request. Note + * that the same IP address can correspond to multiple domain names and therefore receive + * request with different "hostRef" URIs. + *
  • request's {@link Request#getResourceRef()}: the URI of the target resource of the request. + * If this reference is relative, then it is based on the "hostRef", otherwise it is + * maintained as received. This difference is useful for resources identified by URNs or for + * Web proxies or Web caches. + *
  • response's {@link Response#getServerInfo()}: the information about the server connector + * receiving the requests such as it IP address and port number. *
- * When creating a new instance, you can define Java regular expressions ( - * {@link java.util.regex.Pattern}) that must match the domain name, port, - * scheme for references or IP address and port number for server information. - * The default values match everything.
+ * + * When creating a new instance, you can define Java regular expressions ( {@link + * java.util.regex.Pattern}) that must match the domain name, port, scheme for references or IP + * address and port number for server information. The default values match everything.
*
- * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * + * Concurrency note: instances of this class or its subclasses can be invoked by several threads at + * the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * * @see java.util.regex.Pattern - * @see Wikipedia - - * Virtual Hosting - * @see Apache - Virtual - * Hosting + * @see Wikipedia - Virtual Hosting + * @see Apache - Virtual Hosting * @author Jerome Louvel */ public class VirtualHost extends Router { - private static final ThreadLocal CURRENT = new ThreadLocal(); - - /** - * Returns the virtual host code associated to the current thread. - * - * This variable is stored internally as a thread local variable and updated - * each time a call is routed by a virtual host. - * - * @return The current context. - */ - public static Integer getCurrent() { - return CURRENT.get(); - } - - /** - * Returns the IP address of a given domain name. - * - * @param domain The domain name. - * @return The IP address. - */ - public static String getIpAddress(String domain) { - String result = null; - - try { - result = InetAddress.getByName(domain).getHostAddress(); - } catch (UnknownHostException e) { - } - - return result; - } - - /** - * Returns the local host IP address. - * - * @return The local host IP address. - */ - public static String getLocalHostAddress() { - String result = null; - - try { - result = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException e) { - } - - return result; - } - - /** - * Returns the local host name. - * - * @return The local host name. - */ - public static String getLocalHostName() { - String result = null; - - try { - result = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - } - - return result; - } - - /** - * Sets the virtual host code associated with the current thread. - * - * @param code The thread's virtual host code. - */ - public static void setCurrent(Integer code) { - CURRENT.set(code); - } - - /** The hostRef host domain pattern to match. */ - private volatile String hostDomain; - - /** The hostRef host port pattern to match. */ - private volatile String hostPort; - - /** The hostRef scheme pattern to match. */ - private volatile String hostScheme; - - /** The parent component's context. */ - private volatile Context parentContext; - - /** The resourceRef host domain pattern to match. */ - private volatile String resourceDomain; - - /** The resourceRef host port pattern to match. */ - private volatile String resourcePort; - - /** The resourceRef scheme pattern to match. */ - private volatile String resourceScheme; - - /** The listening server address pattern to match. */ - private volatile String serverAddress; - - /** The listening server port pattern to match. */ - private volatile String serverPort; - - /** - * Constructor. Note that usage of this constructor is not recommended as the - * virtual host won't have a proper context set. In general you will prefer to - * use the other constructor and pass it the parent component's context. - */ - public VirtualHost() { - this(null); - } - - /** - * Constructor. Accepts all incoming requests by default, use the set methods to - * restrict the matchable patterns. - * - * @param parentContext The parent component's context. - */ - public VirtualHost(Context parentContext) { - this(parentContext, ".*", ".*", ".*", ".*", ".*", ".*", ".*", ".*"); - } - - /** - * Constructor. - * - * @param parentContext The parent component's context. - * @param hostDomain The hostRef host domain pattern to match. - * @param hostPort The hostRef host port pattern to match. - * @param hostScheme The hostRef scheme protocol pattern to match. - * @param resourceDomain The resourceRef host domain pattern to match. - * @param resourcePort The resourceRef host port pattern to match. - * @param resourceScheme The resourceRef scheme protocol pattern to match. - * @param serverAddress The listening server address pattern to match. - * @param serverPort The listening server port pattern to match. - * @see java.util.regex.Pattern - */ - public VirtualHost(Context parentContext, String hostDomain, String hostPort, String hostScheme, - String resourceDomain, String resourcePort, String resourceScheme, String serverAddress, - String serverPort) { - super((parentContext == null) ? null : parentContext.createChildContext()); - - // Override Router's default modes - setDefaultMatchingMode(Template.MODE_STARTS_WITH); - setRoutingMode(MODE_BEST_MATCH); - - this.parentContext = parentContext; - - this.hostDomain = hostDomain; - this.hostPort = hostPort; - this.hostScheme = hostScheme; - - this.resourceDomain = resourceDomain; - this.resourcePort = resourcePort; - this.resourceScheme = resourceScheme; - - this.serverAddress = serverAddress; - this.serverPort = serverPort; - } - - /** - * Attaches a target Restlet to this router with an empty URI pattern. A new - * route will be added routing to the target when any call is received. - * - * In addition to super class behavior, this method will set the context of the - * target if it is empty by creating a protected context via the - * {@link Context#createChildContext()} method. - * - * @param target The target Restlet to attach. - * @return The created route. - */ - @Override - public TemplateRoute attach(Restlet target) { - checkContext(target); - return super.attach(target); - } - - /** - * Attaches a target Restlet to this router based on a given URI pattern. A new - * route will be added routing to the target when calls with a URI matching the - * pattern will be received. - * - * In addition to super class behavior, this method will set the context of the - * target if it is empty by creating a protected context via the - * {@link Context#createChildContext()} method. - * - * @param uriPattern The URI pattern that must match the relative part of the - * resource URI. - * @param target The target Restlet to attach. - * @return The created route. - */ - @Override - public TemplateRoute attach(String uriPattern, Restlet target) { - checkContext(target); - return super.attach(uriPattern, target); - } - - /** - * Attaches a Restlet to this router as the default target to invoke when no - * route matches. It actually sets a default route that scores all calls to 1.0. - * - * In addition to super class behavior, this method will set the context of the - * target if it is empty by creating a protected context via the - * {@link Context#createChildContext()} method. - * - * @param defaultTarget The Restlet to use as the default target. - * @return The created route. - */ - @Override - public TemplateRoute attachDefault(Restlet defaultTarget) { - checkContext(defaultTarget); - return super.attachDefault(defaultTarget); - } - - /** - * Checks the context and sets it if necessary. - * - * @param target The target Restlet. - */ - protected void checkContext(Restlet target) { - if ((target.getContext() == null) && (this.parentContext != null)) { - target.setContext(this.parentContext.createChildContext()); - } - } - - /** - * Creates a new finder instance based on the "targetClass" property. - * - * In addition to super class behavior, this method will set the context of the - * finder by creating a protected context via the - * {@link Context#createChildContext()} method. - * - * @param targetClass The target Resource class to attach. - * @return The new finder instance. - */ - @Override - public Finder createFinder(Class targetClass) { - Finder result = super.createFinder(targetClass); - result.setContext(getContext().createChildContext()); - return result; - } - - @Override - protected TemplateRoute createRoute(String uriPattern, Restlet target, int matchingMode) { - TemplateRoute result = new TemplateRoute(this, uriPattern, target) { - @Override - protected int beforeHandle(Request request, Response response) { - final int result = super.beforeHandle(request, response); - - // Set the request's root reference - request.setRootRef(request.getResourceRef().getBaseRef()); - - // Save the hash code of the current host - setCurrent(VirtualHost.this.hashCode()); - - return result; - } - }; - - result.getTemplate().setMatchingMode(matchingMode); - result.setMatchingQuery(getDefaultMatchingQuery()); - return result; - } - - /** - * Returns the hostRef host domain to match. See the - * {@link java.util.regex.Pattern} class for details on the syntax. - * - * @return The hostRef host domain to match. - */ - public String getHostDomain() { - return this.hostDomain; - } - - /** - * Returns the hostRef host port to match. See the - * {@link java.util.regex.Pattern} class for details on the syntax. - * - * @return The hostRef host port to match. - */ - public String getHostPort() { - return this.hostPort; - } - - /** - * Returns the hostRef scheme to match. See the {@link java.util.regex.Pattern} - * class for details on the syntax. - * - * @return The hostRef scheme to match. - */ - public String getHostScheme() { - return this.hostScheme; - } - - /** - * Returns the resourceRef host domain to match. See the - * {@link java.util.regex.Pattern} class for details on the syntax. - * - * @return The resourceRef host domain to match. - */ - public String getResourceDomain() { - return this.resourceDomain; - } - - /** - * Returns the resourceRef host port to match. See the - * {@link java.util.regex.Pattern} class for details on the syntax. - * - * @return The resourceRef host port to match. - */ - public String getResourcePort() { - return this.resourcePort; - } - - /** - * Returns the resourceRef scheme to match. See the - * {@link java.util.regex.Pattern} class for details on the syntax. - * - * @return The resourceRef scheme to match. - */ - public String getResourceScheme() { - return this.resourceScheme; - } - - /** - * Returns the listening server address. See the {@link java.util.regex.Pattern} - * class for details on the syntax. - * - * @return The listening server address. - */ - public String getServerAddress() { - return this.serverAddress; - } - - /** - * Returns the listening server port. See the {@link java.util.regex.Pattern} - * class for details on the syntax. - * - * @return The listening server port. - */ - public String getServerPort() { - return this.serverPort; - } - - @Override - public void setContext(Context parentContext) { - this.parentContext = parentContext; - super.setContext((parentContext == null) ? null : parentContext.createChildContext()); - } - - /** - * Sets the hostRef host domain to match. See the - * {@link java.util.regex.Pattern} class for details on the syntax. - * - * @param hostDomain The hostRef host domain to match. - */ - public void setHostDomain(String hostDomain) { - this.hostDomain = hostDomain; - } - - /** - * Sets the hostRef host port to match. See the {@link java.util.regex.Pattern} - * class for details on the syntax. - * - * @param hostPort The hostRef host port to match. - */ - public void setHostPort(String hostPort) { - this.hostPort = hostPort; - } - - /** - * Sets the hostRef scheme to match. See the {@link java.util.regex.Pattern} - * class for details on the syntax. - * - * @param hostScheme The hostRef scheme to match. - */ - public void setHostScheme(String hostScheme) { - this.hostScheme = hostScheme; - } - - /** - * Sets the resourceRef host domain to match. See the - * {@link java.util.regex.Pattern} class for details on the syntax. - * - * @param resourceDomain The resourceRef host domain to match. - */ - public void setResourceDomain(String resourceDomain) { - this.resourceDomain = resourceDomain; - } - - /** - * Sets the resourceRef host port to match. See the - * {@link java.util.regex.Pattern} class for details on the syntax. - * - * @param resourcePort The resourceRef host port to match. - */ - public void setResourcePort(String resourcePort) { - this.resourcePort = resourcePort; - } - - /** - * Sets the resourceRef scheme to match. See the {@link java.util.regex.Pattern} - * class for details on the syntax. - * - * @param resourceScheme The resourceRef scheme to match. - */ - public void setResourceScheme(String resourceScheme) { - this.resourceScheme = resourceScheme; - } - - /** - * Sets the listening server address. See the {@link java.util.regex.Pattern} - * class for details on the syntax. - * - * @param serverAddress The listening server address. - */ - public void setServerAddress(String serverAddress) { - this.serverAddress = serverAddress; - } - - /** - * Sets the listening server port. See the {@link java.util.regex.Pattern} class - * for details on the syntax. - * - * @param serverPort The listening server port. - */ - public void setServerPort(String serverPort) { - this.serverPort = serverPort; - } - + private static final ThreadLocal CURRENT = new ThreadLocal<>(); + + /** + * Returns the virtual host code associated with the current thread. + * + *

This variable is stored internally as a thread local variable and updated each time a call + * is routed by a virtual host. + * + * @return The current context. + */ + public static Integer getCurrent() { + return CURRENT.get(); + } + + /** + * Returns the IP address of a given domain name. + * + * @param domain The domain name. + * @return The IP address. + */ + public static String getIpAddress(String domain) { + String result = null; + + try { + result = InetAddress.getByName(domain).getHostAddress(); + } catch (UnknownHostException ignored) { + // Ignored exception, the result is null + } + + return result; + } + + /** + * Returns the local host IP address. + * + * @return The local host IP address. + */ + public static String getLocalHostAddress() { + String result = null; + + try { + result = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException ignored) { + // Ignored exception, the result is null + } + + return result; + } + + /** + * Returns the local host name. + * + * @return The local host name. + */ + public static String getLocalHostName() { + String result = null; + + try { + result = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException ignored) { + // Ignored exception, the result is null + } + + return result; + } + + /** + * Sets the virtual host code associated with the current thread. + * + * @param code The thread's virtual host code. + */ + public static void setCurrent(Integer code) { + CURRENT.set(code); + } + + /** The hostRef host domain pattern to match. */ + private volatile String hostDomain; + + /** The hostRef host port pattern to match. */ + private volatile String hostPort; + + /** The hostRef scheme pattern to match. */ + private volatile String hostScheme; + + /** The parent component's context. */ + private volatile Context parentContext; + + /** The resourceRef host domain pattern to match. */ + private volatile String resourceDomain; + + /** The resourceRef host port pattern to match. */ + private volatile String resourcePort; + + /** The resourceRef scheme pattern to match. */ + private volatile String resourceScheme; + + /** The listening server address pattern to match. */ + private volatile String serverAddress; + + /** The listening server port pattern to match. */ + private volatile String serverPort; + + /** + * Constructor. Note that usage of this constructor is not recommended as the virtual host won't + * have a proper context set. In general, you will prefer to use the other constructor and pass + * it the parent component's context. + */ + public VirtualHost() { + this(null); + } + + /** + * Constructor. Accepts all incoming requests by default, use the set methods to restrict the + * matchable patterns. + * + * @param parentContext The parent component's context. + */ + public VirtualHost(Context parentContext) { + this(parentContext, ".*", ".*", ".*", ".*", ".*", ".*", ".*", ".*"); + } + + /** + * Constructor. + * + * @param parentContext The parent component's context. + * @param hostDomain The hostRef host domain pattern to match. + * @param hostPort The hostRef host port pattern to match. + * @param hostScheme The hostRef scheme protocol pattern to match. + * @param resourceDomain The resourceRef host domain pattern to match. + * @param resourcePort The resourceRef host port pattern to match. + * @param resourceScheme The resourceRef scheme protocol pattern to match. + * @param serverAddress The listening server address pattern to match. + * @param serverPort The listening server port pattern to match. + * @see java.util.regex.Pattern + */ + public VirtualHost( + Context parentContext, + String hostDomain, + String hostPort, + String hostScheme, + String resourceDomain, + String resourcePort, + String resourceScheme, + String serverAddress, + String serverPort) { + super((parentContext == null) ? null : parentContext.createChildContext()); + + // Override Router's default modes + setDefaultMatchingMode(Template.MODE_STARTS_WITH); + setRoutingMode(MODE_BEST_MATCH); + + this.parentContext = parentContext; + + this.hostDomain = hostDomain; + this.hostPort = hostPort; + this.hostScheme = hostScheme; + + this.resourceDomain = resourceDomain; + this.resourcePort = resourcePort; + this.resourceScheme = resourceScheme; + + this.serverAddress = serverAddress; + this.serverPort = serverPort; + } + + /** + * Attaches a target Restlet to this router with an empty URI pattern. A new route will be added + * routing to the target when any call is received. + * + *

In addition to super class behavior, this method will set the context of the target if it + * is empty by creating a protected context via the {@link Context#createChildContext()} method. + * + * @param target The target Restlet to attach. + * @return The created route. + */ + @Override + public TemplateRoute attach(Restlet target) { + checkContext(target); + return super.attach(target); + } + + /** + * Attaches a target Restlet to this router based on a given URI pattern. A new route will be + * added routing to the target when calls with a URI matching the pattern are received. + * + *

In addition to super class behavior, this method will set the context of the target if it + * is empty by creating a protected context via the {@link Context#createChildContext()} method. + * + * @param uriPattern The URI pattern that must match the relative part of the resource URI. + * @param target The target Restlet to attach. + * @return The created route. + */ + @Override + public TemplateRoute attach(String uriPattern, Restlet target) { + checkContext(target); + return super.attach(uriPattern, target); + } + + /** + * Attaches a Restlet to this router as the default target to invoke when no route matches. It + * actually sets a default route that scores all calls to 1.0. + * + *

In addition to super class behavior, this method will set the context of the target if it + * is empty by creating a protected context via the {@link Context#createChildContext()} method. + * + * @param defaultTarget The Restlet to use as the default target. + * @return The created route. + */ + @Override + public TemplateRoute attachDefault(Restlet defaultTarget) { + checkContext(defaultTarget); + return super.attachDefault(defaultTarget); + } + + /** + * Checks the context and sets it if necessary. + * + * @param target The target Restlet. + */ + protected void checkContext(Restlet target) { + if ((target.getContext() == null) && (this.parentContext != null)) { + target.setContext(this.parentContext.createChildContext()); + } + } + + /** + * Creates a new finder instance based on the "targetClass" property. + * + *

In addition to super class behavior, this method will set the context of the finder by + * creating a protected context via the {@link Context#createChildContext()} method. + * + * @param targetClass The target Resource class to attach. + * @return The new finder instance. + */ + @Override + public Finder createFinder(Class targetClass) { + Finder result = super.createFinder(targetClass); + result.setContext(getContext().createChildContext()); + return result; + } + + @Override + protected TemplateRoute createRoute(String uriPattern, Restlet target, int matchingMode) { + TemplateRoute result = + new TemplateRoute(this, uriPattern, target) { + @Override + protected int beforeHandle(Request request, Response response) { + final int result = super.beforeHandle(request, response); + + // Set the request's root reference + request.setRootRef(request.getResourceRef().getBaseRef()); + + // Save the hash code of the current host + setCurrent(VirtualHost.this.hashCode()); + + return result; + } + }; + + result.getTemplate().setMatchingMode(matchingMode); + result.setMatchingQuery(getDefaultMatchingQuery()); + return result; + } + + /** + * Returns the hostRef host domain to match. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @return The hostRef host domain to match. + */ + public String getHostDomain() { + return this.hostDomain; + } + + /** + * Returns the hostRef host port to match. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @return The hostRef host port to match. + */ + public String getHostPort() { + return this.hostPort; + } + + /** + * Returns the hostRef scheme to match. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @return The hostRef scheme to match. + */ + public String getHostScheme() { + return this.hostScheme; + } + + /** + * Returns the resourceRef host domain to match. See the {@link java.util.regex.Pattern} class + * for details on the syntax. + * + * @return The resourceRef host domain to match. + */ + public String getResourceDomain() { + return this.resourceDomain; + } + + /** + * Returns the resourceRef host port to match. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @return The resourceRef host port to match. + */ + public String getResourcePort() { + return this.resourcePort; + } + + /** + * Returns the resourceRef scheme to match. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @return The resourceRef scheme to match. + */ + public String getResourceScheme() { + return this.resourceScheme; + } + + /** + * Returns the listening server address. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @return The listening server address. + */ + public String getServerAddress() { + return this.serverAddress; + } + + /** + * Returns the listening server port. See the {@link java.util.regex.Pattern} class for details + * on the syntax. + * + * @return The listening server port. + */ + public String getServerPort() { + return this.serverPort; + } + + @Override + public void setContext(Context parentContext) { + this.parentContext = parentContext; + super.setContext((parentContext == null) ? null : parentContext.createChildContext()); + } + + /** + * Sets the hostRef host domain to match. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @param hostDomain The hostRef host domain to match. + */ + public void setHostDomain(String hostDomain) { + this.hostDomain = hostDomain; + } + + /** + * Sets the hostRef host port to match. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @param hostPort The hostRef host port to match. + */ + public void setHostPort(String hostPort) { + this.hostPort = hostPort; + } + + /** + * Sets the hostRef scheme to match. See the {@link java.util.regex.Pattern} class for details + * on the syntax. + * + * @param hostScheme The hostRef scheme to match. + */ + public void setHostScheme(String hostScheme) { + this.hostScheme = hostScheme; + } + + /** + * Sets the resourceRef host domain to match. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @param resourceDomain The resourceRef host domain to match. + */ + public void setResourceDomain(String resourceDomain) { + this.resourceDomain = resourceDomain; + } + + /** + * Sets the resourceRef host port to match. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @param resourcePort The resourceRef host port to match. + */ + public void setResourcePort(String resourcePort) { + this.resourcePort = resourcePort; + } + + /** + * Sets the resourceRef scheme to match. See the {@link java.util.regex.Pattern} class for + * details on the syntax. + * + * @param resourceScheme The resourceRef scheme to match. + */ + public void setResourceScheme(String resourceScheme) { + this.resourceScheme = resourceScheme; + } + + /** + * Sets the listening server address. See the {@link java.util.regex.Pattern} class for details + * on the syntax. + * + * @param serverAddress The listening server address. + */ + public void setServerAddress(String serverAddress) { + this.serverAddress = serverAddress; + } + + /** + * Sets the listening server port. See the {@link java.util.regex.Pattern} class for details on + * the syntax. + * + * @param serverPort The listening server port. + */ + public void setServerPort(String serverPort) { + this.serverPort = serverPort; + } } diff --git a/org.restlet/src/main/java/org/restlet/routing/package-info.java b/org.restlet/src/main/java/org/restlet/routing/package-info.java new file mode 100644 index 0000000000..c891425a8f --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/routing/package-info.java @@ -0,0 +1,8 @@ +/** + * Classes related to call routing. + * + * @since Restlet 2.0 + * @see User + * Guide - Routing package + */ +package org.restlet.routing; diff --git a/org.restlet/src/main/java/org/restlet/routing/package.html b/org.restlet/src/main/java/org/restlet/routing/package.html deleted file mode 100644 index 825cbce9e2..0000000000 --- a/org.restlet/src/main/java/org/restlet/routing/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - - Classes related to call routing. -

- @since Restlet 2.0 - @see User Guide - Routing package - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/security/Authenticator.java b/org.restlet/src/main/java/org/restlet/security/Authenticator.java index 1f47571a2c..467fee1444 100644 --- a/org.restlet/src/main/java/org/restlet/security/Authenticator.java +++ b/org.restlet/src/main/java/org/restlet/security/Authenticator.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -16,252 +16,249 @@ import org.restlet.data.Status; import org.restlet.routing.Filter; -import java.util.logging.Level; - /** - * Filter authenticating the client sending the inbound request. Its main role - * is to inspect various credentials provided by the client and to add related - * application roles to the request's {@link ClientInfo} property. - * + * Filter authenticating the client sending the inbound request. Its main role is to inspect various + * credentials provided by the client and to add related application roles to the request's {@link + * ClientInfo} property. + * * @author Jerome Louvel */ public abstract class Authenticator extends Filter { - /** - * Invoked upon successful authentication to update the subject with new - * principals. - */ - private volatile Enroler enroler; - - /** - * Indicates if the authenticator should attempt to authenticate an already - * authenticated client. By default, it is set to true. - */ - private volatile boolean multiAuthenticating; + /** Invoked upon successful authentication to update the subject with new principals. */ + private volatile Enroler enroler; - /** - * Indicates if the authenticator is not required to succeed. In those cases, - * the attached Restlet is invoked. Note that authentication will be attempted - * independently of this property unless the client is already authenticated and - * the {@link #isMultiAuthenticating()} prevents multiple authentications. - */ - private volatile boolean optional; + /** + * Indicates if the authenticator should attempt to authenticate an already authenticated + * client. By default, it is set to true. + */ + private volatile boolean multiAuthenticating; - /** - * Constructor setting the mode to "required". - * - * @param context The context. - * @see #Authenticator(Context, boolean) - */ - public Authenticator(Context context) { - this(context, false); - } + /** + * Indicates if the authenticator is not required to succeed. In those cases, the attached + * Restlet is invoked. Note that authentication will be attempted independently of this property + * unless the client is already authenticated and the {@link #isMultiAuthenticating()} prevents + * multiple authentications. + */ + private volatile boolean optional; - /** - * Constructor using the context's default enroler. - * - * @param context The context. - * @param optional Indicates if the authenticator is not required to succeed. - * @see #Authenticator(Context, boolean, Enroler) - */ - public Authenticator(Context context, boolean optional) { - this(context, optional, (context != null) ? context.getDefaultEnroler() : null); - } + /** + * Constructor setting the mode to "required". + * + * @param context The context. + * @see #Authenticator(Context, boolean) + */ + protected Authenticator(Context context) { + this(context, false); + } - /** - * Constructor. - * - * @param context The context. - * @param multiAuthenticating Indicates if the authenticator should attempt to - * authenticate an already authenticated client. - * @param optional Indicates if the authenticator is not required to - * succeed. - * @param enroler The enroler to invoke upon successful - * authentication. - */ - public Authenticator(Context context, boolean multiAuthenticating, boolean optional, Enroler enroler) { - super(context); - this.multiAuthenticating = multiAuthenticating; - this.optional = optional; - this.enroler = enroler; - } + /** + * Constructor using the context's default enroler. + * + * @param context The context. + * @param optional Indicates if the authenticator is not required to succeed. + * @see #Authenticator(Context, boolean, Enroler) + */ + protected Authenticator(Context context, boolean optional) { + this(context, optional, (context != null) ? context.getDefaultEnroler() : null); + } - /** - * Constructor. - * - * @param context The context. - * @param optional Indicates if the authenticator is not required to succeed. - * @param enroler The enroler to invoke upon successful authentication. - */ - public Authenticator(Context context, boolean optional, Enroler enroler) { - this(context, true, optional, enroler); - } + /** + * Constructor. + * + * @param context The context. + * @param multiAuthenticating Indicates if the authenticator should attempt to authenticate an + * already authenticated client. + * @param optional Indicates if the authenticator is not required to succeed. + * @param enroler The enroler to invoke upon successful authentication. + */ + protected Authenticator( + Context context, boolean multiAuthenticating, boolean optional, Enroler enroler) { + super(context); + this.multiAuthenticating = multiAuthenticating; + this.optional = optional; + this.enroler = enroler; + } - /** - * Attempts to authenticate the subject sending the request. - * - * @param request The request sent. - * @param response The response to update. - * @return True if the authentication succeeded. - */ - protected abstract boolean authenticate(Request request, Response response); + /** + * Constructor. + * + * @param context The context. + * @param optional Indicates if the authenticator is not required to succeed. + * @param enroler The enroler to invoke upon successful authentication. + */ + protected Authenticator(Context context, boolean optional, Enroler enroler) { + this(context, true, optional, enroler); + } - /** - * Invoked upon successful authentication. By default, it updates the request's - * clientInfo and challengeResponse "authenticated" properties, clears the - * existing challenge requests on the response, calls the enroler and finally - * returns {@link Filter#CONTINUE}. - * - * @param request The request sent. - * @param response The response to update. - * @return The filter continuation code. - */ - protected int authenticated(Request request, Response response) { - boolean loggable = request.isLoggable() && getLogger().isLoggable(Level.FINE); + /** + * Attempts to authenticate the subject sending the request. + * + * @param request The request sent. + * @param response The response to update. + * @return True if the authentication succeeded. + */ + protected abstract boolean authenticate(Request request, Response response); - if (loggable && request.getChallengeResponse() != null) { - getLogger().log(Level.FINE, - "The authentication succeeded for the identifer \"" + request.getChallengeResponse().getIdentifier() - + "\" using the " + request.getChallengeResponse().getScheme() + " scheme."); - } + /** + * Invoked upon successful authentication. By default, it updates the request's clientInfo and + * challengeResponse "authenticated" properties, clears the existing challenge requests on the + * response, calls the enroler and finally returns {@link Filter#CONTINUE}. + * + * @param request The request sent. + * @param response The response to update. + * @return The filter continuation code. + */ + protected int authenticated(Request request, Response response) { + boolean loggable = request.isLoggable() && getLogger().isLoggable(Level.FINE); - // Update the client info accordingly - if (request.getClientInfo() != null) { - request.getClientInfo().setAuthenticated(true); - } + if (loggable && request.getChallengeResponse() != null) { + getLogger() + .fine( + () -> + "The authentication succeeded for the identifier \"" + + request.getChallengeResponse().getIdentifier() + + "\" using the " + + request.getChallengeResponse().getScheme() + + " scheme."); + } - // Clear previous challenge requests - response.getChallengeRequests().clear(); + // Update the client info accordingly + if (request.getClientInfo() != null) { + request.getClientInfo().setAuthenticated(true); + } - // Add the roles for the authenticated subject - if (getEnroler() != null) { - getEnroler().enrole(request.getClientInfo()); - } + // Clear previous challenge requests + response.getChallengeRequests().clear(); - return CONTINUE; - } + // Add the roles for the authenticated subject + if (getEnroler() != null) { + getEnroler().enrole(request.getClientInfo()); + } - /** - * Handles the authentication by first invoking the - * {@link #authenticate(Request, Response)} method, only if - * {@link #isMultiAuthenticating()} returns true and if - * {@link ClientInfo#isAuthenticated()} returns false. If the method is invoked - * and returns true, the {@link #authenticated(Request, Response)} is called. - * Otherwise, if {@link #isOptional()} returns true it continues to the next - * Restlet or if it returns false it calls the - * {@link #unauthenticated(Request, Response)} method. - */ - @Override - protected int beforeHandle(Request request, Response response) { - if (isMultiAuthenticating() || !request.getClientInfo().isAuthenticated()) { - if (authenticate(request, response)) { - return authenticated(request, response); - } else if (isOptional()) { - response.setStatus(Status.SUCCESS_OK); - return CONTINUE; - } else { - return unauthenticated(request, response); - } - } else { - return CONTINUE; - } - } + return CONTINUE; + } - /** - * Returns the enroler invoked upon successful authentication to update the - * subject with new principals. Typically new {@link Role} are added based on - * the available {@link User} instances available. - * - * @return The enroler invoked upon successful authentication - */ - public Enroler getEnroler() { - return enroler; - } + /** + * Handles the authentication by first invoking the {@link #authenticate(Request, Response)} + * method, only if {@link #isMultiAuthenticating()} returns true and if {@link + * ClientInfo#isAuthenticated()} returns false. If the method is invoked and returns true, the + * {@link #authenticated(Request, Response)} is called. Otherwise, if {@link #isOptional()} + * returns true it continues to the next Restlet or if it returns false it calls the {@link + * #unauthenticated(Request, Response)} method. + */ + @Override + protected int beforeHandle(Request request, Response response) { + if (isMultiAuthenticating() || !request.getClientInfo().isAuthenticated()) { + if (authenticate(request, response)) { + return authenticated(request, response); + } else if (isOptional()) { + response.setStatus(Status.SUCCESS_OK); + return CONTINUE; + } else { + return unauthenticated(request, response); + } + } else { + return CONTINUE; + } + } - /** - * Indicates if the authenticator should attempt to authenticate an already - * authenticated client. The client is considered authenticated if - * {@link ClientInfo#isAuthenticated()} returns true. By default, it is set to - * true. - * - * @return True if the authenticator should attempt to authenticate an already - * authenticated client. - */ - public boolean isMultiAuthenticating() { - return multiAuthenticating; - } + /** + * Returns the enroler invoked upon successful authentication to update the subject with new + * principals. Typically, new {@link Role} are added based on the available {@link User} + * instances available. + * + * @return The enroler invoked upon successful authentication + */ + public Enroler getEnroler() { + return enroler; + } - /** - * Indicates if the authenticator is not required to succeed. In those cases, - * the attached Restlet is invoked. Note that authentication will be attempted - * independently of this property unless the client is already authenticated and - * the {@link #isMultiAuthenticating()} prevents multiple authentications. - * - * @return True if the authentication success is optional. - */ - public boolean isOptional() { - return optional; - } + /** + * Indicates if the authenticator should attempt to authenticate an already authenticated + * client. The client is considered authenticated if {@link ClientInfo#isAuthenticated()} + * returns true. By default, it is set to true. + * + * @return True if the authenticator should attempt to authenticate an already authenticated + * client. + */ + public boolean isMultiAuthenticating() { + return multiAuthenticating; + } - /** - * Sets the enroler invoked upon successful authentication. - * - * @param enroler The enroler invoked upon successful authentication. - */ - public void setEnroler(Enroler enroler) { - this.enroler = enroler; - } + /** + * Indicates if the authenticator is not required to succeed. In those cases, the attached + * Restlet is invoked. Note that authentication will be attempted independently of this property + * unless the client is already authenticated and the {@link #isMultiAuthenticating()} prevents + * multiple authentications. + * + * @return True if the authentication success is optional. + */ + public boolean isOptional() { + return optional; + } - /** - * Indicates if the authenticator should attempt to authenticate an already - * authenticated client. The client is considered authenticated if - * {@link ClientInfo#isAuthenticated()} returns true. By default, it is set to - * true. - * - * @param multiAuthenticating True if the authenticator should attempt to - * authenticate an already authenticated client. - */ - public void setMultiAuthenticating(boolean multiAuthenticating) { - this.multiAuthenticating = multiAuthenticating; - } + /** + * Sets the enroler invoked upon successful authentication. + * + * @param enroler The enroler invoked upon successful authentication. + */ + public void setEnroler(Enroler enroler) { + this.enroler = enroler; + } - /** - * Indicates if the authenticator is not required to succeed. In those cases, - * the attached Restlet is invoked. Note that authentication will be attempted - * independently of this property unless the client is already authenticated and - * the {@link #isMultiAuthenticating()} prevents multiple authentications. - * - * @param optional True if the authentication success is optional. - */ - public void setOptional(boolean optional) { - this.optional = optional; - } + /** + * Indicates if the authenticator should attempt to authenticate an already authenticated + * client. The client is considered authenticated if {@link ClientInfo#isAuthenticated()} + * returns true. By default, it is set to true. + * + * @param multiAuthenticating True if the authenticator should attempt to authenticate an + * already authenticated client. + */ + public void setMultiAuthenticating(boolean multiAuthenticating) { + this.multiAuthenticating = multiAuthenticating; + } - /** - * Invoked upon failed authentication. By default, it updates the request's - * clientInfo and challengeResponse "authenticated" properties, and returns - * {@link Filter#STOP}. - * - * @param request The request sent. - * @param response The response to update. - * @return The filter continuation code. - */ - protected int unauthenticated(Request request, Response response) { - boolean loggable = request.isLoggable() && getLogger().isLoggable(Level.FINE); + /** + * Indicates if the authenticator is not required to succeed. In those cases, the attached + * Restlet is invoked. Note that authentication will be attempted independently of this property + * unless the client is already authenticated and the {@link #isMultiAuthenticating()} prevents + * multiple authentications. + * + * @param optional True if the authentication success is optional. + */ + public void setOptional(boolean optional) { + this.optional = optional; + } - if (request.getChallengeResponse() != null && loggable) { - getLogger().log(Level.FINE, - "The authentication failed for the identifier \"" + request.getChallengeResponse().getIdentifier() - + "\" using the " + request.getChallengeResponse().getScheme() + " scheme."); - } + /** + * Invoked upon failed authentication. By default, it updates the request's clientInfo and + * challengeResponse "authenticated" properties, and returns {@link Filter#STOP}. + * + * @param request The request sent. + * @param response The response to update. + * @return The filter continuation code. + */ + protected int unauthenticated(Request request, Response response) { + boolean loggable = request.isLoggable() && getLogger().isLoggable(Level.FINE); - // Update the client info accordingly - if (request.getClientInfo() != null) { - request.getClientInfo().setAuthenticated(false); - } + if (request.getChallengeResponse() != null && loggable) { + getLogger() + .fine( + () -> + "The authentication failed for the identifier \"" + + request.getChallengeResponse().getIdentifier() + + "\" using the " + + request.getChallengeResponse().getScheme() + + " scheme."); + } - // Stop the filtering chain - return STOP; - } + // Update the client info accordingly + if (request.getClientInfo() != null) { + request.getClientInfo().setAuthenticated(false); + } + // Stop the filtering chain + return STOP; + } } diff --git a/org.restlet/src/main/java/org/restlet/security/Authorizer.java b/org.restlet/src/main/java/org/restlet/security/Authorizer.java index df080bc53f..602b3bfed6 100644 --- a/org.restlet/src/main/java/org/restlet/security/Authorizer.java +++ b/org.restlet/src/main/java/org/restlet/security/Authorizer.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import org.restlet.Request; @@ -18,128 +17,124 @@ import org.restlet.routing.Filter; /** - * Filter authorizing inbound request. It can be attached to protect a set of - * downstream {@link Restlet} and {@link ServerResource} objects. - * + * Filter authorizing inbound request. It can be attached to protect a set of downstream {@link + * Restlet} and {@link ServerResource} objects. + * * @author Jerome Louvel */ public abstract class Authorizer extends Filter { - /** Authorizer returning true all the time. */ - public static final Authorizer ALWAYS = new Authorizer() { - @Override - public boolean authorize(Request request, Response response) { - return true; - } - }; - - /** - * Authorizer returning true for all authenticated requests. For unauthenticated - * requests, it sets the response's status to - * {@link Status#CLIENT_ERROR_UNAUTHORIZED} instead of the default - * {@link Status#CLIENT_ERROR_FORBIDDEN}. - * - * @see ClientInfo#isAuthenticated() - */ - public static final Authorizer AUTHENTICATED = new Authorizer() { - @Override - public boolean authorize(Request request, Response response) { - return request.getClientInfo().isAuthenticated(); - } - - @Override - protected int unauthorized(Request request, Response response) { - response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED); - return STOP; - } - }; - - /** Authorizer returning false all the time. */ - public static final Authorizer NEVER = new Authorizer() { - @Override - public boolean authorize(Request request, Response response) { - return false; - } - }; - - /** The identifier unique within an application. */ - private volatile String identifier; - - /** - * Default constructor. - */ - public Authorizer() { - } - - /** - * Constructor. - * - * @param identifier The identifier unique within an application. - */ - public Authorizer(String identifier) { - this.identifier = identifier; - } - - /** - * Attempts to authorize the request. - * - * @param request The request sent. - * @param response The response to update. - * @return True if the authorization succeeded. - */ - protected abstract boolean authorize(Request request, Response response); - - /** - * Invoked upon successful authorization. Returns {@link Filter#CONTINUE} by - * default. - * - * @param request The request sent. - * @param response The response to update. - * @return The filter continuation code. - */ - protected int authorized(Request request, Response response) { - return CONTINUE; - } - - @Override - protected int beforeHandle(Request request, Response response) { - if (authorize(request, response)) { - return authorized(request, response); - } - - return unauthorized(request, response); - } - - /** - * Returns the identifier unique within an application. - * - * @return The identifier unique within an application. - */ - public String getIdentifier() { - return identifier; - } - - /** - * Sets the identifier unique within an application. - * - * @param identifier The identifier unique within an application. - */ - public void setIdentifier(String identifier) { - this.identifier = identifier; - } - - /** - * Invoked upon failed authorization. Sets the status to - * {@link Status#CLIENT_ERROR_FORBIDDEN} and returns {@link Filter#STOP} by - * default. - * - * @param request The request sent. - * @param response The response to update. - * @return The filter continuation code. - */ - protected int unauthorized(Request request, Response response) { - response.setStatus(Status.CLIENT_ERROR_FORBIDDEN); - return STOP; - } - + /** Authorizer returning true all the time. */ + public static final Authorizer ALWAYS = + new Authorizer() { + @Override + public boolean authorize(Request request, Response response) { + return true; + } + }; + + /** + * Authorizer returning true for all authenticated requests. For unauthenticated requests, it + * sets the response's status to {@link Status#CLIENT_ERROR_UNAUTHORIZED} instead of the default + * {@link Status#CLIENT_ERROR_FORBIDDEN}. + * + * @see ClientInfo#isAuthenticated() + */ + public static final Authorizer AUTHENTICATED = + new Authorizer() { + @Override + public boolean authorize(Request request, Response response) { + return request.getClientInfo().isAuthenticated(); + } + + @Override + protected int unauthorized(Request request, Response response) { + response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED); + return STOP; + } + }; + + /** Authorizer returning false all the time. */ + public static final Authorizer NEVER = + new Authorizer() { + @Override + public boolean authorize(Request request, Response response) { + return false; + } + }; + + /** The identifier unique within an application. */ + private volatile String identifier; + + /** Default constructor. */ + protected Authorizer() {} + + /** + * Constructor. + * + * @param identifier The identifier unique within an application. + */ + protected Authorizer(String identifier) { + this.identifier = identifier; + } + + /** + * Attempts to authorize the request. + * + * @param request The request sent. + * @param response The response to update. + * @return True if the authorization succeeded. + */ + protected abstract boolean authorize(Request request, Response response); + + /** + * Invoked upon successful authorization. Returns {@link Filter#CONTINUE} by default. + * + * @param request The request sent. + * @param response The response to update. + * @return The filter continuation code. + */ + protected int authorized(Request request, Response response) { + return CONTINUE; + } + + @Override + protected int beforeHandle(Request request, Response response) { + if (authorize(request, response)) { + return authorized(request, response); + } + + return unauthorized(request, response); + } + + /** + * Returns the identifier unique within an application. + * + * @return The identifier unique within an application. + */ + public String getIdentifier() { + return identifier; + } + + /** + * Sets the identifier unique within an application. + * + * @param identifier The identifier unique within an application. + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Invoked upon failed authorization. Sets the status to {@link Status#CLIENT_ERROR_FORBIDDEN} + * and returns {@link Filter#STOP} by default. + * + * @param request The request sent. + * @param response The response to update. + * @return The filter continuation code. + */ + protected int unauthorized(Request request, Response response) { + response.setStatus(Status.CLIENT_ERROR_FORBIDDEN); + return STOP; + } } diff --git a/org.restlet/src/main/java/org/restlet/security/CertificateAuthenticator.java b/org.restlet/src/main/java/org/restlet/security/CertificateAuthenticator.java index d5863072f8..837308bda6 100644 --- a/org.restlet/src/main/java/org/restlet/security/CertificateAuthenticator.java +++ b/org.restlet/src/main/java/org/restlet/security/CertificateAuthenticator.java @@ -1,119 +1,113 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; -import org.restlet.Context; -import org.restlet.Request; -import org.restlet.Response; -import org.restlet.data.Status; - -import javax.security.auth.x500.X500Principal; import java.security.Principal; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import javax.security.auth.x500.X500Principal; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.Status; /** - * Authenticator based on the SSL client certificate. If a client certificate is - * presented, and accepted by your SSL certificate truststore, it adds the - * Principal of its subject to the list of principals in the request's - * ClientInfo. It also sets the user to be a new User based on this Principal. - * - * {@link #getPrincipals(List)} and {@link #getUser(Principal)} can be - * overridden to change the default behavior. - * + * Authenticator based on the SSL client certificate. If a client certificate is presented, and + * accepted by your SSL certificate truststore, it adds the Principal of its subject to the list of + * principals in the request's ClientInfo. It also sets the user to be a new User based on this + * Principal. + * + *

{@link #getPrincipals(List)} and {@link #getUser(Principal)} can be overridden to change the + * default behavior. + * * @author Bruno Harbulot (bruno/distributedmatter.net) */ public class CertificateAuthenticator extends Authenticator { - /** - * - * @param context - */ - public CertificateAuthenticator(Context context) { - super(context); - } - - /** - * Extracts the Principal of the subject to use from a chain of certificate. By - * default, this is the X500Principal of the subject of the first - * certificate in the chain. - * - * @see X509Certificate - * @see X500Principal - * @param certificateChain chain of client certificates. - * @return Principal of the client certificate or null if the chain is empty. - */ - protected List getPrincipals(List certificateChain) { - ArrayList principals = null; + /** + * @param context + */ + public CertificateAuthenticator(Context context) { + super(context); + } - if ((certificateChain != null) && (!certificateChain.isEmpty())) { - Certificate userCert = certificateChain.get(0); + /** + * Extracts the Principal of the subject to use from a chain of certificate. By default, this is + * the X500Principal of the subject of the first certificate in the chain. + * + * @see X509Certificate + * @see X500Principal + * @param certificateChain chain of client certificates. + * @return Principal of the client certificate or null if the chain is empty. + */ + protected List getPrincipals(List certificateChain) { + ArrayList principals = null; - if (userCert instanceof X509Certificate) { - principals = new ArrayList(); - principals.add(((X509Certificate) userCert).getSubjectX500Principal()); - } + if ((certificateChain != null) && (!certificateChain.isEmpty())) { + Certificate userCert = certificateChain.getFirst(); - return principals; - } else { - return null; - } - } + if (userCert instanceof X509Certificate x509Certificate) { + principals = new ArrayList<>(); + principals.add(x509Certificate.getSubjectX500Principal()); + } - /** - * Creates a new User based on the subject's X500Principal. By default, the user - * name is the subject distinguished name, formatted accorded to RFC 2253. Some - * may choose to extract the Common Name only, for example. - * - * @param principal subject's Principal (most likely X500Principal). - * @return User instance corresponding to this principal or null. - */ - protected User getUser(Principal principal) { - if (principal != null) { - return new User(principal.getName()); - } else { - return null; - } - } + return principals; + } else { + return null; + } + } - /** - * Authenticates the call using the X.509 client certificate. The verification - * of the credentials is normally done by the SSL layer, via the TrustManagers. - * - * It uses the certificate chain in the request's - * "org.restlet.https.clientCertificates" attribute, adds the principal returned - * from this chain by {@link #getPrincipals(List)} to the request's ClientInfo - * and set the user to the result of {@link #getUser(Principal)} if that user is - * non-null. - * - * If no client certificate is available, then a 401 status is set. - */ - @Override - protected boolean authenticate(Request request, Response response) { - List certchain = request.getClientInfo().getCertificates(); - List principals = getPrincipals(certchain); + /** + * Creates a new User based on the subject's X500Principal. By default, the username is the + * subject distinguished name, formatted according to RFC 2253. Some may choose to extract the + * Common Name only, for example. + * + * @param principal subject's Principal (most likely X500Principal). + * @return User instance corresponding to this principal or null. + */ + protected User getUser(Principal principal) { + if (principal != null) { + return new User(principal.getName()); + } else { + return null; + } + } - if ((principals != null) && (!principals.isEmpty())) { - request.getClientInfo().getPrincipals().addAll(principals); - User user = getUser(principals.get(0)); + /** + * Authenticates the call using the X.509 client certificate. The SSL layer normally does the + * verification of the credentials, via the TrustManagers. + * + *

It uses the certificate chain in the request's "org.restlet.https.clientCertificates" + * attribute, adds the principal returned from this chain by {@link #getPrincipals(List)} to the + * request's ClientInfo, and set the user to the result of {@link #getUser(Principal)} if that + * user is non-null. + * + *

If no client certificate is available, then a 401 status is set. + */ + @Override + protected boolean authenticate(Request request, Response response) { + List certChain = request.getClientInfo().getCertificates(); + List principals = getPrincipals(certChain); - if (user != null) { - request.getClientInfo().setUser(user); - } - return true; - } else { - response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED); - return false; - } - } + if ((principals != null) && (!principals.isEmpty())) { + request.getClientInfo().getPrincipals().addAll(principals); + User user = getUser(principals.getFirst()); + if (user != null) { + request.getClientInfo().setUser(user); + } + return true; + } else { + response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED); + return false; + } + } } diff --git a/org.restlet/src/main/java/org/restlet/security/ChallengeAuthenticator.java b/org.restlet/src/main/java/org/restlet/security/ChallengeAuthenticator.java index 2fedc570e6..769a513aac 100644 --- a/org.restlet/src/main/java/org/restlet/security/ChallengeAuthenticator.java +++ b/org.restlet/src/main/java/org/restlet/security/ChallengeAuthenticator.java @@ -1,25 +1,27 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; -import org.restlet.data.*; - -import java.util.logging.Level; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.ClientInfo; +import org.restlet.data.Status; /** - * Authenticator based on a challenge scheme. This is typically used to support - * the HTTP BASIC and DIGEST challenge schemes. - * + * Authenticator based on a challenge scheme. This is typically used to support the HTTP BASIC and + * DIGEST challenge schemes. + * * @see ChallengeScheme * @see ChallengeRequest * @see ChallengeResponse @@ -27,275 +29,287 @@ */ public class ChallengeAuthenticator extends Authenticator { - /** The authentication realm. */ - private volatile String realm; - - /** - * Indicates if a new challenge should be sent when invalid credentials are - * received (true by default to conform to HTTP recommendations). - */ - private volatile boolean rechallenging; - - /** The expected challenge scheme. */ - private final ChallengeScheme scheme; - - /** The credentials verifier. */ - private volatile Verifier verifier; - - /** - * Constructor using the context's default verifier. - * - * @param context The context. - * @param optional Indicates if the authentication success is optional. - * @param challengeScheme The authentication scheme to use. - * @param realm The authentication realm. - * - * @see #ChallengeAuthenticator(Context, boolean, ChallengeScheme, String, - * Verifier) - */ - public ChallengeAuthenticator(Context context, boolean optional, ChallengeScheme challengeScheme, String realm) { - this(context, optional, challengeScheme, realm, (context != null) ? context.getDefaultVerifier() : null); - } - - /** - * Constructor. - * - * @param context The context. - * @param optional Indicates if the authentication success is optional. - * @param challengeScheme The authentication scheme to use. - * @param realm The authentication realm. - * @param verifier The credentials verifier. - */ - public ChallengeAuthenticator(Context context, boolean optional, ChallengeScheme challengeScheme, String realm, - Verifier verifier) { - super(context, optional); - this.realm = realm; - this.rechallenging = true; - this.scheme = challengeScheme; - this.verifier = verifier; - } - - /** - * Constructor setting the optional property to false. - * - * @param context The context. - * @param challengeScheme The authentication scheme to use. - * @param realm The authentication realm. - * @see #ChallengeAuthenticator(Context, boolean, ChallengeScheme, String, - * Verifier) - */ - public ChallengeAuthenticator(Context context, ChallengeScheme challengeScheme, String realm) { - this(context, false, challengeScheme, realm); - } - - /** - * Authenticates the call, relying on the verifier to check the credentials - * provided (in general an identifier + secret couple). If the credentials are - * valid, the next Restlet attached is invoked.
- *
- * If the credentials are missing, then {@link #challenge(Response, boolean)} is - * invoked.
- *
- * If the credentials are invalid and if the "rechallenge" property is true then - * {@link #challenge(Response, boolean)} is invoked. Otherwise, - * {@link #forbid(Response)} is invoked.
- *
- * If the credentials are stale, then {@link #challenge(Response, boolean)} is - * invoked with the "stale" parameter to true.
- *
- * At the end of the process, the {@link ClientInfo#setAuthenticated(boolean)} - * method is invoked. - */ - @Override - protected boolean authenticate(Request request, Response response) { - boolean result = false; - boolean loggable = request.isLoggable() && getLogger().isLoggable(Level.FINE); - - if (getVerifier() != null) { - switch (getVerifier().verify(request, response)) { - case Verifier.RESULT_VALID: - // Valid credentials provided - result = true; - - if (loggable) { - ChallengeResponse challengeResponse = request.getChallengeResponse(); - - if (challengeResponse != null) { - getLogger().fine("Authentication succeeded. Valid credentials provided for identifier: " - + request.getChallengeResponse().getIdentifier() + "."); - } else { - getLogger().fine("Authentication succeeded. Valid credentials provided."); - } - } - break; - case Verifier.RESULT_MISSING: - // No credentials provided - if (loggable) { - getLogger().fine("Authentication failed. No credentials provided."); - } - - if (!isOptional()) { - challenge(response, false); - } - break; - case Verifier.RESULT_INVALID: - // Invalid credentials provided - if (loggable) { - getLogger().fine("Authentication failed. Invalid credentials provided."); - } - - if (!isOptional()) { - if (isRechallenging()) { - challenge(response, false); - } else { - forbid(response); - } - } - break; - case Verifier.RESULT_STALE: - if (loggable) { - getLogger().fine("Authentication failed. Stale credentials provided."); - } - - if (!isOptional()) { - challenge(response, true); - } - break; - case Verifier.RESULT_UNKNOWN: - if (loggable) { - getLogger().fine("Authentication failed. Identifier is unknown."); - } - - if (!isOptional()) { - if (isRechallenging()) { - challenge(response, false); - } else { - forbid(response); - } - } - break; - } - } else { - getLogger().warning("Authentication failed. No verifier provided."); - response.setStatus(Status.SERVER_ERROR_INTERNAL, "Authentication failed. No verifier provided."); - } - - return result; - } - - /** - * Challenges the client by adding a challenge request to the response and by - * setting the status to {@link Status#CLIENT_ERROR_UNAUTHORIZED}. - * - * @param response The response to update. - * @param stale Indicates if the new challenge is due to a stale response. - */ - public void challenge(Response response, boolean stale) { - boolean loggable = response.getRequest().isLoggable() && getLogger().isLoggable(Level.FINE); - - if (loggable) { - getLogger().log(Level.FINE, "An authentication challenge was requested."); - } - - response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED); - response.getChallengeRequests().add(createChallengeRequest(stale)); - } - - /** - * Creates a new challenge request. - * - * @param stale Indicates if the new challenge is due to a stale response. - * @return A new challenge request. - */ - protected ChallengeRequest createChallengeRequest(boolean stale) { - return new ChallengeRequest(getScheme(), getRealm()); - } - - /** - * Rejects the call due to a failed authentication or authorization. This can be - * overridden to change the default behavior, for example to display an error - * page. By default, if authentication is required, the challenge method is - * invoked, otherwise the call status is set to CLIENT_ERROR_FORBIDDEN. - * - * @param response The reject response. - */ - public void forbid(Response response) { - boolean loggable = response.getRequest().isLoggable() && getLogger().isLoggable(Level.FINE); - - if (loggable) { - getLogger().log(Level.FINE, - "Authentication or authorization failed for this URI: " + response.getRequest().getResourceRef()); - } - - response.setStatus(Status.CLIENT_ERROR_FORBIDDEN); - } - - /** - * Returns the authentication realm. - * - * @return The authentication realm. - */ - public String getRealm() { - return this.realm; - } - - /** - * Returns the authentication challenge scheme. - * - * @return The authentication challenge scheme. - */ - public ChallengeScheme getScheme() { - return scheme; - } - - /** - * Returns the credentials verifier. - * - * @return The credentials verifier. - */ - public Verifier getVerifier() { - return verifier; - } - - /** - * Indicates if a new challenge should be sent when invalid credentials are - * received (true by default to conform to HTTP recommendations). If set to - * false, upon reception of invalid credentials, the method - * {@link #forbid(Response)} will be called. - * - * @return True if invalid credentials result in a new challenge. - */ - public boolean isRechallenging() { - return this.rechallenging; - } - - /** - * Sets the authentication realm. - * - * @param realm The authentication realm. - */ - public void setRealm(String realm) { - this.realm = realm; - } - - /** - * Indicates if a new challenge should be sent when invalid credentials are - * received. - * - * @param rechallenging True if invalid credentials result in a new challenge. - * @see #isRechallenging() - */ - public void setRechallenging(boolean rechallenging) { - this.rechallenging = rechallenging; - } - - /** - * Sets the credentials verifier. - * - * @param verifier The credentials verifier. - */ - public void setVerifier(Verifier verifier) { - this.verifier = verifier; - } - + /** The authentication realm. */ + private volatile String realm; + + /** + * Indicates if a new challenge should be sent when invalid credentials are received (true by + * default to conform to HTTP recommendations). + */ + private volatile boolean rechallenging; + + /** The expected challenge scheme. */ + private final ChallengeScheme scheme; + + /** The credentials verifier. */ + private volatile Verifier verifier; + + /** + * Constructor using the context's default verifier. + * + * @param context The context. + * @param optional Indicates if the authentication success is optional. + * @param challengeScheme The authentication scheme to use. + * @param realm The authentication realm. + * @see #ChallengeAuthenticator(Context, boolean, ChallengeScheme, String, Verifier) + */ + public ChallengeAuthenticator( + Context context, boolean optional, ChallengeScheme challengeScheme, String realm) { + this( + context, + optional, + challengeScheme, + realm, + (context != null) ? context.getDefaultVerifier() : null); + } + + /** + * Constructor. + * + * @param context The context. + * @param optional Indicates if the authentication success is optional. + * @param challengeScheme The authentication scheme to use. + * @param realm The authentication realm. + * @param verifier The credentials' verifier. + */ + public ChallengeAuthenticator( + Context context, + boolean optional, + ChallengeScheme challengeScheme, + String realm, + Verifier verifier) { + super(context, optional); + this.realm = realm; + this.rechallenging = true; + this.scheme = challengeScheme; + this.verifier = verifier; + } + + /** + * Constructor setting the optional property to false. + * + * @param context The context. + * @param challengeScheme The authentication scheme to use. + * @param realm The authentication realm. + * @see #ChallengeAuthenticator(Context, boolean, ChallengeScheme, String, Verifier) + */ + public ChallengeAuthenticator(Context context, ChallengeScheme challengeScheme, String realm) { + this(context, false, challengeScheme, realm); + } + + /** + * Authenticates the call, relying on the verifier to check the credentials provided (in general + * an identifier and secret couple). If the credentials are valid, the next Restlet attached is + * invoked.
+ *
+ * If the credentials are missing, then {@link #challenge(Response, boolean)} is invoked.
+ *
+ * If the credentials are invalid and if the "rechallenge" property is true then {@link + * #challenge(Response, boolean)} is invoked. Otherwise, {@link #forbid(Response)} is invoked. + *
+ *
+ * If the credentials are stale, then {@link #challenge(Response, boolean)} is invoked with the + * "stale" parameter to true.
+ *
+ * At the end of the process, the {@link ClientInfo#setAuthenticated(boolean)} method is + * invoked. + */ + @Override + protected boolean authenticate(Request request, Response response) { + boolean result = false; + boolean loggable = request.isLoggable() && getLogger().isLoggable(Level.FINE); + + if (getVerifier() != null) { + switch (getVerifier().verify(request, response)) { + case Verifier.RESULT_VALID: + // Valid credentials provided + result = true; + + if (loggable) { + ChallengeResponse challengeResponse = request.getChallengeResponse(); + + if (challengeResponse != null) { + getLogger() + .fine( + "Authentication succeeded. Valid credentials provided for identifier: " + + request.getChallengeResponse().getIdentifier() + + "."); + } else { + getLogger() + .fine("Authentication succeeded. Valid credentials provided."); + } + } + break; + case Verifier.RESULT_MISSING: + // No credentials provided + if (loggable) { + getLogger().fine("Authentication failed. No credentials provided."); + } + + if (!isOptional()) { + challenge(response, false); + } + break; + case Verifier.RESULT_INVALID: + // Invalid credentials provided + if (loggable) { + getLogger().fine("Authentication failed. Invalid credentials provided."); + } + + if (!isOptional()) { + if (isRechallenging()) { + challenge(response, false); + } else { + forbid(response); + } + } + break; + case Verifier.RESULT_STALE: + if (loggable) { + getLogger().fine("Authentication failed. Stale credentials provided."); + } + + if (!isOptional()) { + challenge(response, true); + } + break; + case Verifier.RESULT_UNKNOWN: + if (loggable) { + getLogger().fine("Authentication failed. Identifier is unknown."); + } + + if (!isOptional()) { + if (isRechallenging()) { + challenge(response, false); + } else { + forbid(response); + } + } + break; + default: + } + } else { + getLogger().warning("Authentication failed. No verifier provided."); + response.setStatus( + Status.SERVER_ERROR_INTERNAL, "Authentication failed. No verifier provided."); + } + + return result; + } + + /** + * Challenges the client by adding a challenge request to the response and by setting the status + * to {@link Status#CLIENT_ERROR_UNAUTHORIZED}. + * + * @param response The response to update. + * @param stale Indicates if the new challenge is due to a stale response. + */ + public void challenge(Response response, boolean stale) { + boolean loggable = response.getRequest().isLoggable() && getLogger().isLoggable(Level.FINE); + + if (loggable) { + getLogger().log(Level.FINE, "An authentication challenge was requested."); + } + + response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED); + response.getChallengeRequests().add(createChallengeRequest(stale)); + } + + /** + * Creates a new challenge request. + * + * @param stale Indicates if the new challenge is due to a stale response. + * @return A new challenge request. + */ + protected ChallengeRequest createChallengeRequest(boolean stale) { + return new ChallengeRequest(getScheme(), getRealm()); + } + + /** + * Rejects the call due to a failed authentication or authorization. This can be overridden to + * change the default behavior, for example, to display an error page. By default, if + * authentication is required, the challenge method is invoked, otherwise the call status is set + * to CLIENT_ERROR_FORBIDDEN. + * + * @param response The reject response. + */ + public void forbid(Response response) { + boolean loggable = response.getRequest().isLoggable() && getLogger().isLoggable(Level.FINE); + + if (loggable) { + getLogger() + .log( + Level.FINE, + "Authentication or authorization failed for this URI: {0}", + response.getRequest().getResourceRef()); + } + + response.setStatus(Status.CLIENT_ERROR_FORBIDDEN); + } + + /** + * Returns the authentication realm. + * + * @return The authentication realm. + */ + public String getRealm() { + return this.realm; + } + + /** + * Returns the authentication challenge scheme. + * + * @return The authentication challenge scheme. + */ + public ChallengeScheme getScheme() { + return scheme; + } + + /** + * Returns the credentials verifier. + * + * @return The credentials verifier. + */ + public Verifier getVerifier() { + return verifier; + } + + /** + * Indicates if a new challenge should be sent when invalid credentials are received (true by + * default to conform to HTTP recommendations). If set to false, upon reception of invalid + * credentials, the method {@link #forbid(Response)} will be called. + * + * @return True if invalid credentials result in a new challenge. + */ + public boolean isRechallenging() { + return this.rechallenging; + } + + /** + * Sets the authentication realm. + * + * @param realm The authentication realm. + */ + public void setRealm(String realm) { + this.realm = realm; + } + + /** + * Indicates if a new challenge should be sent when invalid credentials are received. + * + * @param rechallenging True if invalid credentials result in a new challenge. + * @see #isRechallenging() + */ + public void setRechallenging(boolean rechallenging) { + this.rechallenging = rechallenging; + } + + /** + * Sets the credentials' verifier. + * + * @param verifier The credentials' verifier. + */ + public void setVerifier(Verifier verifier) { + this.verifier = verifier; + } } diff --git a/org.restlet/src/main/java/org/restlet/security/ConfidentialAuthorizer.java b/org.restlet/src/main/java/org/restlet/security/ConfidentialAuthorizer.java index c24767f7b1..0305605de9 100644 --- a/org.restlet/src/main/java/org/restlet/security/ConfidentialAuthorizer.java +++ b/org.restlet/src/main/java/org/restlet/security/ConfidentialAuthorizer.java @@ -1,35 +1,33 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import org.restlet.Request; import org.restlet.Response; /** - * Authorizer allowing only confidential calls. Confidential calls typically - * come through HTTPS server connectors. - * + * Authorizer allowing only confidential calls. Confidential calls typically come through HTTPS + * server connectors. + * * @author Jerome Louvel */ public class ConfidentialAuthorizer extends Authorizer { - /** - * Authorizes the request only if its method is one of the authorized methods. - * - * @param request The request sent. - * @param response The response to update. - * @return True if the authorization succeeded. - */ - @Override - public boolean authorize(Request request, Response response) { - return request.isConfidential(); - } - + /** + * Authorizes the request only if its method is one of the authorized methods. + * + * @param request The request sent. + * @param response The response to update. + * @return True if the authorization succeeded. + */ + @Override + public boolean authorize(Request request, Response response) { + return request.isConfidential(); + } } diff --git a/org.restlet/src/main/java/org/restlet/security/Enroler.java b/org.restlet/src/main/java/org/restlet/security/Enroler.java index 43ad1d9a04..bcb9159175 100644 --- a/org.restlet/src/main/java/org/restlet/security/Enroler.java +++ b/org.restlet/src/main/java/org/restlet/security/Enroler.java @@ -1,23 +1,21 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; -import org.restlet.data.ClientInfo; - import java.security.Principal; +import org.restlet.data.ClientInfo; /** - * Updates an authenticated client user with assigned roles. Typically, it is - * invoked by an {@link Authenticator} after successful authentication to add - * {@link Role} instances based on available {@link User}. - * + * Updates an authenticated client user with assigned roles. Typically, it is invoked by an {@link + * Authenticator} after successful authentication to add {@link Role} instances based on available + * {@link User}. + * * @see Authenticator#getEnroler() * @see Authenticator#setEnroler(Enroler) * @see ClientInfo#getUser() @@ -26,15 +24,13 @@ */ public interface Enroler { - /** - * Attempts to update an authenticated client, with a {@link User} properly - * defined, by adding the {@link Role} that are assigned to this user. Note that - * principals could also be added to the {@link ClientInfo} if necessary. The - * addition could also potentially be based on the presence of - * {@link Principal}. - * - * @param clientInfo The clientInfo to update. - */ - void enrole(ClientInfo clientInfo); - + /** + * Attempts to update an authenticated client, with a {@link User} properly defined, by adding + * the {@link Role} that are assigned to this user. Note that principals could also be added to + * the {@link ClientInfo} if necessary. The addition could also potentially be based on the + * presence of {@link Principal}. + * + * @param clientInfo The clientInfo to update. + */ + void enrole(ClientInfo clientInfo); } diff --git a/org.restlet/src/main/java/org/restlet/security/Group.java b/org.restlet/src/main/java/org/restlet/security/Group.java index 68c15676b5..a8c640e838 100644 --- a/org.restlet/src/main/java/org/restlet/security/Group.java +++ b/org.restlet/src/main/java/org/restlet/security/Group.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import java.util.List; @@ -14,171 +13,166 @@ /** * Group that contains member groups and users. - * + * * @author Jerome Louvel */ public class Group { - /** The description. */ - private volatile String description; - - /** - * Indicates if the roles of the parent group should be inherited. Those roles - * indirectly cover the granted or denied permissions. - */ - private volatile boolean inheritingRoles; - - /** The modifiable list of child groups. */ - private final List memberGroups; - - /** The modifiable list of members user references. */ - private final List memberUsers; - - /** The display name. */ - private volatile String name; - - /** - * Default constructor. Note that roles are inherited by default. - */ - public Group() { - this(null, null); - } - - /** - * Constructor. Note that roles are inherited by default. - * - * @param name The display name. - * @param description The description. - */ - public Group(String name, String description) { - this(name, description, true); - } - - /** - * Constructor. - * - * @param name The display name. - * @param description The description. - * @param inheritingRoles Indicates if the roles of the parent group should be - * inherited. - */ - public Group(String name, String description, boolean inheritingRoles) { - this.name = name; - this.description = description; - this.inheritingRoles = inheritingRoles; - this.memberGroups = new CopyOnWriteArrayList(); - this.memberUsers = new CopyOnWriteArrayList(); - } - - /** - * Returns the description. - * - * @return The description - */ - public String getDescription() { - return this.description; - } - - /** - * Returns the modifiable list of member groups. - * - * @return The modifiable list of member groups. - */ - public List getMemberGroups() { - return memberGroups; - } - - public List getMemberUsers() { - return memberUsers; - } - - /** - * Returns the display name. - * - * @return The display name. - */ - public String getName() { - return this.name; - } - - /** - * Indicates if the roles of the parent group should be inherited. Those roles - * indirectly cover the granted or denied permissions. - * - * @return True if the roles of the parent group should be inherited. - */ - public boolean isInheritingRoles() { - return inheritingRoles; - } - - /** - * Sets the description. - * - * @param description The description. - */ - public void setDescription(String description) { - this.description = description; - } - - /** - * Indicates if the roles of the parent group should be inherited. Those roles - * indirectly cover the granted or denied permissions. - * - * @param inheritingRoles True if the roles of the parent group should be - * inherited. - */ - public void setInheritingRoles(boolean inheritingRoles) { - this.inheritingRoles = inheritingRoles; - } - - /** - * Sets the modifiable list of member groups. This method clears the current - * list and adds all entries in the parameter list. - * - * @param memberGroups A list of member groups. - */ - public void setMemberGroups(List memberGroups) { - synchronized (getMemberGroups()) { - if (memberGroups != getMemberGroups()) { - getMemberGroups().clear(); - - if (memberGroups != null) { - getMemberGroups().addAll(memberGroups); - } - } - } - } - - /** - * Sets the modifiable list of member user references. This method clears the - * current list and adds all entries in the parameter list. - * - * @param memberUsers A list of member user references. - */ - public void setMemberUsers(List memberUsers) { - synchronized (getMemberUsers()) { - if (memberUsers != getMemberUsers()) { - getMemberUsers().clear(); - - if (memberUsers != null) { - getMemberUsers().addAll(memberUsers); - } - } - } - } - - /** - * Sets the display name. - * - * @param name The display name. - */ - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return getName(); - } - + /** The description. */ + private volatile String description; + + /** + * Indicates if the roles of the parent group should be inherited. Those roles indirectly cover + * the granted or denied permissions. + */ + private volatile boolean inheritingRoles; + + /** The modifiable list of child groups. */ + private final List memberGroups; + + /** The modifiable list of members user references. */ + private final List memberUsers; + + /** The display name. */ + private volatile String name; + + /** Default constructor. Note that roles are inherited by default. */ + public Group() { + this(null, null); + } + + /** + * Constructor. Note that roles are inherited by default. + * + * @param name The display name. + * @param description The description. + */ + public Group(String name, String description) { + this(name, description, true); + } + + /** + * Constructor. + * + * @param name The display name. + * @param description The description. + * @param inheritingRoles Indicates if the roles of the parent group should be inherited. + */ + public Group(String name, String description, boolean inheritingRoles) { + this.name = name; + this.description = description; + this.inheritingRoles = inheritingRoles; + this.memberGroups = new CopyOnWriteArrayList(); + this.memberUsers = new CopyOnWriteArrayList(); + } + + /** + * Returns the description. + * + * @return The description + */ + public String getDescription() { + return this.description; + } + + /** + * Returns the modifiable list of member groups. + * + * @return The modifiable list of member groups. + */ + public List getMemberGroups() { + return memberGroups; + } + + public List getMemberUsers() { + return memberUsers; + } + + /** + * Returns the display name. + * + * @return The display name. + */ + public String getName() { + return this.name; + } + + /** + * Indicates if the roles of the parent group should be inherited. Those roles indirectly cover + * the granted or denied permissions. + * + * @return True if the roles of the parent group should be inherited. + */ + public boolean isInheritingRoles() { + return inheritingRoles; + } + + /** + * Sets the description. + * + * @param description The description. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Indicates if the roles of the parent group should be inherited. Those roles indirectly cover + * the granted or denied permissions. + * + * @param inheritingRoles True if the roles of the parent group should be inherited. + */ + public void setInheritingRoles(boolean inheritingRoles) { + this.inheritingRoles = inheritingRoles; + } + + /** + * Sets the modifiable list of member groups. This method clears the current list and adds all + * entries in the parameter list. + * + * @param memberGroups A list of member groups. + */ + public void setMemberGroups(List memberGroups) { + synchronized (getMemberGroups()) { + if (memberGroups != getMemberGroups()) { + getMemberGroups().clear(); + + if (memberGroups != null) { + getMemberGroups().addAll(memberGroups); + } + } + } + } + + /** + * Sets the modifiable list of member user references. This method clears the current list and + * adds all entries in the parameter list. + * + * @param memberUsers A list of member user references. + */ + public void setMemberUsers(List memberUsers) { + synchronized (getMemberUsers()) { + if (memberUsers != getMemberUsers()) { + getMemberUsers().clear(); + + if (memberUsers != null) { + getMemberUsers().addAll(memberUsers); + } + } + } + } + + /** + * Sets the display name. + * + * @param name The display name. + */ + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return getName(); + } } diff --git a/org.restlet/src/main/java/org/restlet/security/LocalVerifier.java b/org.restlet/src/main/java/org/restlet/security/LocalVerifier.java index ec161b6de2..bc8ca8f693 100644 --- a/org.restlet/src/main/java/org/restlet/security/LocalVerifier.java +++ b/org.restlet/src/main/java/org/restlet/security/LocalVerifier.java @@ -1,34 +1,31 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; /** - * Verifier that can locally retrieve the secrets. This verifier assumes that - * the secret associated to an identifier can be retrieved, which isn't always - * possible or even desirable. - * + * Verifier that can locally retrieve the secrets. This verifier assumes that the secret associated + * to an identifier can be retrieved, which isn't always possible or even desirable. + * * @author Jerome Louvel */ public abstract class LocalVerifier extends SecretVerifier { - /** - * Returns the local secret associated to a given identifier. - * - * @param identifier The identifier to lookup. - * @return The secret associated to the identifier or null. - */ - public abstract char[] getLocalSecret(String identifier); - - @Override - public int verify(String identifier, char[] secret) { - return compare(secret, getLocalSecret(identifier)) ? RESULT_VALID : RESULT_INVALID; - } + /** + * Returns the local secret associated with a given identifier. + * + * @param identifier The identifier to lookup. + * @return The secret associated with the identifier or null. + */ + public abstract char[] getLocalSecret(String identifier); + @Override + public int verify(String identifier, char[] secret) { + return compare(secret, getLocalSecret(identifier)) ? RESULT_VALID : RESULT_INVALID; + } } diff --git a/org.restlet/src/main/java/org/restlet/security/MapVerifier.java b/org.restlet/src/main/java/org/restlet/security/MapVerifier.java index 121bb2eaaa..8a512c2adb 100644 --- a/org.restlet/src/main/java/org/restlet/security/MapVerifier.java +++ b/org.restlet/src/main/java/org/restlet/security/MapVerifier.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import java.util.Map; @@ -14,62 +13,59 @@ import java.util.concurrent.ConcurrentMap; /** - * Verifier that stores its local secrets in a map indexed by the identifier. - * Note that this verifier isn't very secure by itself. - * + * Verifier that stores its local secrets in a map indexed by the identifier. Note that this + * verifier isn't very secure by itself. + * * @author Jerome Louvel */ public class MapVerifier extends LocalVerifier { - /** The map of local secrets. */ - private final ConcurrentMap localSecrets; - - /** - * Constructor. - */ - public MapVerifier() { - this(new ConcurrentHashMap()); - } + /** The map of local secrets. */ + private final ConcurrentMap localSecrets; - /** - * Constructor. - * - * @param localSecrets The map of local secrets. - */ - public MapVerifier(ConcurrentMap localSecrets) { - this.localSecrets = localSecrets; - } + /** Constructor. */ + public MapVerifier() { + this(new ConcurrentHashMap<>()); + } - @Override - public char[] getLocalSecret(String identifier) { - return (identifier == null) ? null : getLocalSecrets().get(identifier); - } + /** + * Constructor. + * + * @param localSecrets The map of local secrets. + */ + public MapVerifier(ConcurrentMap localSecrets) { + this.localSecrets = localSecrets; + } - /** - * Returns the map of local secrets. - * - * @return The map of local secrets. - */ - public ConcurrentMap getLocalSecrets() { - return localSecrets; - } + @Override + public char[] getLocalSecret(String identifier) { + return (identifier == null) ? null : getLocalSecrets().get(identifier); + } - /** - * Sets the modifiable map of local secrets. This method clears the current map - * and puts all entries in the parameter map. - * - * @param localSecrets A map of local secrets. - */ - public void setLocalSecrets(Map localSecrets) { - synchronized (getLocalSecrets()) { - if (localSecrets != getLocalSecrets()) { - getLocalSecrets().clear(); + /** + * Returns the map of local secrets. + * + * @return The map of local secrets. + */ + public ConcurrentMap getLocalSecrets() { + return localSecrets; + } - if (localSecrets != null) { - getLocalSecrets().putAll(localSecrets); - } - } - } - } + /** + * Sets the modifiable map of local secrets. This method clears the current map and puts all + * entries in the parameter map. + * + * @param localSecrets A map of local secrets. + */ + public void setLocalSecrets(Map localSecrets) { + synchronized (getLocalSecrets()) { + if (localSecrets != getLocalSecrets()) { + getLocalSecrets().clear(); + if (localSecrets != null) { + getLocalSecrets().putAll(localSecrets); + } + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/security/MemoryRealm.java b/org.restlet/src/main/java/org/restlet/security/MemoryRealm.java index 10e3ef6b57..7f642884b0 100644 --- a/org.restlet/src/main/java/org/restlet/security/MemoryRealm.java +++ b/org.restlet/src/main/java/org/restlet/security/MemoryRealm.java @@ -1,513 +1,509 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; -import org.restlet.Application; -import org.restlet.Request; -import org.restlet.Response; -import org.restlet.data.ClientInfo; -import org.restlet.engine.security.RoleMapping; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import org.restlet.Application; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ClientInfo; +import org.restlet.engine.security.RoleMapping; /** - * Security realm based on a memory model. The model is composed of root groups, - * users and mapping to associated roles. - * + * Security realm based on a memory model. The model is composed of root groups, users and mapping + * to associated roles. + * * @author Jerome Louvel */ public class MemoryRealm extends Realm { - /** - * Enroler based on the default security model. - */ - private class DefaultEnroler implements Enroler { - - public void enrole(ClientInfo clientInfo) { - User user = findUser(clientInfo.getUser().getIdentifier()); - - if (user != null) { - // Find all the inherited groups of this user - Set userGroups = findGroups(user); - - // Add roles specific to this user - Set userRoles = findRoles(user); - - for (Role role : userRoles) { - clientInfo.getRoles().add(role); - } - - // Add roles common to group members - Set groupRoles = findRoles(userGroups); - - for (Role role : groupRoles) { - clientInfo.getRoles().add(role); - } - } - } - } - - /** - * Verifier based on the default security model. It looks up users in the mapped - * organizations. - */ - private class DefaultVerifier extends SecretVerifier { - - @Override - protected User createUser(String identifier, Request request, Response response) { - User result = new User(identifier); - - // Find the reference user - User user = findUser(identifier); - - if (user != null) { - // Copy the properties of the reference user - result.setEmail(user.getEmail()); - result.setFirstName(user.getFirstName()); - result.setLastName(user.getLastName()); - } - - return result; - } - - @Override - public int verify(String identifier, char[] secret) { - char[] actualSecret = null; - User user = findUser(identifier); - - if (user != null) { - actualSecret = user.getSecret(); - } - - return compare(secret, actualSecret) ? RESULT_VALID : RESULT_INVALID; - } - } - - /** The modifiable list of role mappings. */ - private final List roleMappings; - - /** The modifiable list of root groups. */ - private final List rootGroups; - - /** The modifiable list of users. */ - private final List users; - - /** - * Constructor. - */ - public MemoryRealm() { - setVerifier(new DefaultVerifier()); - setEnroler(new DefaultEnroler()); - this.rootGroups = new CopyOnWriteArrayList(); - this.roleMappings = new CopyOnWriteArrayList(); - this.users = new CopyOnWriteArrayList(); - } - - /** - * Recursively adds groups where a given user is a member. - * - * @param user The member user. - * @param userGroups The set of user groups to update. - * @param currentGroup The current group to inspect. - * @param stack The stack of ancestor groups. - * @param inheritOnly Indicates if only the ancestors groups that have their - * "inheritRoles" property enabled should be added. - */ - private void addGroups(User user, Set userGroups, Group currentGroup, List stack, - boolean inheritOnly) { - if ((currentGroup != null) && !stack.contains(currentGroup)) { - stack.add(currentGroup); - - if (currentGroup.getMemberUsers().contains(user)) { - userGroups.add(currentGroup); - - // Add the ancestor groups as well - boolean inherit = !inheritOnly || currentGroup.isInheritingRoles(); - Group group; - - for (int i = stack.size() - 2; inherit && (i >= 0); i--) { - group = stack.get(i); - userGroups.add(group); - inherit = !inheritOnly || group.isInheritingRoles(); - } - } - - for (Group group : currentGroup.getMemberGroups()) { - addGroups(user, userGroups, group, stack, inheritOnly); - } - } - } - - /** - * Finds the set of groups where a given user is a member. Note that inheritable - * ancestors groups are also returned. - * - * @param user The member user. - * @return The set of groups. - */ - public Set findGroups(User user) { - return findGroups(user, true); - } - - /** - * Finds the set of groups where a given user is a member. - * - * @param user The member user. - * @param inheritOnly Indicates if only the ancestors groups that have their - * "inheritRoles" property enabled should be added. - * @return The set of groups. - */ - public Set findGroups(User user, boolean inheritOnly) { - Set result = new HashSet(); - List stack; - - // Recursively find user groups - for (Group group : getRootGroups()) { - stack = new ArrayList(); - addGroups(user, result, group, stack, inheritOnly); - } - - return result; - } - - /** - * Finds the roles mapped to a given user group. - * - * @param application The parent application. Can't be null. - * @param userGroup The user group. - * @return The roles found. - * @throws IllegalArgumentException If application is null. - */ - public Set findRoles(Application application, Group userGroup) { - if (application == null) { - throw new IllegalArgumentException("The application argument can't be null"); - } - - Set result = new HashSet(); - Object source; - - for (RoleMapping mapping : getRoleMappings()) { - source = mapping.getSource(); - - if ((userGroup != null) && userGroup.equals(source)) { - if (mapping.getTarget().getApplication() == application) { - result.add(mapping.getTarget()); - } - } - } - - return result; - } - - /** - * Finds the roles mapped to given user groups. - * - * @param application The parent application. Can't be null. - * @param userGroups The user groups. - * @return The roles found. - * @throws IllegalArgumentException If application is null. - */ - public Set findRoles(Application application, Set userGroups) { - if (application == null) { - throw new IllegalArgumentException("The application argument can't be null"); - } - - Set result = new HashSet(); - Object source; - - for (RoleMapping mapping : getRoleMappings()) { - source = mapping.getSource(); - - if ((userGroups != null) && userGroups.contains(source)) { - if (mapping.getTarget().getApplication() == application) { - result.add(mapping.getTarget()); - } - } - } - - return result; - } - - /** - * Finds the roles mapped to a given user, for a specific application. - * - * @param application The parent application. Can't be null. - * @param user The user. - * @return The roles found. - * @throws IllegalArgumentException If application is null. - */ - public Set findRoles(Application application, User user) { - if (application == null) { - throw new IllegalArgumentException("The application argument can't be null"); - } - - Set result = new HashSet(); - Object source; - - for (RoleMapping mapping : getRoleMappings()) { - source = mapping.getSource(); - - if ((user != null) && user.equals(source)) { - if (mapping.getTarget().getApplication() == application) { - result.add(mapping.getTarget()); - } - } - } - - return result; - } - - /** - * Finds the roles mapped to given user group. - * - * @param userGroup The user group. - * @return The roles found. - */ - public Set findRoles(Group userGroup) { - Set result = new HashSet(); - Object source; - - for (RoleMapping mapping : getRoleMappings()) { - source = mapping.getSource(); - - if ((userGroup != null) && userGroup.equals(source)) { - result.add(mapping.getTarget()); - } - } - - return result; - } - - /** - * Finds the roles mapped to given user groups. - * - * @param userGroups The user groups. - * @return The roles found. - */ - public Set findRoles(Set userGroups) { - Set result = new HashSet(); - Object source; - - for (RoleMapping mapping : getRoleMappings()) { - source = mapping.getSource(); - - if ((userGroups != null) && userGroups.contains(source)) { - result.add(mapping.getTarget()); - } - } - - return result; - } - - /** - * Finds the roles mapped to a given user. - * - * @param user The user. - * @return The roles found. - */ - public Set findRoles(User user) { - Set result = new HashSet(); - Object source; - - for (RoleMapping mapping : getRoleMappings()) { - source = mapping.getSource(); - - if ((user != null) && user.equals(source)) { - result.add(mapping.getTarget()); - } - } - - return result; - } - - /** - * Finds a user in the organization based on its identifier. - * - * @param userIdentifier The identifier to match. - * @return The matched user or null. - */ - public User findUser(String userIdentifier) { - User result = null; - User user; - - for (int i = 0; (result == null) && (i < getUsers().size()); i++) { - user = getUsers().get(i); - - if (user.getIdentifier().equals(userIdentifier)) { - result = user; - } - } - - return result; - } - - /** - * Returns the modifiable list of role mappings. - * - * @return The modifiable list of role mappings. - */ - private List getRoleMappings() { - return roleMappings; - } - - /** - * Returns the modifiable list of root groups. - * - * @return The modifiable list of root groups. - */ - public List getRootGroups() { - return rootGroups; - } - - /** - * Returns the modifiable list of users. - * - * @return The modifiable list of users. - */ - public List getUsers() { - return users; - } - - /** - * Maps a group defined in a component to a role defined in the application. - * - * @param group The source group. - * @param role The target role. - */ - public void map(Group group, Role role) { - getRoleMappings().add(new RoleMapping(group, role)); - } - - /** - * Maps a user defined in a component to a role defined in the application. - * - * @param user The source user. - * @param application The parent application. Can't be null. - * @param roleName The target role name. - * @throws IllegalArgumentException If application is null. - */ - public void map(User user, Application application, String roleName) { - map(user, Role.get(application, roleName, null)); - } - - /** - * Maps a user defined in a component to a role defined in the application. - * - * @param user The source user. - * @param role The target role. - */ - public void map(User user, Role role) { - getRoleMappings().add(new RoleMapping(user, role)); - } - - /** - * Sets the modifiable list of root groups. This method clears the current list - * and adds all entries in the parameter list. - * - * @param rootGroups A list of root groups. - */ - public void setRootGroups(List rootGroups) { - synchronized (getRootGroups()) { - if (rootGroups != getRootGroups()) { - getRootGroups().clear(); - - if (rootGroups != null) { - getRootGroups().addAll(rootGroups); - } - } - } - } - - /** - * Sets the modifiable list of users. This method clears the current list and - * adds all entries in the parameter list. - * - * @param users A list of users. - */ - public void setUsers(List users) { - synchronized (getUsers()) { - if (users != getUsers()) { - getUsers().clear(); - - if (users != null) { - getUsers().addAll(users); - } - } - } - } - - /** - * Unmaps a group defined in a component from a role defined in the application. - * - * @param group The source group. - * @param application The parent application. Can't be null. - * @param roleName The target role name. - * @throws IllegalArgumentException If application is null. - */ - public void unmap(Group group, Application application, String roleName) { - unmap(group, Role.get(application, roleName, null)); - } - - /** - * Unmaps a group defined in a component from a role defined in the application. - * - * @param group The source group. - * @param role The target role. - */ - public void unmap(Group group, Role role) { - unmap((Object) group, role); - } - - /** - * Unmaps an element (user, group or organization) defined in a component from a - * role defined in the application. - * - * @param source The source group. - * @param role The target role. - */ - private void unmap(Object source, Role role) { - RoleMapping mapping; - - for (int i = getRoleMappings().size() - 1; i >= 0; i--) { - mapping = getRoleMappings().get(i); - - if (mapping.getSource().equals(source) && mapping.getTarget().equals(role)) { - getRoleMappings().remove(i); - } - } - } - - /** - * Unmaps a user defined in a component from a role defined in the application. - * - * @param user The source user. - * @param application The parent application. Can't be null. - * @param roleName The target role name. - * @throws IllegalArgumentException If application is null. - */ - public void unmap(User user, Application application, String roleName) { - unmap(user, Role.get(application, roleName, null)); - } - - /** - * Unmaps a user defined in a component from a role defined in the application. - * - * @param user The source user. - * @param role The target role. - */ - public void unmap(User user, Role role) { - unmap((Object) user, role); - } - + /** Enroler based on the default security model. */ + private class DefaultEnroler implements Enroler { + + public void enrole(ClientInfo clientInfo) { + User user = findUser(clientInfo.getUser().getIdentifier()); + + if (user != null) { + // Find all the inherited groups of this user + Set userGroups = findGroups(user); + + // Add roles specific to this user + Set userRoles = findRoles(user); + + for (Role role : userRoles) { + clientInfo.getRoles().add(role); + } + + // Add roles common to group members + Set groupRoles = findRoles(userGroups); + + for (Role role : groupRoles) { + clientInfo.getRoles().add(role); + } + } + } + } + + /** + * Verifier based on the default security model. It looks up users in the mapped organizations. + */ + private class DefaultVerifier extends SecretVerifier { + + @Override + protected User createUser(String identifier, Request request, Response response) { + User result = new User(identifier); + + // Find the reference user + User user = findUser(identifier); + + if (user != null) { + // Copy the properties of the reference user + result.setEmail(user.getEmail()); + result.setFirstName(user.getFirstName()); + result.setLastName(user.getLastName()); + } + + return result; + } + + @Override + public int verify(String identifier, char[] secret) { + char[] actualSecret = null; + User user = findUser(identifier); + + if (user != null) { + actualSecret = user.getSecret(); + } + + return compare(secret, actualSecret) ? RESULT_VALID : RESULT_INVALID; + } + } + + /** The modifiable list of role mappings. */ + private final List roleMappings; + + /** The modifiable list of root groups. */ + private final List rootGroups; + + /** The modifiable list of users. */ + private final List users; + + /** Constructor. */ + public MemoryRealm() { + setVerifier(new DefaultVerifier()); + setEnroler(new DefaultEnroler()); + this.rootGroups = new CopyOnWriteArrayList<>(); + this.roleMappings = new CopyOnWriteArrayList<>(); + this.users = new CopyOnWriteArrayList<>(); + } + + /** + * Recursively adds groups where a given user is a member. + * + * @param user The member user. + * @param userGroups The set of user groups to update. + * @param currentGroup The current group to inspect. + * @param stack The stack of ancestor groups. + * @param inheritOnly Indicates if only the ancestors groups that have their "inheritRoles" + * property enabled should be added. + */ + private void addGroups( + User user, + Set userGroups, + Group currentGroup, + List stack, + boolean inheritOnly) { + if ((currentGroup != null) && !stack.contains(currentGroup)) { + stack.add(currentGroup); + + if (currentGroup.getMemberUsers().contains(user)) { + userGroups.add(currentGroup); + + // Add the ancestor groups as well + boolean inherit = !inheritOnly || currentGroup.isInheritingRoles(); + Group group; + + for (int i = stack.size() - 2; inherit && (i >= 0); i--) { + group = stack.get(i); + userGroups.add(group); + inherit = !inheritOnly || group.isInheritingRoles(); + } + } + + for (Group group : currentGroup.getMemberGroups()) { + addGroups(user, userGroups, group, stack, inheritOnly); + } + } + } + + /** + * Finds the set of groups where a given user is a member. Note that inheritable ancestors + * groups are also returned. + * + * @param user The member user. + * @return The set of groups. + */ + public Set findGroups(User user) { + return findGroups(user, true); + } + + /** + * Finds the set of groups where a given user is a member. + * + * @param user The member user. + * @param inheritOnly Indicates if only the ancestors groups that have their "inheritRoles" + * property enabled should be added. + * @return The set of groups. + */ + public Set findGroups(User user, boolean inheritOnly) { + Set result = new HashSet<>(); + List stack; + + // Recursively find user groups + for (Group group : getRootGroups()) { + stack = new ArrayList<>(); + addGroups(user, result, group, stack, inheritOnly); + } + + return result; + } + + /** + * Finds the roles mapped to a given user group. + * + * @param application The parent application. Can't be null. + * @param userGroup The user group. + * @return The roles found. + * @throws IllegalArgumentException If the application is null. + */ + public Set findRoles(Application application, Group userGroup) { + if (application == null) { + throw new IllegalArgumentException("The application argument can't be null"); + } + + Set result = new HashSet<>(); + Object source; + + for (RoleMapping mapping : getRoleMappings()) { + source = mapping.getSource(); + + if ((userGroup != null) && userGroup.equals(source)) { + if (mapping.getTarget().getApplication() == application) { + result.add(mapping.getTarget()); + } + } + } + + return result; + } + + /** + * Finds the roles mapped to given user groups. + * + * @param application The parent application. Can't be null. + * @param userGroups The user groups. + * @return The roles found. + * @throws IllegalArgumentException If the application is null. + */ + public Set findRoles(Application application, Set userGroups) { + if (application == null) { + throw new IllegalArgumentException("The application argument can't be null"); + } + + Set result = new HashSet<>(); + Object source; + + for (RoleMapping mapping : getRoleMappings()) { + source = mapping.getSource(); + + if ((userGroups != null) + && userGroups.contains(source) + && mapping.getTarget().getApplication() == application) { + result.add(mapping.getTarget()); + } + } + + return result; + } + + /** + * Finds the roles mapped to a given user, for a specific application. + * + * @param application The parent application. Can't be null. + * @param user The user. + * @return The roles found. + * @throws IllegalArgumentException If application is null. + */ + public Set findRoles(Application application, User user) { + if (application == null) { + throw new IllegalArgumentException("The application argument can't be null"); + } + + Set result = new HashSet<>(); + Object source; + + for (RoleMapping mapping : getRoleMappings()) { + source = mapping.getSource(); + + if ((user != null) + && user.equals(source) + && mapping.getTarget().getApplication() == application) { + result.add(mapping.getTarget()); + } + } + + return result; + } + + /** + * Finds the roles mapped to a given user group. + * + * @param userGroup The user group. + * @return The roles found. + */ + public Set findRoles(Group userGroup) { + Set result = new HashSet<>(); + Object source; + + for (RoleMapping mapping : getRoleMappings()) { + source = mapping.getSource(); + + if ((userGroup != null) && userGroup.equals(source)) { + result.add(mapping.getTarget()); + } + } + + return result; + } + + /** + * Finds the roles mapped to given user groups. + * + * @param userGroups The user groups. + * @return The roles found. + */ + public Set findRoles(Set userGroups) { + Set result = new HashSet<>(); + Object source; + + for (RoleMapping mapping : getRoleMappings()) { + source = mapping.getSource(); + + if ((userGroups != null) && userGroups.contains(source)) { + result.add(mapping.getTarget()); + } + } + + return result; + } + + /** + * Finds the roles mapped to a given user. + * + * @param user The user. + * @return The roles found. + */ + public Set findRoles(User user) { + Set result = new HashSet<>(); + Object source; + + for (RoleMapping mapping : getRoleMappings()) { + source = mapping.getSource(); + + if ((user != null) && user.equals(source)) { + result.add(mapping.getTarget()); + } + } + + return result; + } + + /** + * Finds a user in the organization based on its identifier. + * + * @param userIdentifier The identifier to match. + * @return The matched user or null. + */ + public User findUser(String userIdentifier) { + User result = null; + User user; + + for (int i = 0; (result == null) && (i < getUsers().size()); i++) { + user = getUsers().get(i); + + if (user.getIdentifier().equals(userIdentifier)) { + result = user; + } + } + + return result; + } + + /** + * Returns the modifiable list of role mappings. + * + * @return The modifiable list of role mappings. + */ + private List getRoleMappings() { + return roleMappings; + } + + /** + * Returns the modifiable list of root groups. + * + * @return The modifiable list of root groups. + */ + public List getRootGroups() { + return rootGroups; + } + + /** + * Returns the modifiable list of users. + * + * @return The modifiable list of users. + */ + public List getUsers() { + return users; + } + + /** + * Maps a group defined in a component to a role defined in the application. + * + * @param group The source group. + * @param role The target role. + */ + public void map(Group group, Role role) { + getRoleMappings().add(new RoleMapping(group, role)); + } + + /** + * Maps a user defined in a component to a role defined in the application. + * + * @param user The source user. + * @param application The parent application. Can't be null. + * @param roleName The target role name. + * @throws IllegalArgumentException If the application is null. + */ + public void map(User user, Application application, String roleName) { + map(user, Role.get(application, roleName, null)); + } + + /** + * Maps a user defined in a component to a role defined in the application. + * + * @param user The source user. + * @param role The target role. + */ + public void map(User user, Role role) { + getRoleMappings().add(new RoleMapping(user, role)); + } + + /** + * Sets the modifiable list of root groups. This method clears the current list and adds all + * entries in the parameter list. + * + * @param rootGroups A list of root groups. + */ + public void setRootGroups(List rootGroups) { + synchronized (getRootGroups()) { + if (rootGroups != getRootGroups()) { + getRootGroups().clear(); + + if (rootGroups != null) { + getRootGroups().addAll(rootGroups); + } + } + } + } + + /** + * Sets the modifiable list of users. This method clears the current list and adds all entries + * in the parameter list. + * + * @param users A list of users. + */ + public void setUsers(List users) { + synchronized (getUsers()) { + if (users != getUsers()) { + getUsers().clear(); + + if (users != null) { + getUsers().addAll(users); + } + } + } + } + + /** + * Unmaps a group defined in a component from a role defined in the application. + * + * @param group The source group. + * @param application The parent application. Can't be null. + * @param roleName The target role name. + * @throws IllegalArgumentException If the application is null. + */ + public void unmap(Group group, Application application, String roleName) { + unmap(group, Role.get(application, roleName, null)); + } + + /** + * Unmaps a group defined in a component from a role defined in the application. + * + * @param group The source group. + * @param role The target role. + */ + public void unmap(Group group, Role role) { + unmap((Object) group, role); + } + + /** + * Unmaps an element (user, group or organization) defined in a component from a role defined in + * the application. + * + * @param source The source group. + * @param role The target role. + */ + private void unmap(Object source, Role role) { + RoleMapping mapping; + + for (int i = getRoleMappings().size() - 1; i >= 0; i--) { + mapping = getRoleMappings().get(i); + + if (mapping.getSource().equals(source) && mapping.getTarget().equals(role)) { + getRoleMappings().remove(i); + } + } + } + + /** + * Unmaps a user defined in a component from a role defined in the application. + * + * @param user The source user. + * @param application The parent application. Can't be null. + * @param roleName The target role name. + * @throws IllegalArgumentException If the application is null. + */ + public void unmap(User user, Application application, String roleName) { + unmap(user, Role.get(application, roleName, null)); + } + + /** + * Unmaps a user defined in a component from a role defined in the application. + * + * @param user The source user. + * @param role The target role. + */ + public void unmap(User user, Role role) { + unmap((Object) user, role); + } } diff --git a/org.restlet/src/main/java/org/restlet/security/MethodAuthorizer.java b/org.restlet/src/main/java/org/restlet/security/MethodAuthorizer.java index b11028c77e..ffcf61d6c7 100644 --- a/org.restlet/src/main/java/org/restlet/security/MethodAuthorizer.java +++ b/org.restlet/src/main/java/org/restlet/security/MethodAuthorizer.java @@ -1,133 +1,127 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.Method; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - /** - * Authorizer based on authorized methods. Note that this authorizer makes the - * difference between authenticated and anonymous users. - * + * Authorizer based on authorized methods. Note that this authorizer makes the difference between + * authenticated and anonymous users. + * * @author Jerome Louvel */ public class MethodAuthorizer extends Authorizer { - /** The modifiable list of methods authorized for anonymous users. */ - private List anonymousMethods; - - /** The modifiable list of methods authorized for authenticated users. */ - private List authenticatedMethods; - - /** - * Default constructor. - */ - public MethodAuthorizer() { - this(null); - } - - /** - * Constructor. - * - * @param identifier The identifier unique within an application. - */ - public MethodAuthorizer(String identifier) { - super(identifier); - - this.anonymousMethods = new CopyOnWriteArrayList(); - this.authenticatedMethods = new CopyOnWriteArrayList(); - } - - /** - * Authorizes the request only if its method is one of the authorized methods. - * - * @param request The request sent. - * @param response The response to update. - * @return True if the authorization succeeded. - */ - @Override - public boolean authorize(Request request, Response response) { - boolean authorized = false; - - if (request.getClientInfo().isAuthenticated()) { - // Verify if the request method is one of the forbidden methods - for (Method authenticatedMethod : getAuthenticatedMethods()) { - authorized = authorized || request.getMethod().equals(authenticatedMethod); - } - } else { - // Verify if the request method is one of the authorized methods - for (Method authorizedMethod : getAnonymousMethods()) { - authorized = authorized || request.getMethod().equals(authorizedMethod); - } - } - - return authorized; - } - - /** - * Returns the modifiable list of methods authorized for anonymous users. - * - * @return The modifiable list of methods authorized for anonymous users. - */ - public List getAnonymousMethods() { - return anonymousMethods; - } - - /** - * Returns the modifiable list of methods authorized for authenticated users. - * - * @return The modifiable list of methods authorized for authenticated users. - */ - public List getAuthenticatedMethods() { - return authenticatedMethods; - } - - /** - * Sets the modifiable list of methods authorized for anonymous users. This - * method clears the current list and adds all entries in the parameter list. - * - * @param anonymousMethods A list of methods authorized for anonymous users. - */ - public void setAnonymousMethods(List anonymousMethods) { - synchronized (getAnonymousMethods()) { - if (anonymousMethods != getAnonymousMethods()) { - getAnonymousMethods().clear(); - - if (anonymousMethods != null) { - getAnonymousMethods().addAll(anonymousMethods); - } - } - } - } - - /** - * Sets the modifiable list of methods authorized for authenticated users. This - * method clears the current list and adds all entries in the parameter list. - * - * @param authenticatedMethods A list of methods authorized for authenticated - * users. - */ - public void setAuthenticatedMethods(List authenticatedMethods) { - synchronized (getAuthenticatedMethods()) { - if (authenticatedMethods != getAuthenticatedMethods()) { - getAuthenticatedMethods().clear(); - - if (authenticatedMethods != null) { - getAuthenticatedMethods().addAll(authenticatedMethods); - } - } - } - } - + /** The modifiable list of methods authorized for anonymous users. */ + private List anonymousMethods; + + /** The modifiable list of methods authorized for authenticated users. */ + private List authenticatedMethods; + + /** Default constructor. */ + public MethodAuthorizer() { + this(null); + } + + /** + * Constructor. + * + * @param identifier The identifier unique within an application. + */ + public MethodAuthorizer(String identifier) { + super(identifier); + + this.anonymousMethods = new CopyOnWriteArrayList<>(); + this.authenticatedMethods = new CopyOnWriteArrayList<>(); + } + + /** + * Authorizes the request only if its method is one of the authorized methods. + * + * @param request The request sent. + * @param response The response to update. + * @return True if the authorization succeeded. + */ + @Override + public boolean authorize(Request request, Response response) { + boolean authorized = false; + + if (request.getClientInfo().isAuthenticated()) { + // Verify if the request method is one of the forbidden methods + for (Method authenticatedMethod : getAuthenticatedMethods()) { + authorized = authorized || request.getMethod().equals(authenticatedMethod); + } + } else { + // Verify if the request method is one of the authorized methods + for (Method authorizedMethod : getAnonymousMethods()) { + authorized = authorized || request.getMethod().equals(authorizedMethod); + } + } + + return authorized; + } + + /** + * Returns the modifiable list of methods authorized for anonymous users. + * + * @return The modifiable list of methods authorized for anonymous users. + */ + public List getAnonymousMethods() { + return anonymousMethods; + } + + /** + * Returns the modifiable list of methods authorized for authenticated users. + * + * @return The modifiable list of methods authorized for authenticated users. + */ + public List getAuthenticatedMethods() { + return authenticatedMethods; + } + + /** + * Sets the modifiable list of methods authorized for anonymous users. This method clears the + * current list and adds all entries in the parameter list. + * + * @param anonymousMethods A list of methods authorized for anonymous users. + */ + public void setAnonymousMethods(List anonymousMethods) { + synchronized (getAnonymousMethods()) { + if (anonymousMethods != getAnonymousMethods()) { + getAnonymousMethods().clear(); + + if (anonymousMethods != null) { + getAnonymousMethods().addAll(anonymousMethods); + } + } + } + } + + /** + * Sets the modifiable list of methods authorized for authenticated users. This method clears + * the current list and adds all entries in the parameter list. + * + * @param authenticatedMethods A list of methods authorized for authenticated users. + */ + public void setAuthenticatedMethods(List authenticatedMethods) { + synchronized (getAuthenticatedMethods()) { + if (authenticatedMethods != getAuthenticatedMethods()) { + getAuthenticatedMethods().clear(); + + if (authenticatedMethods != null) { + getAuthenticatedMethods().addAll(authenticatedMethods); + } + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/security/Realm.java b/org.restlet/src/main/java/org/restlet/security/Realm.java index d51a71ed3d..8f2c54fe48 100644 --- a/org.restlet/src/main/java/org/restlet/security/Realm.java +++ b/org.restlet/src/main/java/org/restlet/security/Realm.java @@ -1,186 +1,174 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; +import java.util.concurrent.CopyOnWriteArrayList; import org.restlet.data.Parameter; import org.restlet.util.Series; -import java.util.concurrent.CopyOnWriteArrayList; - /** * Security realm capable of providing an enroler and a verifier. - * + * * @author Jerome Louvel */ public abstract class Realm { - /** The name. */ - private volatile String name; - - /** The modifiable series of parameters. */ - private final Series parameters; - - /** - * The enroler that can add the user roles based on user principals. - */ - private volatile Enroler enroler; - - /** - * The verifier that can check the validity of the user credentials associated - * to a request. - */ - private volatile Verifier verifier; - - /** Indicates if the realm was started. */ - private volatile boolean started; - - /** - * Constructor. - */ - public Realm() { - this(null, null); - } - - /** - * Constructor. - * - * @param verifier The verifier that can check the validity of the credentials - * associated to a request. - * - * @param enroler The enroler that can add the user roles based on user - * principals. - */ - public Realm(Verifier verifier, Enroler enroler) { - this.enroler = enroler; - this.verifier = verifier; - this.parameters = new Series(Parameter.class, new CopyOnWriteArrayList()); - this.started = false; - } - - /** - * Returns an enroler that can add the user roles based on user principals. - * - * @return An enroler. - */ - public Enroler getEnroler() { - return enroler; - } - - /** - * Returns the name. - * - * @return The name. - */ - public String getName() { - return name; - } - - /** - * Returns the modifiable series of parameters. A parameter is a pair composed - * of a name and a value and is typically used for configuration purpose, like - * Java properties. Note that multiple parameters with the same name can be - * declared and accessed. - * - * @return The modifiable series of parameters. - */ - public Series getParameters() { - return this.parameters; - } - - /** - * Returns a verifier that can check the validity of the credentials associated - * to a request. - * - * @return A verifier. - */ - public Verifier getVerifier() { - return this.verifier; - } - - /** - * Indicates if the realm is started. - * - * @return True if the realm is started. - */ - public boolean isStarted() { - return this.started; - } - - /** - * Indicates if the realm is stopped. - * - * @return True if the realm is stopped. - */ - public boolean isStopped() { - return !this.started; - } - - /** - * Sets an enroler that can add the user roles based on user principals. - * - * @param enroler An enroler. - */ - public void setEnroler(Enroler enroler) { - this.enroler = enroler; - } - - /** - * Sets the name. - * - * @param name The name. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Sets the modifiable series of parameters. This method clears the current - * series and adds all entries in the parameter series. - * - * @param parameters A series of parameters. - */ - public void setParameters(Series parameters) { - synchronized (getParameters()) { - if (parameters != getParameters()) { - getParameters().clear(); - - if (parameters != null) { - getParameters().addAll(parameters); - } - } - } - } - - /** - * Sets a verifier that can check the validity of the credentials associated to - * a request. - * - * @param verifier A local verifier. - */ - public void setVerifier(Verifier verifier) { - this.verifier = verifier; - } - - /** Starts the realm. */ - public synchronized void start() throws Exception { - this.started = true; - } - - /** Stops the realm. */ - public synchronized void stop() throws Exception { - this.started = false; - } - - @Override - public String toString() { - return getName(); - } - + /** The name. */ + private volatile String name; + + /** The modifiable series of parameters. */ + private final Series parameters; + + /** The enroler that can add the user roles based on user principals. */ + private volatile Enroler enroler; + + /** + * The verifier that can check the validity of the user credentials associated with a request. + */ + private volatile Verifier verifier; + + /** Indicates if the realm was started. */ + private volatile boolean started; + + /** Constructor. */ + protected Realm() { + this(null, null); + } + + /** + * Constructor. + * + * @param verifier The verifier that can check the validity of the credentials associated with a + * request. + * @param enroler The enroler that can add the user roles based on user principals. + */ + protected Realm(Verifier verifier, Enroler enroler) { + this.enroler = enroler; + this.verifier = verifier; + this.parameters = + new Series(Parameter.class, new CopyOnWriteArrayList()); + this.started = false; + } + + /** + * Returns an enroler that can add the user roles based on user principals. + * + * @return An enroler. + */ + public Enroler getEnroler() { + return enroler; + } + + /** + * Returns the name. + * + * @return The name. + */ + public String getName() { + return name; + } + + /** + * Returns the modifiable series of parameters. A parameter is a pair composed of a name and a + * value and is typically used for configuration purposes, like Java properties. Note that + * multiple parameters with the same name can be declared and accessed. + * + * @return The modifiable series of parameters. + */ + public Series getParameters() { + return this.parameters; + } + + /** + * Returns a verifier that can check the validity of the credentials associated with a request. + * + * @return A verifier. + */ + public Verifier getVerifier() { + return this.verifier; + } + + /** + * Indicates if the realm is started. + * + * @return True if the realm is started. + */ + public boolean isStarted() { + return this.started; + } + + /** + * Indicates if the realm is stopped. + * + * @return True if the realm is stopped. + */ + public boolean isStopped() { + return !this.started; + } + + /** + * Sets an enroler that can add the user roles based on user principals. + * + * @param enroler An enroler. + */ + public void setEnroler(Enroler enroler) { + this.enroler = enroler; + } + + /** + * Sets the name. + * + * @param name The name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the modifiable series of parameters. This method clears the current series and adds all + * entries in the parameter series. + * + * @param parameters A series of parameters. + */ + public void setParameters(Series parameters) { + synchronized (getParameters()) { + if (parameters != getParameters()) { + getParameters().clear(); + + if (parameters != null) { + getParameters().addAll(parameters); + } + } + } + } + + /** + * Sets a verifier that can check the validity of the credentials associated with a request. + * + * @param verifier A local verifier. + */ + public void setVerifier(Verifier verifier) { + this.verifier = verifier; + } + + /** Starts the realm. */ + public synchronized void start() throws Exception { + this.started = true; + } + + /** Stops the realm. */ + public synchronized void stop() throws Exception { + this.started = false; + } + + @Override + public String toString() { + return getName(); + } } diff --git a/org.restlet/src/main/java/org/restlet/security/Role.java b/org.restlet/src/main/java/org/restlet/security/Role.java index f4b71fa2aa..3e47f23133 100644 --- a/org.restlet/src/main/java/org/restlet/security/Role.java +++ b/org.restlet/src/main/java/org/restlet/security/Role.java @@ -1,209 +1,209 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; -import org.restlet.Application; -import org.restlet.engine.util.SystemUtils; - import java.security.Principal; import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; +import org.restlet.Application; +import org.restlet.engine.util.SystemUtils; /** - * Application specific role. Common examples are "administrator", "user", - * "anonymous", "supervisor". Note that for reusability purpose, it is - * recommended that those role don't reflect an actual organization, but more - * the functional requirements of your application. - * - * Two roles are considered equals if they belong to the same parent application - * and have the same name and child roles. The description isn't used for - * equality assessment. - * - * Since version 2.2, they don't need to be the same Java objects anymore. In - * order to prevent the multiplication of equivalent {@link Role} instances, you - * should try to call {@link Application#getRole(String)} method. - * + * Application-specific role. Common examples are "administrator", "user", "anonymous", + * "supervisor". Note that for reusability purposes, it is recommended that those roles don't + * reflect an actual organization, but more the functional requirements of your application. + * + *

Two roles are considered equals if they belong to the same parent application and have the + * same name and child roles. The description isn't used for equality assessment. + * + *

Since version 2.2, they don't need to be the same Java objects anymore. To prevent the + * multiplication of equivalent {@link Role} instances, you should try to call {@link + * Application#getRole(String)} method. + * * @author Jerome Louvel * @author Tim Peierls */ public class Role implements Principal { - /** - * Finds an existing role or creates a new one if needed. Note that a null - * description will be set if the role has to be created. - * - * @param application The parent application. - * @param name The role name to find or create. - * @return The role found or created. - */ - public static Role get(Application application, String name) { - return get(application, name, null); - } - - /** - * Finds an existing role or creates a new one if needed. - * - * @param application The parent application. - * @param name The role name to find or create. - * @param description The role description if one needs to be created. - * @return The role found or created. - */ - public static Role get(Application application, String name, String description) { - Role role = (application == null) ? null : application.getRole(name); - return (role == null) ? new Role(application, name, description) : role; - } - - /** The parent application. */ - private volatile Application application; - - /** The modifiable list of child roles. */ - private final List childRoles; - - /** The description. */ - private volatile String description; - - /** The name. */ - private volatile String name; - - /** - * Default constructor. Note that the parent application is retrieved using the - * {@link Application#getCurrent()} method if available or is null. - */ - public Role() { - this(Application.getCurrent(), null, null); - } - - /** - * Constructor. - * - * @param application The parent application or null. - * @param name The name. - */ - public Role(Application application, String name) { - this(application, name, null); - } - - /** - * Constructor. - * - * @param application The parent application or null. - * @param name The name. - * @param description The description. - */ - public Role(Application application, String name, String description) { - this.application = application; - this.name = name; - this.description = description; - this.childRoles = new CopyOnWriteArrayList(); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof Role)) - return false; - - Role that = (Role) o; - return Objects.equals(that.getApplication(), getApplication()) && Objects.equals(that.getName(), getName()) - && Objects.equals(that.getChildRoles(), getChildRoles()); - } - - /** - * Returns the parent application. - * - * @return The parent application. - */ - public Application getApplication() { - return application; - } - - /** - * Returns the modifiable list of child roles. - * - * @return The modifiable list of child roles. - */ - public List getChildRoles() { - return childRoles; - } - - /** - * Returns the description. - * - * @return The description. - */ - public String getDescription() { - return description; - } - - /** - * Returns the name. - * - * @return The name. - */ - public String getName() { - return name; - } - - @Override - public int hashCode() { - return SystemUtils.hashCode(getApplication(), getName(), getChildRoles()); - } - - /** - * Sets the parent application. - * - * @param application The parent application. - */ - public void setApplication(Application application) { - this.application = application; - } - - /** - * Sets the modifiable list of child roles. This method clears the current list - * and adds all entries in the parameter list. - * - * @param childRoles A list of child roles. - */ - public void setChildRoles(List childRoles) { - synchronized (getChildRoles()) { - if (childRoles != getChildRoles()) { - getChildRoles().clear(); - - if (childRoles != null) { - getChildRoles().addAll(childRoles); - } - } - } - } - - /** - * Sets the description. - * - * @param description The description. - */ - public void setDescription(String description) { - this.description = description; - } - - /** - * Sets the name. - * - * @param name The name. - */ - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return getName(); - } - + /** + * Finds an existing role or creates a new one if needed. Note that a null description will be + * set if the role has to be created. + * + * @param application The parent application. + * @param name The role name to find or create. + * @return The role found or created. + */ + public static Role get(Application application, String name) { + return get(application, name, null); + } + + /** + * Finds an existing role or creates a new one if needed. + * + * @param application The parent application. + * @param name The role name to find or create. + * @param description The role description if one needs to be created. + * @return The role found or created. + */ + public static Role get(Application application, String name, String description) { + Role role = (application == null) ? null : application.getRole(name); + return (role == null) ? new Role(application, name, description) : role; + } + + /** The parent application. */ + private volatile Application application; + + /** The modifiable list of child roles. */ + private final List childRoles; + + /** The description. */ + private volatile String description; + + /** The name. */ + private volatile String name; + + /** + * Default constructor. Note that the parent application is retrieved using the {@link + * Application#getCurrent()} method if available or is null. + */ + public Role() { + this(Application.getCurrent(), null, null); + } + + /** + * Constructor. + * + * @param application The parent application or null. + * @param name The name. + */ + public Role(Application application, String name) { + this(application, name, null); + } + + /** + * Constructor. + * + * @param application The parent application or null. + * @param name The name. + * @param description The description. + */ + public Role(Application application, String name, String description) { + this.application = application; + this.name = name; + this.description = description; + this.childRoles = new CopyOnWriteArrayList<>(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Role that)) { + return false; + } + + return Objects.equals(getApplication(), that.getApplication()) + && Objects.equals(getName(), that.getName()) + && Objects.equals(getChildRoles(), that.getChildRoles()); + } + + /** + * Returns the parent application. + * + * @return The parent application. + */ + public Application getApplication() { + return application; + } + + /** + * Returns the modifiable list of child roles. + * + * @return The modifiable list of child roles. + */ + public List getChildRoles() { + return childRoles; + } + + /** + * Returns the description. + * + * @return The description. + */ + public String getDescription() { + return description; + } + + /** + * Returns the name. + * + * @return The name. + */ + public String getName() { + return name; + } + + @Override + public int hashCode() { + return SystemUtils.hashCode(getApplication(), getName(), getChildRoles()); + } + + /** + * Sets the parent application. + * + * @param application The parent application. + */ + public void setApplication(Application application) { + this.application = application; + } + + /** + * Sets the modifiable list of child roles. This method clears the current list and adds all + * entries in the parameter list. + * + * @param childRoles A list of child roles. + */ + public void setChildRoles(List childRoles) { + synchronized (getChildRoles()) { + if (childRoles != getChildRoles()) { + getChildRoles().clear(); + + if (childRoles != null) { + getChildRoles().addAll(childRoles); + } + } + } + } + + /** + * Sets the description. + * + * @param description The description. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Sets the name. + * + * @param name The name. + */ + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return getName(); + } } diff --git a/org.restlet/src/main/java/org/restlet/security/RoleAuthorizer.java b/org.restlet/src/main/java/org/restlet/security/RoleAuthorizer.java index 3f929e7908..269d3f4113 100644 --- a/org.restlet/src/main/java/org/restlet/security/RoleAuthorizer.java +++ b/org.restlet/src/main/java/org/restlet/security/RoleAuthorizer.java @@ -1,136 +1,131 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; -import org.restlet.Request; -import org.restlet.Response; - import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import org.restlet.Request; +import org.restlet.Response; /** - * Authorizer based on authorized and forbidden roles. Note that if no role is - * added to the "authorizedRoles" list, then only the "forbiddenRoles" list is - * considered. - * + * Authorizer based on authorized and forbidden roles. Note that if no role is added to the + * "authorizedRoles" list, then only the "forbiddenRoles" list is considered. + * * @author Jerome Louvel */ public class RoleAuthorizer extends Authorizer { - /** The modifiable list of authorized roles. */ - private List authorizedRoles; - - /** The modifiable list of forbidden roles. */ - private List forbiddenRoles; - - /** - * Default constructor. - */ - public RoleAuthorizer() { - this(null); - } - - /** - * Constructor. - * - * @param identifier The identifier unique within an application. - */ - public RoleAuthorizer(String identifier) { - super(identifier); - - this.authorizedRoles = new CopyOnWriteArrayList(); - this.forbiddenRoles = new CopyOnWriteArrayList(); - } - - /** - * Authorizes the request only if its subject is in one of the authorized roles - * and in none of the forbidden ones. - * - * @param request The request sent. - * @param response The response to update. - * @return True if the authorization succeeded. - */ - @Override - public boolean authorize(Request request, Response response) { - boolean authorized = false; - boolean forbidden = false; - - // Verify if the subject is in one of the authorized roles - if (getAuthorizedRoles().isEmpty()) { - authorized = true; - } else { - for (Role authorizedRole : getAuthorizedRoles()) { - authorized = authorized || request.getClientInfo().getRoles().contains(authorizedRole); - } - } - - // Verify if the subject is in one of the forbidden roles - for (Role forbiddenRole : getForbiddenRoles()) { - forbidden = forbidden || request.getClientInfo().getRoles().contains(forbiddenRole); - } - - return authorized && !forbidden; - } - - /** - * Returns the modifiable list of authorized roles. - * - * @return The modifiable list of authorized roles. - */ - public List getAuthorizedRoles() { - return authorizedRoles; - } - - /** - * Returns the modifiable list of forbidden roles. - * - * @return The modifiable list of forbidden roles. - */ - public List getForbiddenRoles() { - return forbiddenRoles; - } - - /** - * Sets the modifiable list of authorized roles. This method clears the current - * list and adds all entries in the parameter list. - * - * @param authorizedRoles A list of authorized roles. - */ - public void setAuthorizedRoles(List authorizedRoles) { - synchronized (getAuthorizedRoles()) { - if (authorizedRoles != getAuthorizedRoles()) { - getAuthorizedRoles().clear(); - - if (authorizedRoles != null) { - getAuthorizedRoles().addAll(authorizedRoles); - } - } - } - } - - /** - * Sets the modifiable list of forbidden roles. This method clears the current - * list and adds all entries in the parameter list. - * - * @param forbiddenRoles A list of forbidden roles. - */ - public void setForbiddenRoles(List forbiddenRoles) { - synchronized (getForbiddenRoles()) { - if (forbiddenRoles != getForbiddenRoles()) { - getForbiddenRoles().clear(); - - if (forbiddenRoles != null) { - getForbiddenRoles().addAll(forbiddenRoles); - } - } - } - } - + /** The modifiable list of authorized roles. */ + private List authorizedRoles; + + /** The modifiable list of forbidden roles. */ + private List forbiddenRoles; + + /** Default constructor. */ + public RoleAuthorizer() { + this(null); + } + + /** + * Constructor. + * + * @param identifier The identifier unique within an application. + */ + public RoleAuthorizer(String identifier) { + super(identifier); + + this.authorizedRoles = new CopyOnWriteArrayList<>(); + this.forbiddenRoles = new CopyOnWriteArrayList<>(); + } + + /** + * Authorizes the request only if its subject is in one of the authorized roles and in none of + * the forbidden ones. + * + * @param request The request sent. + * @param response The response to update. + * @return True if the authorization succeeded. + */ + @Override + public boolean authorize(Request request, Response response) { + boolean authorized = false; + boolean forbidden = false; + + // Verify if the subject is in one of the authorized roles + if (getAuthorizedRoles().isEmpty()) { + authorized = true; + } else { + for (Role authorizedRole : getAuthorizedRoles()) { + authorized = + authorized || request.getClientInfo().getRoles().contains(authorizedRole); + } + } + + // Verify if the subject is in one of the forbidden roles + for (Role forbiddenRole : getForbiddenRoles()) { + forbidden = forbidden || request.getClientInfo().getRoles().contains(forbiddenRole); + } + + return authorized && !forbidden; + } + + /** + * Returns the modifiable list of authorized roles. + * + * @return The modifiable list of authorized roles. + */ + public List getAuthorizedRoles() { + return authorizedRoles; + } + + /** + * Returns the modifiable list of forbidden roles. + * + * @return The modifiable list of forbidden roles. + */ + public List getForbiddenRoles() { + return forbiddenRoles; + } + + /** + * Sets the modifiable list of authorized roles. This method clears the current list and adds + * all entries in the parameter list. + * + * @param authorizedRoles A list of authorized roles. + */ + public void setAuthorizedRoles(List authorizedRoles) { + synchronized (getAuthorizedRoles()) { + if (authorizedRoles != getAuthorizedRoles()) { + getAuthorizedRoles().clear(); + + if (authorizedRoles != null) { + getAuthorizedRoles().addAll(authorizedRoles); + } + } + } + } + + /** + * Sets the modifiable list of forbidden roles. This method clears the current list and adds all + * entries in the parameter list. + * + * @param forbiddenRoles A list of forbidden roles. + */ + public void setForbiddenRoles(List forbiddenRoles) { + synchronized (getForbiddenRoles()) { + if (forbiddenRoles != getForbiddenRoles()) { + getForbiddenRoles().clear(); + + if (forbiddenRoles != null) { + getForbiddenRoles().addAll(forbiddenRoles); + } + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/security/SecretVerifier.java b/org.restlet/src/main/java/org/restlet/security/SecretVerifier.java index 969f155fc7..2cb7d18f65 100644 --- a/org.restlet/src/main/java/org/restlet/security/SecretVerifier.java +++ b/org.restlet/src/main/java/org/restlet/security/SecretVerifier.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import org.restlet.Request; @@ -15,112 +14,108 @@ import org.restlet.data.ClientInfo; /** - * Verifier of identifier/secret couples. By default, it extracts the identifier - * and the secret from the {@link ChallengeResponse}. If the verification is - * successful, it automatically adds a new {@link User} for the given - * identifier. - * + * Verifier of identifier/secret couples. By default, it extracts the identifier and the secret from + * the {@link ChallengeResponse}. If the verification is successful, it automatically adds a new + * {@link User} for the given identifier. + * * @author Jerome Louvel */ public abstract class SecretVerifier implements Verifier { - /** - * Compares that two secrets are equal and not null. - * - * @param secret1 The input secret. - * @param secret2 The output secret. - * @return True if both are equal. - */ - public static boolean compare(char[] secret1, char[] secret2) { - boolean result = false; - - if ((secret1 != null) && (secret2 != null)) { - // None is null - if (secret1.length == secret2.length) { - boolean equals = true; - - for (int i = 0; (i < secret1.length) && equals; i++) { - equals = (secret1[i] == secret2[i]); - } - - result = equals; - } - } - - return result; - } - - /** - * Called back to create a new user when valid credentials are provided. - * - * @param identifier The user identifier. - * @param request The request handled. - * @param response The response handled. - * @return The {@link User} instance created. - */ - protected User createUser(String identifier, Request request, Response response) { - return new User(identifier); - } - - /** - * Returns the user identifier. - * - * @param request The request to inspect. - * @param response The response to inspect. - * @return The user identifier. - */ - protected String getIdentifier(Request request, Response response) { - return request.getChallengeResponse().getIdentifier(); - } - - /** - * Returns the secret provided by the user. - * - * @param request The request to inspect. - * @param response The response to inspect. - * @return The secret provided by the user. - */ - protected char[] getSecret(Request request, Response response) { - return request.getChallengeResponse().getSecret(); - } - - /** - * Verifies that the proposed secret is correct for the specified request. By - * default, it compares the inputSecret of the request's authentication response - * with the one obtain by the {@link ChallengeResponse#getSecret()} method and - * sets the {@link org.restlet.security.User} instance of the request's - * {@link ClientInfo} if successful. - * - * @param request The request to inspect. - * @param response The response to inspect. - * @return Result of the verification based on the RESULT_* constants. - */ - public int verify(Request request, Response response) { - int result = RESULT_VALID; - - if (request.getChallengeResponse() == null) { - result = RESULT_MISSING; - } else { - String identifier = getIdentifier(request, response); - char[] secret = getSecret(request, response); - result = verify(identifier, secret); - - if (result == RESULT_VALID) { - request.getClientInfo().setUser(createUser(identifier, request, response)); - } - } - - return result; - } - - /** - * Verifies that the identifier/secret couple is valid. It throws an - * IllegalArgumentException in case the identifier is either null or does not - * identify a user. - * - * @param identifier The user identifier to match. - * @param secret The provided secret to verify. - * @return Result of the verification based on the RESULT_* constants. - */ - public abstract int verify(String identifier, char[] secret); - + /** + * Compares that two secrets are equal and not null. + * + * @param secret1 The input secret. + * @param secret2 The output secret. + * @return True if both are equal. + */ + public static boolean compare(char[] secret1, char[] secret2) { + boolean result = false; + + if ((secret1 != null) && (secret2 != null)) { + // None is null + if (secret1.length == secret2.length) { + boolean equals = true; + + for (int i = 0; (i < secret1.length) && equals; i++) { + equals = (secret1[i] == secret2[i]); + } + + result = equals; + } + } + + return result; + } + + /** + * Called back to create a new user when valid credentials are provided. + * + * @param identifier The user identifier. + * @param request The request handled. + * @param response The response handled. + * @return The {@link User} instance created. + */ + protected User createUser(String identifier, Request request, Response response) { + return new User(identifier); + } + + /** + * Returns the user identifier. + * + * @param request The request to inspect. + * @param response The response to inspect. + * @return The user identifier. + */ + protected String getIdentifier(Request request, Response response) { + return request.getChallengeResponse().getIdentifier(); + } + + /** + * Returns the secret provided by the user. + * + * @param request The request to inspect. + * @param response The response to inspect. + * @return The secret provided by the user. + */ + protected char[] getSecret(Request request, Response response) { + return request.getChallengeResponse().getSecret(); + } + + /** + * Verifies that the proposed secret is correct for the specified request. By default, it + * compares the inputSecret of the request's authentication response with the one obtain by the + * {@link ChallengeResponse#getSecret()} method and sets the {@link org.restlet.security.User} + * instance of the request's {@link ClientInfo} if successful. + * + * @param request The request to inspect. + * @param response The response to inspect. + * @return Result of the verification based on the RESULT_* constants. + */ + public int verify(Request request, Response response) { + int result = RESULT_VALID; + + if (request.getChallengeResponse() == null) { + result = RESULT_MISSING; + } else { + String identifier = getIdentifier(request, response); + char[] secret = getSecret(request, response); + result = verify(identifier, secret); + + if (result == RESULT_VALID) { + request.getClientInfo().setUser(createUser(identifier, request, response)); + } + } + + return result; + } + + /** + * Verifies that the identifier/secret couple is valid. It throws an IllegalArgumentException in + * case the identifier is either null or does not identify a user. + * + * @param identifier The user identifier to match. + * @param secret The provided secret to verify. + * @return Result of the verification based on the RESULT_* constants. + */ + public abstract int verify(String identifier, char[] secret); } diff --git a/org.restlet/src/main/java/org/restlet/security/User.java b/org.restlet/src/main/java/org/restlet/security/User.java index c6b6f9b0fb..4f3436d484 100644 --- a/org.restlet/src/main/java/org/restlet/security/User.java +++ b/org.restlet/src/main/java/org/restlet/security/User.java @@ -1,208 +1,204 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import java.security.Principal; /** - * User part of a security realm. Note the same user can be member of several - * groups. - * + * User part of a security realm. Note the same user can be a member of several groups. + * * @see Realm * @see Group * @author Jerome Louvel */ public class User implements Principal { - /** The email. */ - private volatile String email; - - /** The first name. */ - private volatile String firstName; - - /** The identifier. */ - private volatile String identifier; - - /** The last name. */ - private volatile String lastName; - - /** The secret. */ - private volatile char[] secret; - - /** - * Default constructor. - */ - public User() { - this(null, (char[]) null, null, null, null); - } - - /** - * Constructor. - * - * @param identifier The identifier (login). - */ - public User(String identifier) { - this(identifier, (char[]) null, null, null, null); - } - - /** - * Constructor. - * - * @param identifier The identifier (login). - * @param secret The identification secret. - */ - public User(String identifier, char[] secret) { - this(identifier, secret, null, null, null); - } - - /** - * Constructor. - * - * @param identifier The identifier (login). - * @param secret The identification secret. - * @param firstName The first name. - * @param lastName The last name. - * @param email The email. - */ - public User(String identifier, char[] secret, String firstName, String lastName, String email) { - this.identifier = identifier; - this.secret = secret; - this.firstName = firstName; - this.lastName = lastName; - this.email = email; - } - - /** - * Constructor. - * - * @param identifier The identifier (login). - * @param secret The identification secret. - */ - public User(String identifier, String secret) { - this(identifier, secret.toCharArray()); - } - - /** - * Constructor. - * - * @param identifier The identifier (login). - * @param secret The identification secret. - * @param firstName The first name. - * @param lastName The last name. - * @param email The email. - */ - public User(String identifier, String secret, String firstName, String lastName, String email) { - this(identifier, secret.toCharArray(), firstName, lastName, email); - } - - /** - * Returns the email. - * - * @return The email. - */ - public String getEmail() { - return email; - } - - /** - * Returns the first name. - * - * @return The first name. - */ - public String getFirstName() { - return firstName; - } - - /** - * Returns the identifier. - * - * @return The identifier. - */ - public String getIdentifier() { - return identifier; - } - - /** - * Returns the last name. - * - * @return The last name. - */ - public String getLastName() { - return lastName; - } - - /** - * Returns the user identifier. - * - * @see #getIdentifier() - */ - public String getName() { - return getIdentifier(); - } - - /** - * Returns the secret. - * - * @return The secret. - */ - public char[] getSecret() { - return secret; - } - - /** - * Sets the email. - * - * @param email The email. - */ - public void setEmail(String email) { - this.email = email; - } - - /** - * Sets the first name. - * - * @param firstName The first name. - */ - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - /** - * Sets the identifier. - * - * @param identifier The identifier. - */ - public void setIdentifier(String identifier) { - this.identifier = identifier; - } - - /** - * Sets the last name. - * - * @param lastName The last name. - */ - public void setLastName(String lastName) { - this.lastName = lastName; - } - - /** - * Sets the secret. - * - * @param secret The secret. - */ - public void setSecret(char[] secret) { - this.secret = secret; - } - - @Override - public String toString() { - return getIdentifier(); - } + /** The email. */ + private volatile String email; + + /** The first name. */ + private volatile String firstName; + + /** The identifier. */ + private volatile String identifier; + + /** The last name. */ + private volatile String lastName; + + /** The secret. */ + private volatile char[] secret; + + /** Default constructor. */ + public User() { + this(null, (char[]) null, null, null, null); + } + + /** + * Constructor. + * + * @param identifier The identifier (login). + */ + public User(String identifier) { + this(identifier, (char[]) null, null, null, null); + } + + /** + * Constructor. + * + * @param identifier The identifier (login). + * @param secret The identification secret. + */ + public User(String identifier, char[] secret) { + this(identifier, secret, null, null, null); + } + + /** + * Constructor. + * + * @param identifier The identifier (login). + * @param secret The identification secret. + * @param firstName The first name. + * @param lastName The last name. + * @param email The email. + */ + public User(String identifier, char[] secret, String firstName, String lastName, String email) { + this.identifier = identifier; + this.secret = secret; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + } + + /** + * Constructor. + * + * @param identifier The identifier (login). + * @param secret The identification secret. + */ + public User(String identifier, String secret) { + this(identifier, secret.toCharArray()); + } + + /** + * Constructor. + * + * @param identifier The identifier (login). + * @param secret The identification secret. + * @param firstName The first name. + * @param lastName The last name. + * @param email The email. + */ + public User(String identifier, String secret, String firstName, String lastName, String email) { + this(identifier, secret.toCharArray(), firstName, lastName, email); + } + + /** + * Returns the email. + * + * @return The email. + */ + public String getEmail() { + return email; + } + + /** + * Returns the first name. + * + * @return The first name. + */ + public String getFirstName() { + return firstName; + } + + /** + * Returns the identifier. + * + * @return The identifier. + */ + public String getIdentifier() { + return identifier; + } + + /** + * Returns the last name. + * + * @return The last name. + */ + public String getLastName() { + return lastName; + } + + /** + * Returns the user identifier. + * + * @see #getIdentifier() + */ + public String getName() { + return getIdentifier(); + } + + /** + * Returns the secret. + * + * @return The secret. + */ + public char[] getSecret() { + return secret; + } + + /** + * Sets the email. + * + * @param email The email. + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * Sets the first name. + * + * @param firstName The first name. + */ + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + /** + * Sets the identifier. + * + * @param identifier The identifier. + */ + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + /** + * Sets the last name. + * + * @param lastName The last name. + */ + public void setLastName(String lastName) { + this.lastName = lastName; + } + + /** + * Sets the secret. + * + * @param secret The secret. + */ + public void setSecret(char[] secret) { + this.secret = secret; + } + + @Override + public String toString() { + return getIdentifier(); + } } diff --git a/org.restlet/src/main/java/org/restlet/security/Verifier.java b/org.restlet/src/main/java/org/restlet/security/Verifier.java index c503b50b20..2671d67526 100644 --- a/org.restlet/src/main/java/org/restlet/security/Verifier.java +++ b/org.restlet/src/main/java/org/restlet/security/Verifier.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import org.restlet.Request; @@ -14,37 +13,35 @@ /** * Verifies the credentials provided by a client user sending a request. - * + * * @author Jerome Louvel */ public interface Verifier { - /** Invalid credentials provided. */ - public final static int RESULT_INVALID = -1; - - /** No credentials provided. */ - public final static int RESULT_MISSING = 0; + /** Invalid credentials provided. */ + int RESULT_INVALID = -1; - /** Stale credentials provided. */ - public final static int RESULT_STALE = 1; + /** No credentials provided. */ + int RESULT_MISSING = 0; - /** Unsupported credentials. */ - public final static int RESULT_UNSUPPORTED = 3; + /** Stale credentials provided. */ + int RESULT_STALE = 1; - /** Unknown user. */ - public final static int RESULT_UNKNOWN = 5; + /** Unsupported credentials. */ + int RESULT_UNSUPPORTED = 3; - /** Valid credentials provided. */ - public final static int RESULT_VALID = 4; + /** Unknown user. */ + int RESULT_UNKNOWN = 5; - /** - * Attempts to verify the credentials provided by the client user sending the - * request. - * - * @param request The request sent. - * @param response The response to update. - * @return Result of the verification based on the RESULT_* constants. - */ - int verify(Request request, Response response); + /** Valid credentials provided. */ + int RESULT_VALID = 4; + /** + * Attempts to verify the credentials provided by the client user sending the request. + * + * @param request The request sent. + * @param response The response to update. + * @return Result of the verification based on the RESULT_* constants. + */ + int verify(Request request, Response response); } diff --git a/org.restlet/src/main/java/org/restlet/security/package-info.java b/org.restlet/src/main/java/org/restlet/security/package-info.java new file mode 100644 index 0000000000..e0676fd58e --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/security/package-info.java @@ -0,0 +1,9 @@ +/** + * Classes related to security. + * + * @since Restlet 2.0 + * @see User + * Guide - Security package + */ +package org.restlet.security; diff --git a/org.restlet/src/main/java/org/restlet/security/package.html b/org.restlet/src/main/java/org/restlet/security/package.html deleted file mode 100644 index 446c81c83e..0000000000 --- a/org.restlet/src/main/java/org/restlet/security/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - - Classes related to security. -

- @since Restlet 2.0 - @see User Guide - Security package - - \ No newline at end of file diff --git a/org.restlet/src/main/java/org/restlet/service/ConnectorService.java b/org.restlet/src/main/java/org/restlet/service/ConnectorService.java index 58c6652e95..96a6ba0134 100644 --- a/org.restlet/src/main/java/org/restlet/service/ConnectorService.java +++ b/org.restlet/src/main/java/org/restlet/service/ConnectorService.java @@ -1,130 +1,119 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import org.restlet.Application; import org.restlet.data.Protocol; import org.restlet.representation.Representation; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - /** - * Application service declaring client and server connectors. This is useful at - * deployment time to know which connectors an application expects to be able to - * use.
+ * Application service declaring client and server connectors. This is useful at deployment time to + * know which connectors an application expects to be able to use.
*
- * If you need to override the {@link #afterSend(Representation)} method for - * example, just create a subclass and set it on your application with the - * {@link Application#setConnectorService(ConnectorService)} method.
+ * If you need to override the {@link #afterSend(Representation)} method for example, just create a + * subclass and set it on your application with the {@link + * Application#setConnectorService(ConnectorService)} method.
*
- * Implementation note: the parent component will ensure that client connectors - * won't automatically follow redirections. This will ensure a consistent - * behavior and portability of applications. - * + * Implementation note: the parent component will ensure that client connectors won't automatically + * follow redirections. This will ensure a consistent behavior and portability of applications. + * * @author Jerome Louvel */ public class ConnectorService extends Service { - /** The list of required client protocols. */ - private final List clientProtocols; - - /** The list of required server protocols. */ - private final List serverProtocols; + /** The list of required client protocols. */ + private final List clientProtocols; - /** - * Constructor. - */ - public ConnectorService() { - this.clientProtocols = new CopyOnWriteArrayList<>(); - this.serverProtocols = new CopyOnWriteArrayList<>(); - } + /** The list of required server protocols. */ + private final List serverProtocols; - /** - * Call-back method invoked by the client or server connectors just after - * sending the response to the target component. The default implementation does - * nothing. - * - * @param entity The optional entity about to be committed. - */ - public void afterSend(Representation entity) { - // Do nothing by default. - } + /** Constructor. */ + public ConnectorService() { + this.clientProtocols = new CopyOnWriteArrayList<>(); + this.serverProtocols = new CopyOnWriteArrayList<>(); + } - /** - * Call-back method invoked by the client or server connectors just before - * sending the response to the target component. The default implementation does - * nothing. - * - * @param entity The optional entity about to be committed. - */ - public void beforeSend(Representation entity) { - // Do nothing by default. - } + /** + * Call-back method invoked by the client or server connectors just after sending the response + * to the target component. The default implementation does nothing. + * + * @param entity The optional entity about to be committed. + */ + public void afterSend(Representation entity) { + // Do nothing by default. + } - /** - * Returns the modifiable list of required client protocols. You need to update - * this list if you need the parent component to provide additional client - * connectors. - * - * @return The list of required client protocols. - */ - public List getClientProtocols() { - return this.clientProtocols; - } + /** + * Call-back method invoked by the client or server connectors just before sending the response + * to the target component. The default implementation does nothing. + * + * @param entity The optional entity about to be committed. + */ + public void beforeSend(Representation entity) { + // Do nothing by default. + } - /** - * Returns the modifiable list of required server protocols. An empty list means - * that all protocols are potentially supported (default case). You should - * update this list to restrict the actual protocols supported by your - * application. - * - * @return The list of required server protocols. - */ - public List getServerProtocols() { - return this.serverProtocols; - } + /** + * Returns the modifiable list of required client protocols. You need to update this list if you + * need the parent component to provide additional client connectors. + * + * @return The list of required client protocols. + */ + public List getClientProtocols() { + return this.clientProtocols; + } - /** - * Sets the modifiable list of required client protocols. This method clears the - * current list and adds all entries in the parameter list. - * - * @param clientProtocols A list of required client protocols. - */ - public void setClientProtocols(List clientProtocols) { - synchronized (getClientProtocols()) { - if (clientProtocols != getClientProtocols()) { - getClientProtocols().clear(); + /** + * Returns the modifiable list of required server protocols. An empty list means that all + * protocols are potentially supported (default case). You should update this list to restrict + * the actual protocols supported by your application. + * + * @return The list of required server protocols. + */ + public List getServerProtocols() { + return this.serverProtocols; + } - if (clientProtocols != null) { - getClientProtocols().addAll(clientProtocols); - } - } - } - } + /** + * Sets the modifiable list of required client protocols. This method clears the current list + * and adds all entries in the parameter list. + * + * @param clientProtocols A list of required client protocols. + */ + public void setClientProtocols(List clientProtocols) { + synchronized (getClientProtocols()) { + if (clientProtocols != getClientProtocols()) { + getClientProtocols().clear(); - /** - * Sets the modifiable list of required server protocols. This method clears the - * current list and adds all entries in the parameter list. - * - * @param serverProtocols A list of required server protocols. - */ - public void setServerProtocols(List serverProtocols) { - synchronized (getServerProtocols()) { - if (serverProtocols != getServerProtocols()) { - getServerProtocols().clear(); + if (clientProtocols != null) { + getClientProtocols().addAll(clientProtocols); + } + } + } + } - if (serverProtocols != null) { - getServerProtocols().addAll(serverProtocols); - } - } - } - } + /** + * Sets the modifiable list of required server protocols. This method clears the current list + * and adds all entries in the parameter list. + * + * @param serverProtocols A list of required server protocols. + */ + public void setServerProtocols(List serverProtocols) { + synchronized (getServerProtocols()) { + if (serverProtocols != getServerProtocols()) { + getServerProtocols().clear(); + if (serverProtocols != null) { + getServerProtocols().addAll(serverProtocols); + } + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/service/ConnegService.java b/org.restlet/src/main/java/org/restlet/service/ConnegService.java index 0918778082..80cf6af7e8 100644 --- a/org.restlet/src/main/java/org/restlet/service/ConnegService.java +++ b/org.restlet/src/main/java/org/restlet/service/ConnegService.java @@ -1,96 +1,87 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import java.util.List; import org.restlet.Request; import org.restlet.engine.application.Conneg; import org.restlet.engine.application.FlexibleConneg; import org.restlet.engine.application.StrictConneg; import org.restlet.representation.Variant; -import java.util.List; - /** - * Application service negotiating the preferred resource variants. This service - * is leveraged by server-side and client-side content negotiation, annotated - * method dispatching, and so on. - * + * Application service negotiating the preferred resource variants. This service is leveraged by + * server-side and client-side content negotiation, annotated method dispatching, and so on. + * * @author Jerome Louvel */ public class ConnegService extends Service { - /** - * Indicates if the conneg algorithm should strictly respect client preferences - * or be more flexible. - */ - private volatile boolean strict; - - /** - * Constructor. - */ - public ConnegService() { - this(true); - } + /** + * Indicates if the conneg algorithm should strictly respect client preferences or be more + * flexible. + */ + private volatile boolean strict; - /** - * Constructor. - * - * @param enabled True if the service has been enabled. - */ - public ConnegService(boolean enabled) { - super(enabled); - this.strict = false; - } + /** Constructor. */ + public ConnegService() { + this(true); + } - /** - * Returns the best variant representation for a given resource according the - * the client preferences.
- * A default language is provided in case the variants don't match the client - * preferences. - * - * @param variants The list of variants to compare. - * @param request The request including client preferences. - * @param metadataService The metadata service used to get default metadata - * values. - * @return The preferred variant. - * @see Apache - * content negotiation algorithm - */ - public Variant getPreferredVariant(List variants, Request request, - MetadataService metadataService) { - Conneg conneg = isStrict() ? new StrictConneg(request, metadataService) - : new FlexibleConneg(request, metadataService); - return conneg.getPreferredVariant(variants); - } + /** + * Constructor. + * + * @param enabled True if the service has been enabled. + */ + public ConnegService(boolean enabled) { + super(enabled); + this.strict = false; + } - /** - * Indicates if the conneg algorithm should strictly respect client preferences - * or be more flexible. Value is false by default. - * - * @return True if the conneg algorithm should strictly respect client - * preferences. - */ - public boolean isStrict() { - return strict; - } + /** + * Returns the best variant representation for a given resource according the the client + * preferences.
+ * A default language is provided in case the variants don't match the client preferences. + * + * @param variants The list of variants to compare. + * @param request The request including client preferences. + * @param metadataService The metadata service used to get default metadata values. + * @return The preferred variant. + * @see Apache + * content negotiation algorithm + */ + public Variant getPreferredVariant( + List variants, Request request, MetadataService metadataService) { + Conneg conneg = + isStrict() + ? new StrictConneg(request, metadataService) + : new FlexibleConneg(request, metadataService); + return conneg.getPreferredVariant(variants); + } - /** - * Indicates if the conneg algorithm should strictly respect client preferences - * or be more flexible. - * - * @param strict True if the conneg algorithm should strictly respect client - * preferences. - */ - public void setStrict(boolean strict) { - this.strict = strict; - } + /** + * Indicates if the conneg algorithm should strictly respect client preferences or be more + * flexible. Value is false by default. + * + * @return True if the conneg algorithm should strictly respect client preferences. + */ + public boolean isStrict() { + return strict; + } + /** + * Indicates if the conneg algorithm should strictly respect client preferences or be more + * flexible. + * + * @param strict True if the conneg algorithm should strictly respect client preferences. + */ + public void setStrict(boolean strict) { + this.strict = strict; + } } diff --git a/org.restlet/src/main/java/org/restlet/service/ConverterService.java b/org.restlet/src/main/java/org/restlet/service/ConverterService.java index 4490a30a83..d94ccc6792 100644 --- a/org.restlet/src/main/java/org/restlet/service/ConverterService.java +++ b/org.restlet/src/main/java/org/restlet/service/ConverterService.java @@ -1,14 +1,17 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.data.MediaType; import org.restlet.data.Preference; @@ -20,309 +23,321 @@ import org.restlet.representation.Variant; import org.restlet.resource.Resource; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; - /** - * Application service converting between representation and regular Java - * objects. The conversion can work in both directions. Actual converters can be - * plugged into the engine to support this service.
+ * Application service converting between representation and regular Java objects. The conversion + * can work in both directions. Actual converters can be plugged into the engine to support this + * service.
*
- * Root object classes used for conversion shouldn't be generic classes - * otherwise important contextual type information will be missing at runtime - * due to Java type erasure mechanism. If needed, create a fully resolved - * subclasses and/or a container classes. - * + * Root object classes used for conversion shouldn't be generic classes, otherwise important + * contextual type information will be missing at runtime due to Java type erasure mechanism. If + * needed, create a fully resolved subclasses and/or a container classes. + * * @author Jerome Louvel */ public class ConverterService extends Service { - /** - * Constructor. - */ - public ConverterService() { - super(); - } - - /** - * Constructor. - * - * @param enabled True if the service has been enabled. - */ - public ConverterService(boolean enabled) { - super(enabled); - } - - /** - * Applies a patch representation to an initial representation in order to - * obtain a modified one. The patch must have a recognized media type in order - * for the {@link ConverterService} to be able to process it. - * - * @param initial The initial representation on which the patch must be applied. - * @param patch The patch representation to apply. - * @return The modified representation. - * @throws IOException - */ - public Representation applyPatch(Representation initial, Representation patch) throws IOException { - - return null; - } - - /** - * Creates a patch representation by calculating a diff between initial and - * modified representations. - * - * @param initial The initial representation. - * @param modified The modified representation. - * @return The patch representation able to convert the initial representation - * into the modified representation. - * @throws IOException - */ - public Representation createPatch(Representation initial, Representation modified) throws IOException { - - return null; - } - - /** - * Returns the list of object classes that can be converted from a given - * variant. - * - * @param source The source variant. - * @return The list of object class that can be converted. - */ - public List> getObjectClasses(Variant source) { - List> result = null; - List> helperObjectClasses = null; - - for (ConverterHelper ch : Engine.getInstance().getRegisteredConverters()) { - helperObjectClasses = ch.getObjectClasses(source); - - if (helperObjectClasses != null) { - if (result == null) { - result = new ArrayList>(); - } - - result.addAll(helperObjectClasses); - } - } - - return result; - } - - /** - * Returns the list of patch media types available for the given representation - * types. - * - * @param representationType The representation media type or null for all - * supported patch types. - * @return The list of patch media types available. - */ - public List getPatchTypes(MediaType representationType) { - return null; - } - - /** - * Returns the list of variants that can be converted from a given object class. - * - * @param source The source class. - * @param target The expected representation metadata. - * @return The list of variants that can be converted. - * @throws IOException - */ - public List getVariants(Class source, Variant target) throws IOException { - return ConverterUtils.getVariants(source, target); - } - - /** - * Reverts a patch representation from a modified representation in order to - * obtain the initial one. The patch must have a recognized media type in order - * for the {@link ConverterService} to be able to process it. - * - * @param modified The modified representation from which the patch must be - * reverted. - * @param patch The patch representation to revert. - * @return The initial representation. - * @throws IOException - */ - public Representation revertPatch(Representation modified, Representation patch) throws IOException { - - return null; - } - - /** - * Converts a Representation into a regular Java object. - * - * @param source The source representation to convert. - * @return The converted Java object. - * @throws IOException - */ - public Object toObject(Representation source) throws IOException { - return toObject(source, null, null); - } - - /** - * Converts a Representation into a regular Java object. - * - * @param The expected class of the Java object. - * @param source The source representation to convert. - * @param target The target class of the Java object. - * @param resource The parent resource. - * @return The converted Java object. - * @throws IOException - */ - public T toObject(Representation source, Class target, Resource resource) throws IOException { - T result = null; - boolean loggable = resource == null || resource.isLoggable(); - - if ((source != null) && source.isAvailable() && (source.getSize() != 0)) { - ConverterHelper ch = ConverterUtils.getBestHelper(source, target, resource); - - if (ch != null) { - if (loggable && Context.getCurrentLogger().isLoggable(Level.FINE)) { - Context.getCurrentLogger() - .fine("The following converter was selected for the " + source + " representation: " + ch); - } - - result = ch.toObject(source, target, resource); - - if (result instanceof Representation resultRepresentation) { - - // Copy the variant metadata - resultRepresentation.setCharacterSet(source.getCharacterSet()); - resultRepresentation.setMediaType(source.getMediaType()); - resultRepresentation.getEncodings().addAll(source.getEncodings()); - resultRepresentation.getLanguages().addAll(source.getLanguages()); - } - } else { - if (loggable) { - Context.getCurrentLogger() - .warning("Unable to find a converter for this representation : " + source); - } - } - } - - return result; - } - - /** - * Converts a regular Java object into a Representation. The converter will use - * the preferred variant of the selected converter. - * - * @param source The source object to convert. - * @return The converted representation. - * @throws IOException - */ - public Representation toRepresentation(Object source) throws IOException { - return toRepresentation(source, null, null); - } - - /** - * Converts a regular Java object into a Representation. - * - * @param source The source object to convert. - * @param target The target representation media type. - * @return The converted representation. - * @throws IOException - */ - public Representation toRepresentation(Object source, MediaType target) throws IOException { - return toRepresentation(source, new Variant(target)); - } - - /** - * Converts a regular Java object into a Representation. - * - * @param source The source object to convert. - * @param target The target representation variant. - * @return The converted representation. - * @throws IOException - */ - public Representation toRepresentation(Object source, Variant target) throws IOException { - return toRepresentation(source, target, null); - } - - /** - * Converts a regular Java object into a Representation. - * - * @param source The source object to convert. - * @param target The target representation variant. - * @param resource The parent resource. - * @return The converted representation. - * @throws IOException - */ - public Representation toRepresentation(Object source, Variant target, Resource resource) throws IOException { - Representation result = null; - boolean loggable = (resource == null) || resource.isLoggable(); - ConverterHelper ch = ConverterUtils.getBestHelper(source, target, resource); - - if (ch != null) { - if (loggable && Context.getCurrentLogger().isLoggable(Level.FINE)) { - Context.getCurrentLogger().fine("Converter selected for " + source.getClass().getSimpleName() + ": " - + ch.getClass().getSimpleName()); - } - - if (target == null) { - List variants = ch.getVariants(source.getClass()); - - if ((variants != null) && !variants.isEmpty()) { - if (resource != null) { - target = resource.getConnegService().getPreferredVariant(variants, resource.getRequest(), - resource.getMetadataService()); - } else { - target = variants.get(0); - } - } - } - if (target == null) { - target = new Variant(); - } - - result = ch.toRepresentation(source, target, resource); - - if (result != null) { - // Copy the variant metadata if necessary - if (result.getCharacterSet() == null) { - result.setCharacterSet(target.getCharacterSet()); - } - - if ((result.getMediaType() == null) || !result.getMediaType().isConcrete()) { - if ((target.getMediaType() != null) && target.getMediaType().isConcrete()) { - result.setMediaType(target.getMediaType()); - } else if (resource != null) { - result.setMediaType(resource.getMetadataService().getDefaultMediaType()); - } else { - result.setMediaType(MediaType.APPLICATION_OCTET_STREAM); - } - } - - if (result.getEncodings().isEmpty()) { - result.getEncodings().addAll(target.getEncodings()); - } - - if (result.getLanguages().isEmpty()) { - result.getLanguages().addAll(target.getLanguages()); - } - } - } else { - if (loggable) { - Context.getCurrentLogger().warning("Unable to find a converter for this object : " + source); - } - } - - return result; - } - - /** - * Updates the media type preferences with available conversion capabilities for - * the given entity class. - * - * @param preferences The media type preferences. - * @param entity The entity class to convert. - */ - public void updatePreferences(List> preferences, Class entity) { - for (ConverterHelper ch : Engine.getInstance().getRegisteredConverters()) { - ch.updatePreferences(preferences, entity); - } - } + /** Constructor. */ + public ConverterService() { + super(); + } + + /** + * Constructor. + * + * @param enabled True if the service has been enabled. + */ + public ConverterService(boolean enabled) { + super(enabled); + } + + /** + * Applies a patch representation to an initial representation to obtain a modified one. The + * patch must have a recognized media type in order for the {@link ConverterService} to be able + * to process it. + * + * @param initial The initial representation on which the patch must be applied. + * @param patch The patch representation to apply. + * @return The modified representation. + * @throws IOException + */ + public Representation applyPatch(Representation initial, Representation patch) + throws IOException { + + return null; + } + + /** + * Creates a patch representation by calculating a diff between initial and modified + * representations. + * + * @param initial The initial representation. + * @param modified The modified representation. + * @return The patch representation able to convert the initial representation into the modified + * representation. + * @throws IOException + */ + public Representation createPatch(Representation initial, Representation modified) + throws IOException { + + return null; + } + + /** + * Returns the list of object classes that can be converted from a given variant. + * + * @param source The source variant. + * @return The list of object class that can be converted. + */ + public List> getObjectClasses(Variant source) { + List> result = null; + List> helperObjectClasses = null; + + for (ConverterHelper ch : Engine.getInstance().getRegisteredConverters()) { + helperObjectClasses = ch.getObjectClasses(source); + + if (helperObjectClasses != null) { + if (result == null) { + result = new ArrayList<>(); + } + + result.addAll(helperObjectClasses); + } + } + + return result; + } + + /** + * Returns the list of patch media types available for the given representation types. + * + * @param representationType The representation media type or null for all supported patch + * types. + * @return The list of patch media types available. + */ + public List getPatchTypes(MediaType representationType) { + return null; + } + + /** + * Returns the list of variants that can be converted from a given object class. + * + * @param source The source class. + * @param target The expected representation metadata. + * @return The list of variants that can be converted. + */ + public List getVariants(Class source, Variant target) { + return ConverterUtils.getVariants(source, target); + } + + /** + * Reverts a patch representation from a modified representation to get the initial one. The + * patch must have a recognized media type in order for the {@link ConverterService} to be able + * to process it. + * + * @param modified The modified representation from which the patch must be reverted. + * @param patch The patch representation to revert. + * @return The initial representation. + * @throws IOException + */ + public Representation revertPatch(Representation modified, Representation patch) + throws IOException { + + return null; + } + + /** + * Converts a Representation into a regular Java object. + * + * @param source The source representation to convert. + * @return The converted Java object. + * @throws IOException + */ + public Object toObject(Representation source) throws IOException { + return toObject(source, null, null); + } + + /** + * Converts a Representation into a regular Java object. + * + * @param The expected class of the Java object. + * @param source The source representation to convert. + * @param target The target class of the Java object. + * @param resource The parent resource. + * @return The converted Java object. + * @throws IOException + */ + public T toObject(Representation source, Class target, Resource resource) + throws IOException { + T result = null; + boolean loggable = resource == null || resource.isLoggable(); + + if ((source != null) && source.isAvailable() && (source.getSize() != 0)) { + ConverterHelper ch = ConverterUtils.getBestHelper(source, target, resource); + + if (ch == null) { + if (loggable) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Unable to find a converter for this representation : {0}", + source); + } + } else { + if (loggable && Context.getCurrentLogger().isLoggable(Level.FINE)) { + Context.getCurrentLogger() + .fine( + "The following converter was selected for the " + + source + + " representation: " + + ch); + } + + result = ch.toObject(source, target, resource); + + if (result instanceof Representation resultRepresentation) { + + // Copy the variant metadata + resultRepresentation.setCharacterSet(source.getCharacterSet()); + resultRepresentation.setMediaType(source.getMediaType()); + resultRepresentation.getEncodings().addAll(source.getEncodings()); + resultRepresentation.getLanguages().addAll(source.getLanguages()); + } + } + } + + return result; + } + + /** + * Converts a regular Java object into a Representation. The converter will use the preferred + * variant of the selected converter. + * + * @param source The source object to convert. + * @return The converted representation. + * @throws IOException + */ + public Representation toRepresentation(Object source) throws IOException { + return toRepresentation(source, null, null); + } + + /** + * Converts a regular Java object into a Representation. + * + * @param source The source object to convert. + * @param target The target representation media type. + * @return The converted representation. + * @throws IOException + */ + public Representation toRepresentation(Object source, MediaType target) throws IOException { + return toRepresentation(source, new Variant(target)); + } + + /** + * Converts a regular Java object into a Representation. + * + * @param source The source object to convert. + * @param target The target representation variant. + * @return The converted representation. + * @throws IOException + */ + public Representation toRepresentation(Object source, Variant target) throws IOException { + return toRepresentation(source, target, null); + } + + /** + * Converts a regular Java object into a Representation. + * + * @param source The source object to convert. + * @param target The target representation variant. + * @param resource The parent resource. + * @return The converted representation. + * @throws IOException + */ + public Representation toRepresentation(Object source, Variant target, Resource resource) + throws IOException { + Representation result = null; + boolean loggable = (resource == null) || resource.isLoggable(); + ConverterHelper ch = ConverterUtils.getBestHelper(source, target, resource); + + if (ch != null) { + if (loggable && Context.getCurrentLogger().isLoggable(Level.FINE)) { + Context.getCurrentLogger() + .fine( + "Converter selected for " + + source.getClass().getSimpleName() + + ": " + + ch.getClass().getSimpleName()); + } + + if (target == null) { + List variants = ch.getVariants(source.getClass()); + + if ((variants != null) && !variants.isEmpty()) { + if (resource != null) { + target = + resource.getConnegService() + .getPreferredVariant( + variants, + resource.getRequest(), + resource.getMetadataService()); + } else { + target = variants.getFirst(); + } + } + } + if (target == null) { + target = new Variant(); + } + + result = ch.toRepresentation(source, target, resource); + + if (result != null) { + // Copy the variant metadata if necessary + if (result.getCharacterSet() == null) { + result.setCharacterSet(target.getCharacterSet()); + } + + if ((result.getMediaType() == null) || !result.getMediaType().isConcrete()) { + if ((target.getMediaType() != null) && target.getMediaType().isConcrete()) { + result.setMediaType(target.getMediaType()); + } else if (resource != null) { + result.setMediaType(resource.getMetadataService().getDefaultMediaType()); + } else { + result.setMediaType(MediaType.APPLICATION_OCTET_STREAM); + } + } + + if (result.getEncodings().isEmpty()) { + result.getEncodings().addAll(target.getEncodings()); + } + + if (result.getLanguages().isEmpty()) { + result.getLanguages().addAll(target.getLanguages()); + } + } + } else { + if (loggable) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "Unable to find a converter for this object : {0}", + source); + } + } + + return result; + } + + /** + * Updates the media type preferences with available conversion capabilities for the given + * entity class. + * + * @param preferences The media type preferences. + * @param entity The entity class to convert. + */ + public void updatePreferences(List> preferences, Class entity) { + for (ConverterHelper ch : Engine.getInstance().getRegisteredConverters()) { + ch.updatePreferences(preferences, entity); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/service/CorsService.java b/org.restlet/src/main/java/org/restlet/service/CorsService.java index 04bad797e9..766f8aabfe 100644 --- a/org.restlet/src/main/java/org/restlet/service/CorsService.java +++ b/org.restlet/src/main/java/org/restlet/service/CorsService.java @@ -1,291 +1,279 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.restlet.Context; import org.restlet.data.Method; import org.restlet.engine.application.CorsFilter; import org.restlet.engine.util.SetUtils; import org.restlet.routing.Filter; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - /** - * Application service that adds support of CORS. This service lets the target - * resource specifies the allowed methods. - * - * Example: - * + * Application service that adds support of CORS. This service lets the target resource specify the + * allowed methods. + * + *

Example: + * *

  * CorsService corsService = new CorsService();
  * corsService.setAllowedOrigins(new HashSet(Arrays.asList("http://server.com")));
  * corsService.setAllowedCredentials(true);
  * 
- * + * * @author Manuel Boillod */ public class CorsService extends Service { - /** - * If true, add 'Access-Control-Allow-Credentials' header. Default is false. - */ - private boolean allowedCredentials = false; + /** If true, add an 'Access-Control-Allow-Credentials' header. Default is false. */ + private boolean allowedCredentials = false; - /** - * The value of 'Access-Control-Allow-Headers' response header. Used only if - * {@link #allowingAllRequestedHeaders} is false. - */ - private Set allowedHeaders = null; + /** + * The value of 'Access-Control-Allow-Headers' response header. Used only if {@link + * #allowingAllRequestedHeaders} is false. + */ + private Set allowedHeaders = null; - /** The value of 'Access-Control-Allow-Origin' header. Default is '*'. */ - private Set allowedOrigins = SetUtils.newHashSet("*"); + /** The value of the 'Access-Control-Allow-Origin' header. Default is '*'. */ + private Set allowedOrigins = SetUtils.newHashSet("*"); - /** - * If true, copies the value of 'Access-Control-Request-Headers' request header - * into the 'Access-Control-Allow-Headers' response header. If false, use - * {@link #allowedHeaders}. Default is true. - */ - private boolean allowingAllRequestedHeaders = true; + /** + * If true, copies the value of 'Access-Control-Request-Headers' request header into the + * 'Access-Control-Allow-Headers' response header. If false, use {@link #allowedHeaders}. + * Default is true. + */ + private boolean allowingAllRequestedHeaders = true; - /** - * The set of methods allowed by default, used when - * {@link #skippingResourceForCorsOptions} is turned on. By default: GET, PUT, - * POST, DELETE, PATCH. - */ - private Set defaultAllowedMethods = new HashSet<>( - Arrays.asList(Method.GET, Method.POST, Method.PUT, Method.DELETE, Method.PATCH)); + /** + * The set of methods allowed by default, used when {@link #skippingResourceForCorsOptions} is + * turned on. The default list is: GET, PUT, POST, DELETE, PATCH. + */ + private Set defaultAllowedMethods = + new HashSet<>( + Arrays.asList( + Method.GET, Method.POST, Method.PUT, Method.DELETE, Method.PATCH)); - /** The value of 'Access-Control-Expose-Headers' response header. */ - private Set exposedHeaders = null; + /** The value of 'Access-Control-Expose-Headers' response header. */ + private Set exposedHeaders = null; - /** - * The value of 'Access-Control-Max-Age' response header. Default is that the - * header is not set. - */ - private int maxAge = -1; + /** + * The value of the 'Access-Control-Max-Age' response header. Default is that the header is not + * set. + */ + private int maxAge = -1; - /** - * If true, the filter does not call the server resource for OPTIONS method of - * CORS request and set Access-Control-Allow-Methods header with the default - * methods cf {@link #getDefaultAllowedMethods()}. Default is false. - */ - private boolean skippingResourceForCorsOptions = false; + /** + * If true, the filter does not call the server resource for OPTIONS method of CORS request and + * set Access-Control-Allow-Methods header with the default methods cf {@link + * #getDefaultAllowedMethods()}. Default is false. + */ + private boolean skippingResourceForCorsOptions = false; - /** - * Constructor. - */ - public CorsService() { - this(true); - } + /** Constructor. */ + public CorsService() { + this(true); + } - /** - * Constructor. - * - * @param enabled True if the service has been enabled. - */ - public CorsService(boolean enabled) { - super(enabled); - } + /** + * Constructor. + * + * @param enabled True if the service has been enabled. + */ + public CorsService(boolean enabled) { + super(enabled); + } - @Override - public Filter createInboundFilter(Context context) { - return new CorsFilter().setAllowedCredentials(allowedCredentials).setAllowedOrigins(allowedOrigins) - .setAllowingAllRequestedHeaders(allowingAllRequestedHeaders).setAllowedHeaders(allowedHeaders) - .setExposedHeaders(exposedHeaders).setSkippingResourceForCorsOptions(skippingResourceForCorsOptions) - .setDefaultAllowedMethods(getDefaultAllowedMethods()).setMaxAge(maxAge); - } + @Override + public Filter createInboundFilter(Context context) { + return new CorsFilter() + .setAllowedCredentials(allowedCredentials) + .setAllowedOrigins(allowedOrigins) + .setAllowingAllRequestedHeaders(allowingAllRequestedHeaders) + .setAllowedHeaders(allowedHeaders) + .setExposedHeaders(exposedHeaders) + .setSkippingResourceForCorsOptions(skippingResourceForCorsOptions) + .setDefaultAllowedMethods(getDefaultAllowedMethods()) + .setMaxAge(maxAge); + } - /** - * Returns the modifiable set of headers allowed by the actual request on the - * current resource.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Headers" header. - * - * @return The set of headers allowed by the actual request on the current - * resource. - */ - public Set getAllowedHeaders() { - return allowedHeaders; - } + /** + * Returns the modifiable set of headers allowed by the actual request on the current resource. + *
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Headers" header. + * + * @return The set of headers allowed by the actual request on the current resource. + */ + public Set getAllowedHeaders() { + return allowedHeaders; + } - /** - * Returns the URI an origin server allows for the requested resource. Use "*" - * as a wildcard character.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Allow-Origin" header. - * - * @return The origin allowed by the requested resource. - */ - public Set getAllowedOrigins() { - return allowedOrigins; - } + /** + * Returns the URI an origin server allows for the requested resource. Use "*" as a wildcard + * character.
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Allow-Origin" header. + * + * @return The origin allowed by the requested resource. + */ + public Set getAllowedOrigins() { + return allowedOrigins; + } - /** - * Returns the list of methods allowed by default, used when - * {@link #skippingResourceForCorsOptions} is turned on. - * - * @return The list of methods allowed by default, used when - * {@link #skippingResourceForCorsOptions} is turned on. - */ - public Set getDefaultAllowedMethods() { - return defaultAllowedMethods; - } + /** + * Returns the list of methods allowed by default, used when {@link + * #skippingResourceForCorsOptions} is turned on. + * + * @return The list of methods allowed by default, used when {@link + * #skippingResourceForCorsOptions} is turned on. + */ + public Set getDefaultAllowedMethods() { + return defaultAllowedMethods; + } - /** - * Returns a modifiable whitelist of headers an origin server allows for the - * requested resource.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Expose-Headers" header. - * - * @return The set of headers an origin server allows for the requested - * resource. - */ - public Set getExposedHeaders() { - return exposedHeaders; - } + /** + * Returns a modifiable whitelist of headers an origin server allows for the requested resource. + *
+ * Note that when used with HTTP connectors, this property maps to the + * "Access-Control-Expose-Headers" header. + * + * @return The set of headers an origin server allows for the requested resource. + */ + public Set getExposedHeaders() { + return exposedHeaders; + } - /** - * Indicates how long (in seconds) the results of a preflight request can be - * cached in a preflight result cache.
- * In case of a negative value, the results of a preflight request is not meant - * to be cached.
- * Note that when used with HTTP connectors, this property maps to the - * "Access-Control-Max-Age" header. - * - * @return Indicates how long the results of a preflight request can be cached - * in a preflight result cache. - */ - public int getMaxAge() { - return maxAge; - } + /** + * Indicates how long (in seconds) the results of a preflight request can be cached in a + * preflight result cache.
+ * In case of a negative value, the results of a preflight request is not meant to be cached. + *
+ * Note that when used with HTTP connectors, this property maps to the "Access-Control-Max-Age" + * header. + * + * @return Indicates how long the results of a preflight request can be cached in a preflight + * result cache. + */ + public int getMaxAge() { + return maxAge; + } - /** - * If true, adds 'Access-Control-Allow-Credentials' header. - * - * @return True, if the 'Access-Control-Allow-Credentials' header will be added. - */ - public boolean isAllowedCredentials() { - return allowedCredentials; - } + /** + * If true, adds 'Access-Control-Allow-Credentials' header. + * + * @return True, if the 'Access-Control-Allow-Credentials' header will be added. + */ + public boolean isAllowedCredentials() { + return allowedCredentials; + } - /** - * If true, indicates that the value of 'Access-Control-Request-Headers' request - * header will be copied into the 'Access-Control-Allow-Headers' response - * header. If false, use {@link #allowedHeaders}. - * - * @return True to copy the value - */ - public boolean isAllowingAllRequestedHeaders() { - return allowingAllRequestedHeaders; - } + /** + * If true, indicates that the value of 'Access-Control-Request-Headers' request header will be + * copied into the 'Access-Control-Allow-Headers' response header. If false, use {@link + * #allowedHeaders}. + * + * @return True to copy the value + */ + public boolean isAllowingAllRequestedHeaders() { + return allowingAllRequestedHeaders; + } - /** - * If true, the filter does not call the server resource for OPTIONS method of - * CORS request and set Access-Control-Allow-Methods header with the default - * methods. Default is false. - * - * @return True if the filter does not call the server resource for OPTIONS - * method of CORS request. - */ - public boolean isSkippingResourceForCorsOptions() { - return skippingResourceForCorsOptions; - } + /** + * If true, the filter does not call the server resource for OPTIONS method of CORS request and + * set the Access-Control-Allow-Methods header with the default methods. Default is false. + * + * @return True if the filter does not call the server resource for OPTIONS method of CORS + * request. + */ + public boolean isSkippingResourceForCorsOptions() { + return skippingResourceForCorsOptions; + } - /** - * If true, adds 'Access-Control-Allow-Credentials' header. - * - * @param allowedCredentials True to add the 'Access-Control-Allow-Credentials' - * header. - */ - public void setAllowedCredentials(boolean allowedCredentials) { - this.allowedCredentials = allowedCredentials; - } + /** + * If true, adds 'Access-Control-Allow-Credentials' header. + * + * @param allowedCredentials True to add the 'Access-Control-Allow-Credentials' header. + */ + public void setAllowedCredentials(boolean allowedCredentials) { + this.allowedCredentials = allowedCredentials; + } - /** - * Sets the value of the 'Access-Control-Allow-Headers' response header. Used - * only if {@link #allowingAllRequestedHeaders} is false. - * - * @param allowedHeaders The value of 'Access-Control-Allow-Headers' response - * header. - */ - public void setAllowedHeaders(Set allowedHeaders) { - this.allowedHeaders = allowedHeaders; - } + /** + * Sets the value of the 'Access-Control-Allow-Headers' response header. Used only if {@link + * #allowingAllRequestedHeaders} is false. + * + * @param allowedHeaders The value of 'Access-Control-Allow-Headers' response header. + */ + public void setAllowedHeaders(Set allowedHeaders) { + this.allowedHeaders = allowedHeaders; + } - /** - * Sets the value of 'Access-Control-Allow-Origin' header. - * - * @param allowedOrigins The value of 'Access-Control-Allow-Origin' header. - */ - public void setAllowedOrigins(Set allowedOrigins) { - this.allowedOrigins = allowedOrigins; - } + /** + * Sets the value of the 'Access-Control-Allow-Origin' header. + * + * @param allowedOrigins The value of the 'Access-Control-Allow-Origin' header. + */ + public void setAllowedOrigins(Set allowedOrigins) { + this.allowedOrigins = allowedOrigins; + } - /** - * If true, copies the value of 'Access-Control-Request-Headers' request header - * into the 'Access-Control-Allow-Headers' response header. If false, use - * {@link #allowedHeaders}. - * - * @param allowingAllRequestedHeaders True to copy the value of - * 'Access-Control-Request-Headers' request - * header into the - * 'Access-Control-Allow-Headers' response - * header. If false, use - * {@link #allowedHeaders}. - */ - public void setAllowingAllRequestedHeaders(boolean allowingAllRequestedHeaders) { - this.allowingAllRequestedHeaders = allowingAllRequestedHeaders; - } + /** + * If true, copies the value of 'Access-Control-Request-Headers' request header into the + * 'Access-Control-Allow-Headers' response header. If false, use {@link #allowedHeaders}. + * + * @param allowingAllRequestedHeaders True to copy the value of 'Access-Control-Request-Headers' + * request header into the 'Access-Control-Allow-Headers' response header. If false, use + * {@link #allowedHeaders}. + */ + public void setAllowingAllRequestedHeaders(boolean allowingAllRequestedHeaders) { + this.allowingAllRequestedHeaders = allowingAllRequestedHeaders; + } - /** - * Sets the list of methods allowed by default, used when - * {@link #skippingResourceForCorsOptions} is turned on. - * - * @param defaultAllowedMethods The list of methods allowed by default, used - * when {@link #skippingResourceForCorsOptions} is - * turned on. - */ - public void setDefaultAllowedMethods(Set defaultAllowedMethods) { - this.defaultAllowedMethods = defaultAllowedMethods; - } + /** + * Sets the list of methods allowed by default, used when {@link + * #skippingResourceForCorsOptions} is turned on. + * + * @param defaultAllowedMethods The list of methods allowed by default, used when {@link + * #skippingResourceForCorsOptions} is turned on. + */ + public void setDefaultAllowedMethods(Set defaultAllowedMethods) { + this.defaultAllowedMethods = defaultAllowedMethods; + } - /** - * Sets the value of 'Access-Control-Expose-Headers' response header. - * - * @param exposedHeaders The value of 'Access-Control-Expose-Headers' response - * header. - */ - public void setExposedHeaders(Set exposedHeaders) { - this.exposedHeaders = exposedHeaders; - } + /** + * Sets the value of 'Access-Control-Expose-Headers' response header. + * + * @param exposedHeaders The value of 'Access-Control-Expose-Headers' response header. + */ + public void setExposedHeaders(Set exposedHeaders) { + this.exposedHeaders = exposedHeaders; + } - /** - * Sets the value of 'Access-Control-Max-Age' response header.
- * In case of negative value, the header is not set. - * - * @param maxAge The value of 'Access-Control-Max-Age' response header. - */ - public void setMaxAge(int maxAge) { - this.maxAge = maxAge; - } + /** + * Sets the value of the 'Access-Control-Max-Age' response header.
+ * In the case of negative value, the header is not set. + * + * @param maxAge The value of the 'Access-Control-Max-Age' response header. + */ + public void setMaxAge(int maxAge) { + this.maxAge = maxAge; + } - /** - * Sets the value of skipResourceForCorsOptions field. - * - * @param skipResourceForCorsOptions True if the filter does not call the server - * resource for OPTIONS method of CORS - * request. - */ - public void setSkippingResourceForCorsOptions(boolean skipResourceForCorsOptions) { - this.skippingResourceForCorsOptions = skipResourceForCorsOptions; - } + /** + * Sets the value of the skipResourceForCorsOptions field. + * + * @param skipResourceForCorsOptions True if the filter does not call the server resource for + * OPTIONS method of CORS request. + */ + public void setSkippingResourceForCorsOptions(boolean skipResourceForCorsOptions) { + this.skippingResourceForCorsOptions = skipResourceForCorsOptions; + } } diff --git a/org.restlet/src/main/java/org/restlet/service/DecoderService.java b/org.restlet/src/main/java/org/restlet/service/DecoderService.java index 3dff67b15a..1a5f4fba96 100644 --- a/org.restlet/src/main/java/org/restlet/service/DecoderService.java +++ b/org.restlet/src/main/java/org/restlet/service/DecoderService.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; import org.restlet.Context; @@ -14,38 +13,35 @@ import org.restlet.routing.Filter; /** - * Application service automatically decoding or uncompressing received - * entities. This service works both for received requests entities on the - * server-side and received response entities on the client-side. - * + * Application service automatically decoding or uncompressing received entities. This service works + * both for received requests entities on the server-side and received response entities on the + * client-side. + * * @author Jerome Louvel */ public class DecoderService extends Service { - /** - * Constructor. - */ - public DecoderService() { - super(); - } - - /** - * Constructor. - * - * @param enabled True if the service has been enabled. - */ - public DecoderService(boolean enabled) { - super(enabled); - } - - @Override - public Filter createInboundFilter(Context context) { - return new Decoder(context, true, false); - } - - @Override - public Filter createOutboundFilter(Context context) { - return new Decoder(context, false, true); - } - + /** Constructor. */ + public DecoderService() { + super(); + } + + /** + * Constructor. + * + * @param enabled True if the service has been enabled. + */ + public DecoderService(boolean enabled) { + super(enabled); + } + + @Override + public Filter createInboundFilter(Context context) { + return new Decoder(context, true, false); + } + + @Override + public Filter createOutboundFilter(Context context) { + return new Decoder(context, false, true); + } } diff --git a/org.restlet/src/main/java/org/restlet/service/EncoderService.java b/org.restlet/src/main/java/org/restlet/service/EncoderService.java index cf70082f40..50179f7afc 100644 --- a/org.restlet/src/main/java/org/restlet/service/EncoderService.java +++ b/org.restlet/src/main/java/org/restlet/service/EncoderService.java @@ -1,14 +1,17 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import org.restlet.Context; import org.restlet.data.Encoding; import org.restlet.data.MediaType; @@ -16,185 +19,177 @@ import org.restlet.representation.Representation; import org.restlet.routing.Filter; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - /** * Application service automatically encoding or compressing request entities. - * + * * @author Jerome Louvel */ public class EncoderService extends Service { - /** Indicates if the encoding should always occur, regardless of the size. */ - public static final int ANY_SIZE = -1; + /** Indicates if the encoding should always occur, regardless of the size. */ + public static final int ANY_SIZE = -1; - /** Indicates if the default minimum size for encoding to occur. */ - public static final int DEFAULT_MINIMUM_SIZE = 1000; + /** Indicates if the default minimum size for encoding to occur. */ + public static final int DEFAULT_MINIMUM_SIZE = 1000; - /** - * Returns the list of default encoded media types. This can be overridden by - * subclasses. By default, all media types are encoded (except those explicitly - * ignored). - * - * @return The list of default encoded media types. - */ - public static List getDefaultAcceptedMediaTypes() { + /** + * Returns the list of default encoded media types. Subclasses can override this. By default, + * all media types are encoded (except those explicitly ignored). + * + * @return The list of default encoded media types. + */ + public static List getDefaultAcceptedMediaTypes() { return Arrays.asList(MediaType.ALL); - } - - /** - * Returns the list of default ignored media types. This can be overridden by - * subclasses. By default, all archive, audio, image and video media types are - * ignored. - * - * @return The list of default ignored media types. - */ - public static List getDefaultIgnoredMediaTypes() { - return Arrays.asList(MediaType.APPLICATION_CAB, - MediaType.APPLICATION_GNU_ZIP, MediaType.APPLICATION_ZIP, MediaType.APPLICATION_GNU_TAR, - MediaType.APPLICATION_JAVA_ARCHIVE, MediaType.APPLICATION_STUFFIT, MediaType.APPLICATION_TAR, - MediaType.AUDIO_ALL, MediaType.IMAGE_ALL, MediaType.VIDEO_ALL); - } - - /** - * The media types that should be encoded. - */ - private final List acceptedMediaTypes; - - /** - * The media types that should be ignored. - */ - private final List ignoredMediaTypes; - - /** - * The minimal size necessary for encoding. - */ - private volatile long mininumSize; - - /** - * Constructor. - */ - public EncoderService() { - this(true); - } - - /** - * Constructor. The default minimum size - * - * @param enabled True if the service has been enabled. - */ - public EncoderService(boolean enabled) { - super(enabled); - this.mininumSize = DEFAULT_MINIMUM_SIZE; - this.acceptedMediaTypes = new CopyOnWriteArrayList(getDefaultAcceptedMediaTypes()); - this.ignoredMediaTypes = new CopyOnWriteArrayList(getDefaultIgnoredMediaTypes()); - } - - /** - * Indicates if a representation can be encoded. - * - * @param representation The representation to test. - * @return True if the call can be encoded. - */ - public boolean canEncode(Representation representation) { - // Test the existence of the representation and that no existing - // encoding applies - boolean result = false; - - if (representation != null) { - boolean identity = true; - - for (Iterator iter = representation.getEncodings().iterator(); identity && iter.hasNext();) { - identity = (iter.next().equals(Encoding.IDENTITY)); - } - - result = identity; - } - - if (result) { - // Test the size of the representation - result = (getMinimumSize() == EncoderService.ANY_SIZE) - || (representation.getSize() == Representation.UNKNOWN_SIZE) - || (representation.getSize() >= getMinimumSize()); - } - - if (result) { - // Test the acceptance of the media type - MediaType mediaType = representation.getMediaType(); - boolean accepted = false; - - for (Iterator iter = getAcceptedMediaTypes().iterator(); !accepted && iter.hasNext();) { - accepted = iter.next().includes(mediaType); - } - - result = accepted; - } - - if (result) { - // Test the rejection of the media type - MediaType mediaType = representation.getMediaType(); - boolean rejected = false; - - for (Iterator iter = getIgnoredMediaTypes().iterator(); !rejected && iter.hasNext();) { - rejected = iter.next().includes(mediaType); - } - - result = !rejected; - } - - return result; - } - - @Override - public Filter createInboundFilter(Context context) { - return new Encoder(context, false, true, this); - } - - @Override - public Filter createOutboundFilter(Context context) { - return new Encoder(context, true, false, this); - } - - /** - * Returns the media types that should be encoded. - * - * @return The media types that should be encoded. - */ - public List getAcceptedMediaTypes() { - return this.acceptedMediaTypes; - } - - /** - * Returns the media types that should be ignored. - * - * @return The media types that should be ignored. - */ - public List getIgnoredMediaTypes() { - return this.ignoredMediaTypes; - } - - /** - * Returns the minimum size a representation must have before compression is - * done. - * - * @return The minimum size a representation must have before compression is - * done. - */ - public long getMinimumSize() { - return this.mininumSize; - } - - /** - * Sets the minimum size a representation must have before compression is done. - * - * @param mininumSize The minimum size a representation must have before - * compression is done. - */ - public void setMinimumSize(long mininumSize) { - this.mininumSize = mininumSize; - } - + } + + /** + * Returns the list of default ignored media types. Subclasses can override this. By default, + * all archive, audio, image, and video media types are ignored. + * + * @return The list of default ignored media types. + */ + public static List getDefaultIgnoredMediaTypes() { + return Arrays.asList( + MediaType.APPLICATION_CAB, + MediaType.APPLICATION_GNU_ZIP, + MediaType.APPLICATION_ZIP, + MediaType.APPLICATION_GNU_TAR, + MediaType.APPLICATION_JAVA_ARCHIVE, + MediaType.APPLICATION_STUFFIT, + MediaType.APPLICATION_TAR, + MediaType.AUDIO_ALL, + MediaType.IMAGE_ALL, + MediaType.VIDEO_ALL); + } + + /** The media types that should be encoded. */ + private final List acceptedMediaTypes; + + /** The media types that should be ignored. */ + private final List ignoredMediaTypes; + + /** The minimal size necessary for encoding. */ + private volatile long mininumSize; + + /** Constructor. */ + public EncoderService() { + this(true); + } + + /** + * Constructor. The default minimum size + * + * @param enabled True if the service has been enabled. + */ + public EncoderService(boolean enabled) { + super(enabled); + this.mininumSize = DEFAULT_MINIMUM_SIZE; + this.acceptedMediaTypes = new CopyOnWriteArrayList<>(getDefaultAcceptedMediaTypes()); + this.ignoredMediaTypes = new CopyOnWriteArrayList<>(getDefaultIgnoredMediaTypes()); + } + + /** + * Indicates if a representation can be encoded. + * + * @param representation The representation to test. + * @return True if the call can be encoded. + */ + public boolean canEncode(Representation representation) { + // Test the existence of the representation and that no existing + // encoding applies + boolean result = false; + + if (representation != null) { + boolean identity = true; + + for (Iterator iter = representation.getEncodings().iterator(); + identity && iter.hasNext(); ) { + identity = (iter.next().equals(Encoding.IDENTITY)); + } + + result = identity; + } + + if (result) { + // Test the size of the representation + result = + (getMinimumSize() == EncoderService.ANY_SIZE) + || (representation.getSize() == Representation.UNKNOWN_SIZE) + || (representation.getSize() >= getMinimumSize()); + } + + if (result) { + // Test the acceptance of the media type + MediaType mediaType = representation.getMediaType(); + boolean accepted = false; + + for (Iterator iter = getAcceptedMediaTypes().iterator(); + !accepted && iter.hasNext(); ) { + accepted = iter.next().includes(mediaType); + } + + result = accepted; + } + + if (result) { + // Test the rejection of the media type + MediaType mediaType = representation.getMediaType(); + boolean rejected = false; + + for (Iterator iter = getIgnoredMediaTypes().iterator(); + !rejected && iter.hasNext(); ) { + rejected = iter.next().includes(mediaType); + } + + result = !rejected; + } + + return result; + } + + @Override + public Filter createInboundFilter(Context context) { + return new Encoder(context, false, true, this); + } + + @Override + public Filter createOutboundFilter(Context context) { + return new Encoder(context, true, false, this); + } + + /** + * Returns the media types that should be encoded. + * + * @return The media types that should be encoded. + */ + public List getAcceptedMediaTypes() { + return this.acceptedMediaTypes; + } + + /** + * Returns the media types that should be ignored. + * + * @return The media types that should be ignored. + */ + public List getIgnoredMediaTypes() { + return this.ignoredMediaTypes; + } + + /** + * Returns the minimum size a representation must have before compression is done. + * + * @return The minimum size a representation must have before compression is done. + */ + public long getMinimumSize() { + return this.mininumSize; + } + + /** + * Sets the minimum size a representation must have before compression is done. + * + * @param mininumSize The minimum size a representation must have before compression is done. + */ + public void setMinimumSize(long mininumSize) { + this.mininumSize = mininumSize; + } } diff --git a/org.restlet/src/main/java/org/restlet/service/LogService.java b/org.restlet/src/main/java/org/restlet/service/LogService.java index 179ba44088..0879a602d8 100644 --- a/org.restlet/src/main/java/org/restlet/service/LogService.java +++ b/org.restlet/src/main/java/org/restlet/service/LogService.java @@ -1,14 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import java.util.logging.Level; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; @@ -22,378 +22,396 @@ import org.restlet.routing.Filter; import org.restlet.routing.Template; -import java.util.logging.Level; - /** - * Service providing access logging service. The implementation is fully based - * on the standard logging mechanism introduced in JDK 1.4.
+ * Service providing access logging service. The implementation is fully based on the standard + * logging mechanism introduced in JDK 1.4.
*
- * The default access log format follows the - * W3C Extended Log File - * Format with the following fields used:
+ * The default access log format follows the W3C + * Extended Log File Format with the following fields used:
+ * *

    - *
  1. Date (YYYY-MM-DD)
  2. - *
  3. Time (HH:MM:SS)
  4. - *
  5. Client address (IP)
  6. - *
  7. Remote user identifier (see RFC 1413)
  8. - *
  9. Server address (IP)
  10. - *
  11. Server port
  12. - *
  13. Method (GET|POST|...)
  14. - *
  15. Resource reference path (including the leading slash)
  16. - *
  17. Resource reference query (excluding the leading question mark)
  18. - *
  19. Response status code
  20. - *
  21. Number of bytes sent
  22. - *
  23. Number of bytes received
  24. - *
  25. Time to serve the request (in milliseconds)
  26. - *
  27. Host reference
  28. - *
  29. Client agent name
  30. - *
  31. Referrer reference
  32. + *
  33. Date (YYYY-MM-DD) + *
  34. Time (HH:MM:SS) + *
  35. Client address (IP) + *
  36. Remote user identifier (see RFC 1413) + *
  37. Server address (IP) + *
  38. Server port + *
  39. Method (GET|POST|...) + *
  40. Resource reference path (including the leading slash) + *
  41. Resource reference query (excluding the leading question mark) + *
  42. Response status code + *
  43. Number of bytes sent + *
  44. Number of bytes received + *
  45. Time to serve the request (in milliseconds) + *
  46. Host reference + *
  47. Client agent name + *
  48. Referrer reference *
+ * *
- * If you use Analog to - * generate your log reports, and if you use the default log format, then you - * can simply specify this string as a value of the LOGFORMAT command: + * If you use Analog to generate your log + * reports, and if you use the default log format, then you can simply specify this string as a + * value of the LOGFORMAT command: * (%Y-%m-%d\t%h:%n:%j\t%S\t%u\t%j\t%j\t%j\t%r\t%q\t%c\t%b\t%j\t%T\t%v\t%B\t%f)
*
- * For custom access log format, see the syntax to use and the list of available - * variable names in {@link org.restlet.routing.Template}.
- * + * For custom access log format, see the syntax to use and the list of available variable names in + * {@link org.restlet.routing.Template}.
+ * * @see java.util.logging + * "https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html">java.util.logging * @author Jerome Louvel */ public class LogService extends Service { - /** Indicates if the identity check (as specified by RFC1413) is enabled. */ - private volatile boolean identityCheck; - - /** The URI template of loggable resource references. */ - private volatile Template loggableTemplate; - - /** The access logger name. */ - private volatile String loggerName; - - /** The URI reference of the log properties. */ - private volatile Reference logPropertiesRef; - - /** The response log entry format. */ - private volatile String responseLogFormat; - - /** The response log template to use. */ - protected volatile Template responseLogTemplate; - - /** - * Constructor. - */ - public LogService() { - this(true); - } - - /** - * Constructor. - * - * @param enabled True if the service has been enabled. - */ - public LogService(boolean enabled) { - super(enabled); - this.loggableTemplate = null; - this.loggerName = null; - this.responseLogFormat = null; - this.logPropertiesRef = null; - this.identityCheck = false; - } - - @Override - public Filter createInboundFilter(Context context) { - return new LogFilter(context, this); - } - - /** - * Format a log entry using the default IIS log format. - * - * @param response The response to log. - * @param duration The call duration (in milliseconds). - * @return The formatted log entry. - */ - protected String getDefaultResponseLogMessage(Response response, int duration) { - StringBuilder sb = new StringBuilder(); - Request request = response.getRequest(); - long currentTime = System.currentTimeMillis(); - - // Append the date of the request - sb.append(String.format("%tF", currentTime)); - sb.append('\t'); - - // Append the time of the request - sb.append(String.format("%tT", currentTime)); - sb.append('\t'); - - // Append the client IP address - String clientAddress = request.getClientInfo().getUpstreamAddress(); - sb.append((clientAddress == null) ? "-" : clientAddress); - sb.append('\t'); - - // Append the user name (via IDENT protocol) - if (isIdentityCheck()) { - org.restlet.engine.log.IdentClient ic = new org.restlet.engine.log.IdentClient( - request.getClientInfo().getUpstreamAddress(), request.getClientInfo().getPort(), - response.getServerInfo().getPort()); - sb.append((ic.getUserIdentifier() == null) ? "-" : ic.getUserIdentifier()); - } else if ((request.getChallengeResponse() != null) - && (request.getChallengeResponse().getIdentifier() != null)) { - sb.append(request.getChallengeResponse().getIdentifier()); - } else { - sb.append('-'); - } - - sb.append('\t'); - - // Append the server IP address - String serverAddress = response.getServerInfo().getAddress(); - sb.append((serverAddress == null) ? "-" : serverAddress); - sb.append('\t'); - - // Append the server port - Integer serverport = response.getServerInfo().getPort(); - sb.append((serverport == null) ? "-" : serverport.toString()); - sb.append('\t'); - - // Append the method name - String methodName = (request.getMethod() == null) ? "-" : request.getMethod().getName(); - sb.append((methodName == null) ? "-" : methodName); - - // Append the resource path - sb.append('\t'); - String resourcePath = (request.getResourceRef() == null) ? "-" : request.getResourceRef().getPath(); - sb.append((resourcePath == null) ? "-" : resourcePath); - - // Append the resource query - sb.append('\t'); - String resourceQuery = (request.getResourceRef() == null) ? "-" : request.getResourceRef().getQuery(); - sb.append((resourceQuery == null) ? "-" : resourceQuery); - - // Append the status code - sb.append('\t'); - sb.append((response.getStatus() == null) ? "-" : Integer.toString(response.getStatus().getCode())); - - // Append the returned size - sb.append('\t'); - - if (!response.isEntityAvailable() || Status.REDIRECTION_NOT_MODIFIED.equals(response.getStatus()) - || Status.SUCCESS_NO_CONTENT.equals(response.getStatus()) || Method.HEAD.equals(request.getMethod())) { - sb.append('0'); - } else { - sb.append((response.getEntity().getSize() == -1) ? "-" : Long.toString(response.getEntity().getSize())); - } - - // Append the received size - sb.append('\t'); - - try { - if (request.getEntity() == null) { - sb.append('0'); - } else { - sb.append((request.getEntity().getSize() == -1) ? "-" : Long.toString(request.getEntity().getSize())); - } - } catch (Throwable t) { - // Error while getting the request's entity, cf issue #931 - Engine.getLogger(LogService.class).log(Level.SEVERE, "Cannot retrieve size of request's entity", t); - sb.append("-"); - } - - // Append the duration - sb.append('\t'); - sb.append(duration); - - // Append the host reference - sb.append('\t'); - sb.append((request.getHostRef() == null) ? "-" : request.getHostRef().toString()); - - // Append the agent name - sb.append('\t'); - String agentName = request.getClientInfo().getAgent(); - sb.append((agentName == null) ? "-" : agentName); - - // Append the referrer - sb.append('\t'); - sb.append((request.getReferrerRef() == null) ? "-" : request.getReferrerRef().getIdentifier()); - - return sb.toString(); - } - - /** - * Returns the URI template of loggable resource references. Returns null by - * default, meaning the all requests are loggable, independant of their target - * resource URI reference. - * - * @return The URI template of loggable resource references. - * @see Request#getResourceRef() - */ - public Template getLoggableTemplate() { - return loggableTemplate; - } - - /** - * Returns the name of the JDK's logger to use when logging access calls. The - * default name will follow this pattern: "org.restlet.MyComponent.LogService", - * where "MyComponent" will correspond to the simple class name of your - * component subclass or to the base "Component" class. - * - * @return The name of the JDK's logger to use when logging access calls. - */ - public String getLoggerName() { - return this.loggerName; - } - - /** - * Returns the URI reference of the log properties. - * - * @return The URI reference of the log properties. - */ - public Reference getLogPropertiesRef() { - return logPropertiesRef; - } - - /** - * Returns the format used when logging responses. - * - * @return The format used, or null if the default one is used. - * @see org.restlet.routing.Template for format syntax and variables. - */ - public String getResponseLogFormat() { - return this.responseLogFormat; - } - - /** - * Format an access log entry. If the log template property isn't provided, then - * a default IIS like format is used. - * - * @param response The response to log. - * @param duration The call duration. - * @return The formatted log entry. - */ - public String getResponseLogMessage(Response response, int duration) { - String result = null; - - // Format the call into a log entry - if (this.responseLogTemplate != null) { - result = this.responseLogTemplate.format(response.getRequest(), response); - } else { - result = getDefaultResponseLogMessage(response, duration); - } - - return result; - } - - /** - * Indicates if the identity check (as specified by RFC1413) is enabled. Default - * value is false. - * - * @return True if the identity check is enabled. - */ - public boolean isIdentityCheck() { - return this.identityCheck; - } - - /** - * Indicates if the call should be logged during the processing chain. By - * default, it tries to match the request URI with the - * {@link #getLoggableTemplate()} URI template otherwise is returns true. - * - * @param request The request to log. - * @return True if the call should be logged during the processing chain. - */ - public boolean isLoggable(Request request) { - return getLoggableTemplate() == null - || getLoggableTemplate().match(request.getResourceRef().getTargetRef().toString()) > 0; - } - - /** - * Indicates if the identity check (as specified by RFC1413) is enabled. - * - * @param identityCheck True if the identity check is enabled. - */ - public void setIdentityCheck(boolean identityCheck) { - this.identityCheck = identityCheck; - } - - /** - * Sets the URI template of loggable resource references. - * - * @param loggableTemplateRef The URI template of loggable resource references. - * @see #setLoggableTemplate(Template) - */ - public void setLoggableTemplate(String loggableTemplateRef) { - if (loggableTemplateRef != null) { - this.loggableTemplate = new Template(loggableTemplateRef); - } else { - this.loggableTemplate = null; - } - } - - /** - * Sets the URI template of loggable resource references. - * - * @param loggableTemplate The URI template of loggable resource references. - */ - public void setLoggableTemplate(Template loggableTemplate) { - this.loggableTemplate = loggableTemplate; - } - - /** - * Sets the name of the JDK's logger to use when logging access calls. - * - * @param name The name of the JDK's logger to use when logging access calls. - */ - public void setLoggerName(String name) { - this.loggerName = name; - } - - /** - * Sets the URI reference of the log properties. - * - * @param logPropertiesRef The URI reference of the log properties. - */ - public void setLogPropertiesRef(Reference logPropertiesRef) { - this.logPropertiesRef = logPropertiesRef; - } - - /** - * Sets the URI reference of the log properties. - * - * @param logPropertiesUri The URI reference of the log properties. - */ - public void setLogPropertiesRef(String logPropertiesUri) { - setLogPropertiesRef(new Reference(logPropertiesUri)); - } - - /** - * Sets the format to use when logging responses. The default format matches the - * one of IIS 6. - * - * @param responseLogFormat The format to use when logging responses. - * @see org.restlet.routing.Template for format syntax and variables. - */ - public void setResponseLogFormat(String responseLogFormat) { - this.responseLogFormat = responseLogFormat; - } - - /** - * Starts the log service by attempting to read the log properties if the - * {@link #getLogPropertiesRef()} returns a non null URI reference. - */ - @Override - public synchronized void start() throws Exception { - super.start(); - - this.responseLogTemplate = (getResponseLogFormat() == null) ? null : new Template(getResponseLogFormat()); - - if (getLogPropertiesRef() != null) { - Representation logProperties = new ClientResource(getContext(), getLogPropertiesRef()).get(); - - if (logProperties != null) { - java.util.logging.LogManager.getLogManager().readConfiguration(logProperties.getStream()); - } - } - } + /** Indicates if the identity check (as specified by RFC1413) is enabled. */ + private volatile boolean identityCheck; + + /** The URI template of loggable resource references. */ + private volatile Template loggableTemplate; + + /** The access logger name. */ + private volatile String loggerName; + + /** The URI reference of the log properties. */ + private volatile Reference logPropertiesRef; + + /** The response log entry format. */ + private volatile String responseLogFormat; + + /** The response log template to use. */ + protected volatile Template responseLogTemplate; + + /** Constructor. */ + public LogService() { + this(true); + } + + /** + * Constructor. + * + * @param enabled True if the service has been enabled. + */ + public LogService(boolean enabled) { + super(enabled); + this.loggableTemplate = null; + this.loggerName = null; + this.responseLogFormat = null; + this.logPropertiesRef = null; + this.identityCheck = false; + } + + @Override + public Filter createInboundFilter(Context context) { + return new LogFilter(context, this); + } + + /** + * Format a log entry using the default IIS log format. + * + * @param response The response to log. + * @param duration The call duration (in milliseconds). + * @return The formatted log entry. + */ + protected String getDefaultResponseLogMessage(Response response, int duration) { + StringBuilder sb = new StringBuilder(); + Request request = response.getRequest(); + long currentTime = System.currentTimeMillis(); + + // Append the date of the request + sb.append(String.format("%tF", currentTime)); + sb.append('\t'); + + // Append the time of the request + sb.append(String.format("%tT", currentTime)); + sb.append('\t'); + + // Append the client IP address + String clientAddress = request.getClientInfo().getUpstreamAddress(); + sb.append((clientAddress == null) ? "-" : clientAddress); + sb.append('\t'); + + // Append the user name (via IDENT protocol) + if (isIdentityCheck()) { + org.restlet.engine.log.IdentClient ic = + new org.restlet.engine.log.IdentClient( + request.getClientInfo().getUpstreamAddress(), + request.getClientInfo().getPort(), + response.getServerInfo().getPort()); + sb.append((ic.getUserIdentifier() == null) ? "-" : ic.getUserIdentifier()); + } else if ((request.getChallengeResponse() != null) + && (request.getChallengeResponse().getIdentifier() != null)) { + sb.append(request.getChallengeResponse().getIdentifier()); + } else { + sb.append('-'); + } + + sb.append('\t'); + + // Append the server IP address + String serverAddress = response.getServerInfo().getAddress(); + sb.append((serverAddress == null) ? "-" : serverAddress); + sb.append('\t'); + + // Append the server port + Integer serverPort = response.getServerInfo().getPort(); + sb.append((serverPort == null) ? "-" : serverPort.toString()); + sb.append('\t'); + + // Append the method name + String methodName = (request.getMethod() == null) ? "-" : request.getMethod().getName(); + sb.append((methodName == null) ? "-" : methodName); + + // Append the resource path + sb.append('\t'); + String resourcePath = + (request.getResourceRef() == null) ? "-" : request.getResourceRef().getPath(); + sb.append((resourcePath == null) ? "-" : resourcePath); + + // Append the resource query + sb.append('\t'); + String resourceQuery = + (request.getResourceRef() == null) ? "-" : request.getResourceRef().getQuery(); + sb.append((resourceQuery == null) ? "-" : resourceQuery); + + // Append the status code + sb.append('\t'); + sb.append( + (response.getStatus() == null) + ? "-" + : Integer.toString(response.getStatus().getCode())); + + // Append the returned size + sb.append('\t'); + + if (!response.isEntityAvailable() + || Status.REDIRECTION_NOT_MODIFIED.equals(response.getStatus()) + || Status.SUCCESS_NO_CONTENT.equals(response.getStatus()) + || Method.HEAD.equals(request.getMethod())) { + sb.append('0'); + } else { + sb.append( + (response.getEntity().getSize() == -1) + ? "-" + : Long.toString(response.getEntity().getSize())); + } + + // Append the received size + sb.append('\t'); + + try { + if (request.getEntity() == null) { + sb.append('0'); + } else { + sb.append( + (request.getEntity().getSize() == -1) + ? "-" + : Long.toString(request.getEntity().getSize())); + } + } catch (Throwable t) { + // Error while getting the request's entity, cf issue #931 + Engine.getLogger(LogService.class) + .log(Level.SEVERE, "Cannot retrieve size of request's entity", t); + sb.append("-"); + } + + // Append the duration + sb.append('\t'); + sb.append(duration); + + // Append the host reference + sb.append('\t'); + sb.append((request.getHostRef() == null) ? "-" : request.getHostRef().toString()); + + // Append the agent name + sb.append('\t'); + String agentName = request.getClientInfo().getAgent(); + sb.append((agentName == null) ? "-" : agentName); + + // Append the referrer + sb.append('\t'); + sb.append( + (request.getReferrerRef() == null) + ? "-" + : request.getReferrerRef().getIdentifier()); + + return sb.toString(); + } + + /** + * Returns the URI template of loggable resource references. Returns null by default, meaning + * all requests are loggable, independent of their target resource URI reference. + * + * @return The URI template of loggable resource references. + * @see Request#getResourceRef() + */ + public Template getLoggableTemplate() { + return loggableTemplate; + } + + /** + * Returns the name of the JDK's logger to use when logging access calls. The default name will + * follow this pattern: "org.restlet.MyComponent.LogService", where "MyComponent" will + * correspond to the simple class name of your component subclass or to the base "Component" + * class. + * + * @return The name of the JDK's logger to use when logging access calls. + */ + public String getLoggerName() { + return this.loggerName; + } + + /** + * Returns the URI reference of the log properties. + * + * @return The URI reference of the log properties. + */ + public Reference getLogPropertiesRef() { + return logPropertiesRef; + } + + /** + * Returns the format used when logging responses. + * + * @return The format used, or null if the default one is used. + * @see org.restlet.routing.Template for format syntax and variables. + */ + public String getResponseLogFormat() { + return this.responseLogFormat; + } + + /** + * Format an access log entry. If the log template property isn't provided, then a default IIS + * like format is used. + * + * @param response The response to log. + * @param duration The call duration. + * @return The formatted log entry. + */ + public String getResponseLogMessage(Response response, int duration) { + final String result; + + // Format the call into a log entry + if (this.responseLogTemplate != null) { + result = this.responseLogTemplate.format(response.getRequest(), response); + } else { + result = getDefaultResponseLogMessage(response, duration); + } + + return result; + } + + /** + * Indicates if the identity check (as specified by RFC1413) is enabled. The default value is + * false. + * + * @return True if the identity check is enabled. + */ + public boolean isIdentityCheck() { + return this.identityCheck; + } + + /** + * Indicates if the call should be logged during the processing chain. By default, it tries to + * match the request URI with the {@link #getLoggableTemplate()} URI template otherwise is + * returns true. + * + * @param request The request to log. + * @return True if the call should be logged during the processing chain. + */ + public boolean isLoggable(Request request) { + return getLoggableTemplate() == null + || getLoggableTemplate().match(request.getResourceRef().getTargetRef().toString()) + > 0; + } + + /** + * Indicates if the identity check (as specified by RFC1413) is enabled. + * + * @param identityCheck True if the identity check is enabled. + */ + public void setIdentityCheck(boolean identityCheck) { + this.identityCheck = identityCheck; + } + + /** + * Sets the URI template of loggable resource references. + * + * @param loggableTemplateRef The URI template of loggable resource references. + * @see #setLoggableTemplate(Template) + */ + public void setLoggableTemplate(String loggableTemplateRef) { + if (loggableTemplateRef != null) { + this.loggableTemplate = new Template(loggableTemplateRef); + } else { + this.loggableTemplate = null; + } + } + + /** + * Sets the URI template of loggable resource references. + * + * @param loggableTemplate The URI template of loggable resource references. + */ + public void setLoggableTemplate(Template loggableTemplate) { + this.loggableTemplate = loggableTemplate; + } + + /** + * Sets the name of the JDK's logger to use when logging access calls. + * + * @param name The name of the JDK's logger to use when logging access calls. + */ + public void setLoggerName(String name) { + this.loggerName = name; + } + + /** + * Sets the URI reference of the log properties. + * + * @param logPropertiesRef The URI reference of the log properties. + */ + public void setLogPropertiesRef(Reference logPropertiesRef) { + this.logPropertiesRef = logPropertiesRef; + } + + /** + * Sets the URI reference of the log properties. + * + * @param logPropertiesUri The URI reference of the log properties. + */ + public void setLogPropertiesRef(String logPropertiesUri) { + setLogPropertiesRef(new Reference(logPropertiesUri)); + } + + /** + * Sets the format to use when logging responses. The default format matches the one of IIS 6. + * + * @param responseLogFormat The format to use when logging responses. + * @see org.restlet.routing.Template for format syntax and variables. + */ + public void setResponseLogFormat(String responseLogFormat) { + this.responseLogFormat = responseLogFormat; + } + + /** + * Starts the log service by attempting to read the log properties if the {@link + * #getLogPropertiesRef()} returns a non-null URI reference. + */ + @Override + public synchronized void start() throws Exception { + super.start(); + + this.responseLogTemplate = + (getResponseLogFormat() == null) ? null : new Template(getResponseLogFormat()); + + if (getLogPropertiesRef() != null) { + Representation logProperties = + new ClientResource(getContext(), getLogPropertiesRef()).get(); + + if (logProperties != null) { + java.util.logging.LogManager.getLogManager() + .readConfiguration(logProperties.getStream()); + } + } + } } diff --git a/org.restlet/src/main/java/org/restlet/service/MetadataService.java b/org.restlet/src/main/java/org/restlet/service/MetadataService.java index f005ba23d5..0026a01222 100644 --- a/org.restlet/src/main/java/org/restlet/service/MetadataService.java +++ b/org.restlet/src/main/java/org/restlet/service/MetadataService.java @@ -1,792 +1,792 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; -import org.restlet.data.*; -import org.restlet.engine.application.MetadataExtension; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import org.restlet.data.CharacterSet; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; +import org.restlet.engine.application.MetadataExtension; /** - * Application service providing access to metadata and their associated - * extension names. The list of default mappings is documented in the - * {@link #addCommonExtensions()} method.
+ * Application service providing access to metadata and their associated extension names. The list + * of default mappings is documented in the {@link #addCommonExtensions()} method.
*
* Internally, the mappings are stored as a list of "extension, metadata" pairs. - * + * * @author Jerome Louvel */ public class MetadataService extends Service { - /** The default character set for textual representations. */ - private volatile CharacterSet defaultCharacterSet; - - /** The default encoding for representations. */ - private volatile Encoding defaultEncoding; - - /** The default language for representations. */ - private volatile Language defaultLanguage; - - /** The default media type for representations. */ - private volatile MediaType defaultMediaType; - - /** The list of mappings between extension names and metadata. */ - private final List mappings; - - /** - * Constructor. Sets the default language to {@link Language#ENGLISH_US}, the - * default encoding to {@link Encoding#IDENTITY} (no encoding) and the default - * media type to {@link MediaType#APPLICATION_OCTET_STREAM}. It also calls the - * {@link #addCommonExtensions()} method. - */ - public MetadataService() { - this.defaultCharacterSet = CharacterSet.DEFAULT; - this.defaultEncoding = Encoding.IDENTITY; - this.defaultLanguage = Language.DEFAULT; - this.defaultMediaType = MediaType.APPLICATION_OCTET_STREAM; - this.mappings = new CopyOnWriteArrayList(); - addCommonExtensions(); - } - - /** - * Adds a common list of associations from extensions to metadata.
- * - * The list of languages extensions:
- *

    - *
  • en: English
  • - *
  • es: Spanish
  • - *
  • fr: French
  • - *
- *
- * The list of character set extensions:
- *
    - *
  • ascii: US-ASCII
  • - *
- *
- * The list of media type extensions:
- *
    - *
  • ai: PostScript document
  • - *
  • atom: Atom syndication document
  • - *
  • au: AU audio file
  • - *
  • bin: Binary file
  • - *
  • bmp: Bitmap graphics
  • - *
  • class: Java bytecode
  • - *
  • css: CSS stylesheet
  • - *
  • csv: Comma-separated Values
  • - *
  • dat: Fixed-width Values
  • - *
  • dib: Device-Independent Bitmap Graphics
  • - *
  • doc: Microsoft Word document
  • - *
  • docx: Microsoft Office Word 2007 document
  • - *
  • docm: Office Word 2007 macro-enabled document
  • - *
  • dotx: Office Word 2007 template
  • - *
  • dotm: Office Word 2007 macro-enabled document template
  • - *
  • dtd: XML Document Type Definition
  • - *
  • eps: Encapsulated PostScript
  • - *
  • exe: Executable File (Microsoft Corporation)
  • - *
  • fmt: FreeMarker encoding
  • - *
  • form: Web forms (URL encoded)
  • - *
  • ftl: FreeMarker encoding
  • - *
  • gif: GIF image
  • - *
  • gwt: Java serialized object (using GWT-RPC encoder)
  • - *
  • hqx: BinHex 4 Compressed Archive (Macintosh)
  • - *
  • htm, html: HTML document
  • - *
  • ico: Windows icon (Favicon)
  • - *
  • jad: Java Application Descriptor file
  • - *
  • jar: Java Archive
  • - *
  • java: Java source code
  • - *
  • jnlp: Java Web start launch file
  • - *
  • jpe, jpeg, jpg: JPEG image
  • - *
  • js: JavaScript document
  • - *
  • jsf: Java Server Faces file
  • - *
  • json: JavaScript Object Notation document
  • - *
  • jsonsmile: JavaScript Object Notation smile document
  • - *
  • kar: Karaoke MIDI file
  • - *
  • latex: LaTeX document
  • - *
  • man: Manual file
  • - *
  • mathml: Mathml XML document
  • - *
  • mid, midi: MIDI Audio
  • - *
  • mov, qt: QuickTime video clip (Apple Computer, Inc.)
  • - *
  • mp2, mp3: MPEG Audio Stream file
  • - *
  • mp4: MPEG-4 video file
  • - *
  • mpe, mpeg, mpg: MPEG video clip
  • - *
  • n3: RDF N3 document
  • - *
  • nt: RDF N-Triples document
  • - *
  • odb: OpenDocument Database
  • - *
  • odc: OpenDocument Chart
  • - *
  • odf: OpenDocument Formula
  • - *
  • odg: OpenDocument Drawing
  • - *
  • odi: OpenDocument Image
  • - *
  • odm: OpenDocument Master Document
  • - *
  • odp: OpenDocument Presentation
  • - *
  • ods: OpenDocument Spreadsheet
  • - *
  • odt: OpenDocument Text
  • - *
  • onetoc: Microsoft Office OneNote 2007 TOC
  • - *
  • onetoc2: Office OneNote 2007 TOC
  • - *
  • otg: OpenDocument Drawing Template
  • - *
  • oth: HTML Document Template
  • - *
  • otp: OpenDocument Presentation Template
  • - *
  • ots: OpenDocument Spreadsheet Template
  • - *
  • ott: OpenDocument Text Template
  • - *
  • oxt: OpenOffice.org extension
  • - *
  • pdf: Adobe PDF document
  • - *
  • png: PNG image
  • - *
  • potm: Office PowerPoint 2007 macro-enabled presentation template
  • - *
  • potx: Office PowerPoint 2007 template
  • - *
  • ppam: Office PowerPoint 2007 add-in
  • - *
  • pps, ppt: Microsoft Powerpoint document
  • - *
  • ppsm: Office PowerPoint 2007 macro-enabled slide show
  • - *
  • ppsx: Office PowerPoint 2007 slide show
  • - *
  • pptm: Office PowerPoint 2007 macro-enabled presentation
  • - *
  • pptx: Microsoft Office PowerPoint 2007 presentation
  • - *
  • ps: PostScript document
  • - *
  • rdf: Description Framework document
  • - *
  • rnc: Relax NG Schema document, Compact syntax
  • - *
  • rng: Relax NG Schema document, XML syntax
  • - *
  • rss: RSS file
  • - *
  • rtf: Rich Text Format document
  • - *
  • sav: SPSS Data
  • - *
  • sit: StuffIt compressed archive file
  • - *
  • sldm: Office PowerPoint 2007 macro-enabled slide
  • - *
  • sldx: Office PowerPoint 2007 slide
  • - *
  • snd: Amiga sound
  • - *
  • sps: SPSS Script Syntax
  • - *
  • sta: Stata data file
  • - *
  • svg: Scalable Vector Graphics file
  • - *
  • swf: Adobe Flash file
  • - *
  • tar: Tape Archive file
  • - *
  • tex: Tex file
  • - *
  • tif, tiff: Tagged Image Format File
  • - *
  • tsv: Tab-separated Values
  • - *
  • txt: Plain text
  • - *
  • ulw: MU-LAW (US telephony format)
  • - *
  • vm: Velocity encoding
  • - *
  • vrml: Virtual Reality Modeling Language file
  • - *
  • vxml: VoiceXML source file
  • - *
  • wadl: Web Application Description Language document
  • - *
  • wav: Waveform audio
  • - *
  • wrl: Plain text VRML file
  • - *
  • xht, xhtml: XHTML document
  • - *
  • xlam: Office Excel 2007 add-in
  • - *
  • xls: Microsoft Excel document
  • - *
  • xlsb: Office Excel 2007 binary workbook
  • - *
  • xlsm: Office Excel 2007 macro-enabled workbook
  • - *
  • xlsx: Microsoft Office Excel 2007 workbook
  • - *
  • xltm: Office Excel 2007 macro-enabled workbook template
  • - *
  • xltx: Office Excel 2007 template
  • - *
  • xmi: XMI document
  • - *
  • xml: XML document
  • - *
  • xsd: W3C XML Schema document
  • - *
  • xsl, xslt: XSL Transform file
  • - *
  • xul: XML User Interface Language file
  • - *
  • yaml: YAML text format
  • - *
  • z: UNIX compressed archive file
  • - *
  • zip: Zip archive
  • - *
- */ - public void addCommonExtensions() { - List dm = new ArrayList(); - - ext(dm, "en", Language.ENGLISH); - ext(dm, "es", Language.SPANISH); - ext(dm, "fr", Language.FRENCH); - - ext(dm, "ascii", CharacterSet.US_ASCII); - - ext(dm, "ai", MediaType.APPLICATION_POSTSCRIPT); - ext(dm, "atom", MediaType.APPLICATION_ATOM); - ext(dm, "atomcat", MediaType.APPLICATION_ATOMPUB_CATEGORY); - ext(dm, "atomsvc", MediaType.APPLICATION_ATOMPUB_SERVICE); - ext(dm, "au", MediaType.AUDIO_BASIC); - ext(dm, "bin", MediaType.APPLICATION_OCTET_STREAM); - ext(dm, "bmp", MediaType.IMAGE_BMP); - ext(dm, "class", MediaType.APPLICATION_JAVA); - ext(dm, "css", MediaType.TEXT_CSS); - ext(dm, "csv", MediaType.TEXT_CSV); - ext(dm, "dat", MediaType.TEXT_DAT); - ext(dm, "dib", MediaType.IMAGE_BMP); - ext(dm, "doc", MediaType.APPLICATION_WORD); - ext(dm, "docm", MediaType.APPLICATION_MSOFFICE_DOCM); - ext(dm, "docx", MediaType.APPLICATION_MSOFFICE_DOCX); - ext(dm, "dotm", MediaType.APPLICATION_MSOFFICE_DOTM); - ext(dm, "dotx", MediaType.APPLICATION_MSOFFICE_DOTX); - ext(dm, "dtd", MediaType.APPLICATION_XML_DTD); - ext(dm, "ecore", MediaType.APPLICATION_ECORE); - ext(dm, "eps", MediaType.APPLICATION_POSTSCRIPT); - ext(dm, "exe", MediaType.APPLICATION_OCTET_STREAM); - ext(dm, "fmt", Encoding.FREEMARKER); - ext(dm, "form", MediaType.APPLICATION_WWW_FORM); - ext(dm, "ftl", Encoding.FREEMARKER, true); - ext(dm, "gif", MediaType.IMAGE_GIF); - ext(dm, "gwt", MediaType.APPLICATION_JAVA_OBJECT_GWT); - ext(dm, "hqx", MediaType.APPLICATION_MAC_BINHEX40); - ext(dm, "ico", MediaType.IMAGE_ICON); - ext(dm, "jad", MediaType.TEXT_J2ME_APP_DESCRIPTOR); - ext(dm, "jar", MediaType.APPLICATION_JAVA_ARCHIVE); - ext(dm, "java", MediaType.TEXT_PLAIN); - ext(dm, "jnlp", MediaType.APPLICATION_JNLP); - ext(dm, "jpe", MediaType.IMAGE_JPEG); - ext(dm, "jpeg", MediaType.IMAGE_JPEG); - ext(dm, "jpg", MediaType.IMAGE_JPEG); - ext(dm, "js", MediaType.APPLICATION_JAVASCRIPT); - ext(dm, "jsf", MediaType.TEXT_PLAIN); - ext(dm, "kar", MediaType.AUDIO_MIDI); - ext(dm, "latex", MediaType.APPLICATION_LATEX); - ext(dm, "latin1", CharacterSet.ISO_8859_1); - ext(dm, "mac", CharacterSet.MACINTOSH); - ext(dm, "man", MediaType.APPLICATION_TROFF_MAN); - ext(dm, "mathml", MediaType.APPLICATION_MATHML); - ext(dm, "mid", MediaType.AUDIO_MIDI); - ext(dm, "midi", MediaType.AUDIO_MIDI); - ext(dm, "mov", MediaType.VIDEO_QUICKTIME); - ext(dm, "mp2", MediaType.AUDIO_MPEG); - ext(dm, "mp3", MediaType.AUDIO_MPEG); - ext(dm, "mp4", MediaType.VIDEO_MP4); - ext(dm, "mpe", MediaType.VIDEO_MPEG); - ext(dm, "mpeg", MediaType.VIDEO_MPEG); - ext(dm, "mpg", MediaType.VIDEO_MPEG); - ext(dm, "n3", MediaType.TEXT_RDF_N3); - ext(dm, "nt", MediaType.TEXT_PLAIN); - ext(dm, "odb", MediaType.APPLICATION_OPENOFFICE_ODB); - ext(dm, "odc", MediaType.APPLICATION_OPENOFFICE_ODC); - ext(dm, "odf", MediaType.APPLICATION_OPENOFFICE_ODF); - ext(dm, "odi", MediaType.APPLICATION_OPENOFFICE_ODI); - ext(dm, "odm", MediaType.APPLICATION_OPENOFFICE_ODM); - ext(dm, "odg", MediaType.APPLICATION_OPENOFFICE_ODG); - ext(dm, "odp", MediaType.APPLICATION_OPENOFFICE_ODP); - ext(dm, "ods", MediaType.APPLICATION_OPENOFFICE_ODS); - ext(dm, "odt", MediaType.APPLICATION_OPENOFFICE_ODT); - ext(dm, "onetoc", MediaType.APPLICATION_MSOFFICE_ONETOC); - ext(dm, "onetoc2", MediaType.APPLICATION_MSOFFICE_ONETOC2); - ext(dm, "otg", MediaType.APPLICATION_OPENOFFICE_OTG); - ext(dm, "oth", MediaType.APPLICATION_OPENOFFICE_OTH); - ext(dm, "otp", MediaType.APPLICATION_OPENOFFICE_OTP); - ext(dm, "ots", MediaType.APPLICATION_OPENOFFICE_OTS); - ext(dm, "ott", MediaType.APPLICATION_OPENOFFICE_OTT); - ext(dm, "oxt", MediaType.APPLICATION_OPENOFFICE_OXT); - ext(dm, "pdf", MediaType.APPLICATION_PDF); - ext(dm, "png", MediaType.IMAGE_PNG); - ext(dm, "potx", MediaType.APPLICATION_MSOFFICE_POTX); - ext(dm, "potm", MediaType.APPLICATION_MSOFFICE_POTM); - ext(dm, "ppam", MediaType.APPLICATION_MSOFFICE_PPAM); - ext(dm, "pps", MediaType.APPLICATION_POWERPOINT); - ext(dm, "ppsm", MediaType.APPLICATION_MSOFFICE_PPSM); - ext(dm, "ppsx", MediaType.APPLICATION_MSOFFICE_PPSX); - ext(dm, "ppt", MediaType.APPLICATION_POWERPOINT); - ext(dm, "pptm", MediaType.APPLICATION_MSOFFICE_PPTM); - ext(dm, "pptx", MediaType.APPLICATION_MSOFFICE_PPTX); - ext(dm, "ps", MediaType.APPLICATION_POSTSCRIPT); - ext(dm, "qt", MediaType.VIDEO_QUICKTIME); - ext(dm, "rdf", MediaType.APPLICATION_RDF_XML); - ext(dm, "rnc", MediaType.APPLICATION_RELAXNG_COMPACT); - ext(dm, "rng", MediaType.APPLICATION_RELAXNG_XML); - ext(dm, "rss", MediaType.APPLICATION_RSS); - ext(dm, "rtf", MediaType.APPLICATION_RTF); - ext(dm, "sav", MediaType.APPLICATION_SPSS_SAV); - ext(dm, "sit", MediaType.APPLICATION_STUFFIT); - ext(dm, "sldm", MediaType.APPLICATION_MSOFFICE_SLDM); - ext(dm, "sldx", MediaType.APPLICATION_MSOFFICE_SLDX); - ext(dm, "snd", MediaType.AUDIO_BASIC); - ext(dm, "sps", MediaType.APPLICATION_SPSS_SPS); - ext(dm, "sta", MediaType.APPLICATION_STATA_STA); - ext(dm, "svg", MediaType.IMAGE_SVG); - ext(dm, "swf", MediaType.APPLICATION_FLASH); - ext(dm, "tar", MediaType.APPLICATION_TAR); - ext(dm, "tex", MediaType.APPLICATION_TEX); - ext(dm, "tif", MediaType.IMAGE_TIFF); - ext(dm, "tiff", MediaType.IMAGE_TIFF); - ext(dm, "tsv", MediaType.TEXT_TSV); - ext(dm, "ulw", MediaType.AUDIO_BASIC); - ext(dm, "utf16", CharacterSet.UTF_16); - ext(dm, "utf8", CharacterSet.UTF_8); - ext(dm, "vm", Encoding.VELOCITY); - ext(dm, "vrml", MediaType.MODEL_VRML); - ext(dm, "vxml", MediaType.APPLICATION_VOICEXML); - ext(dm, "wadl", MediaType.APPLICATION_WADL); - ext(dm, "wav", MediaType.AUDIO_WAV); - ext(dm, "win", CharacterSet.WINDOWS_1252); - ext(dm, "wrl", MediaType.MODEL_VRML); - ext(dm, "xht", MediaType.APPLICATION_XHTML); - ext(dm, "xls", MediaType.APPLICATION_EXCEL); - ext(dm, "xlsx", MediaType.APPLICATION_MSOFFICE_XLSX); - ext(dm, "xlsm", MediaType.APPLICATION_MSOFFICE_XLSM); - ext(dm, "xltx", MediaType.APPLICATION_MSOFFICE_XLTX); - ext(dm, "xltm", MediaType.APPLICATION_MSOFFICE_XLTM); - ext(dm, "xlsb", MediaType.APPLICATION_MSOFFICE_XLSB); - ext(dm, "xlam", MediaType.APPLICATION_MSOFFICE_XLAM); - ext(dm, "xmi", MediaType.APPLICATION_XMI); - ext(dm, "xsd", MediaType.APPLICATION_W3C_SCHEMA); - ext(dm, "xsl", MediaType.APPLICATION_W3C_XSLT); - ext(dm, "xslt", MediaType.APPLICATION_W3C_XSLT); - ext(dm, "xul", MediaType.APPLICATION_XUL); - ext(dm, "yaml", MediaType.APPLICATION_YAML); - ext(dm, "yaml", MediaType.TEXT_YAML); - ext(dm, "z", MediaType.APPLICATION_COMPRESS); - ext(dm, "zip", MediaType.APPLICATION_ZIP); - ext(dm, "htm", MediaType.TEXT_HTML); - ext(dm, "html", MediaType.TEXT_HTML); - ext(dm, "json", MediaType.APPLICATION_JSON); - ext(dm, "jsonsmile", MediaType.APPLICATION_JSON_SMILE); - ext(dm, "txt", MediaType.TEXT_PLAIN, true); - ext(dm, "xhtml", MediaType.APPLICATION_XHTML); - ext(dm, "xml", MediaType.TEXT_XML); - ext(dm, "xml", MediaType.APPLICATION_XML); - - // Add all those mappings - this.mappings.addAll(dm); - } - - /** - * Maps an extension to some metadata (media type, language or character set) to - * an extension. - * - * @param extension The extension name. - * @param metadata The metadata to map. - */ - public void addExtension(String extension, Metadata metadata) { - addExtension(extension, metadata, false); - } - - /** - * Maps an extension to some metadata (media type, language or character set) to - * an extension. - * - * @param extension The extension name. - * @param metadata The metadata to map. - * @param preferred indicates if this mapping is the preferred one. - */ - public void addExtension(String extension, Metadata metadata, boolean preferred) { - if (preferred) { - // Add the mapping at the beginning of the list - this.mappings.add(0, new MetadataExtension(extension, metadata)); - } else { - // Add the mapping at the end of the list - this.mappings.add(new MetadataExtension(extension, metadata)); - } - } - - /** - * clears the mappings for all extensions. - */ - public void clearExtensions() { - this.mappings.clear(); - } - - /** - * Creates a new extension mapping. - * - * @param extensions The extensions list to update. - * @param extension The extension name. - * @param metadata The associated metadata. - * @return The new extension mapping. - */ - private void ext(List extensions, String extension, Metadata metadata) { - ext(extensions, extension, metadata, false); - } - - /** - * Creates a new extension mapping. - * - * @param extensions The extensions list to update. - * @param extension The extension name. - * @param metadata The associated metadata. - * @param preferred indicates if this mapping is the preferred one. - * @return The new extension mapping. - */ - private void ext(List extensions, String extension, Metadata metadata, boolean preferred) { - if (preferred) { - // Add the mapping at the beginning of the list - extensions.add(0, new MetadataExtension(extension, metadata)); - } else { - // Add the mapping at the end of the list - extensions.add(new MetadataExtension(extension, metadata)); - } - } - - /** - * Return the ordered list of extension names mapped to character set. - * - * @return The ordered list of extension names mapped to character set. - */ - public List getAllCharacterSetExtensionNames() { - List result = new ArrayList(); - - for (MetadataExtension mapping : this.mappings) { - if ((mapping.getMetadata() instanceof CharacterSet) && !result.contains(mapping.getName())) { - result.add(mapping.getName()); - } - } - - return result; - } - - /** - * Returns all the character sets associated to this extension. It returns null - * if the extension was not declared. - * - * @param extension The extension name without any delimiter. - * @return The list of character sets associated to this extension. - */ - public List getAllCharacterSets(String extension) { - List result = null; - - if (extension != null) { - // Look for all registered convenient mapping. - for (MetadataExtension metadataExtension : this.mappings) { - if (extension.equals(metadataExtension.getName()) - && (metadataExtension.getMetadata() instanceof CharacterSet)) { - if (result == null) { - result = new ArrayList(); - } - - result.add(metadataExtension.getCharacterSet()); - } - } - } - - return result; - } - - /** - * Return the ordered list of extension names mapped to encodings. - * - * @return The ordered list of extension names mapped to encodings. - */ - public List getAllEncodingExtensionNames() { - List result = new ArrayList(); - - for (MetadataExtension mapping : this.mappings) { - if ((mapping.getMetadata() instanceof Encoding) && !result.contains(mapping.getName())) { - result.add(mapping.getName()); - } - } - - return result; - } - - /** - * Return the ordered list of extension names. - * - * @return The ordered list of extension names. - */ - public List getAllExtensionNames() { - List result = new ArrayList(); - - for (MetadataExtension mapping : this.mappings) { - if (!result.contains(mapping.getName())) { - result.add(mapping.getName()); - } - } - - return result; - } - - /** - * Return the ordered list of extension names mapped to languages. - * - * @return The ordered list of extension names mapped to languages. - */ - public List getAllLanguageExtensionNames() { - List result = new ArrayList(); - - for (MetadataExtension mapping : this.mappings) { - if ((mapping.getMetadata() instanceof Language) && !result.contains(mapping.getName())) { - result.add(mapping.getName()); - } - } - - return result; - } - - /** - * Returns all the languages associated to this extension. It returns null if - * the extension was not declared. - * - * @param extension The extension name without any delimiter. - * @return The list of languages associated to this extension. - */ - public List getAllLanguages(String extension) { - List result = null; - - if (extension != null) { - // Look for all registered convenient mapping. - for (MetadataExtension metadataExtension : this.mappings) { - if (extension.equals(metadataExtension.getName()) - && (metadataExtension.getMetadata() instanceof Language)) { - if (result == null) { - result = new ArrayList(); - } - - result.add(metadataExtension.getLanguage()); - } - } - } - - return result; - } - - /** - * Return the ordered list of extension names mapped to media types. - * - * @return The ordered list of extension names mapped to media types. - */ - public List getAllMediaTypeExtensionNames() { - List result = new ArrayList(); - - for (MetadataExtension mapping : this.mappings) { - if ((mapping.getMetadata() instanceof MediaType) && !result.contains(mapping.getName())) { - result.add(mapping.getName()); - } - } - - return result; - } - - /** - * Returns all the media types associated to this extension. It returns null if - * the extension was not declared. - * - * @param extension The extension name without any delimiter. - * @return The list of media type associated to this extension. - */ - public List getAllMediaTypes(String extension) { - List result = null; - - if (extension != null) { - // Look for all registered convenient mapping. - for (MetadataExtension metadataExtension : this.mappings) { - if (extension.equals(metadataExtension.getName()) - && (metadataExtension.getMetadata() instanceof MediaType)) { - if (result == null) { - result = new ArrayList(); - } - - result.add(metadataExtension.getMediaType()); - } - } - } - - return result; - } - - /** - * Returns all the metadata associated to this extension. It returns null if the - * extension was not declared. - * - * @param extension The extension name without any delimiter. - * @return The list of metadata associated to this extension. - */ - public List getAllMetadata(String extension) { - List result = null; - - if (extension != null) { - // Look for all registered convenient mapping. - for (MetadataExtension metadataExtension : this.mappings) { - if (extension.equals(metadataExtension.getName())) { - if (result == null) { - result = new ArrayList(); - } - - result.add(metadataExtension.getMetadata()); - } - } - } - - return result; - } - - /** - * Returns the character set associated to this extension. It returns null if - * the extension was not declared of it is corresponds to another type of - * medatata such as a media type. If several metadata are associated to the same - * extension then only the first matching metadata is returned. - * - * - * @param extension The extension name without any delimiter. - * @return The character set associated to this extension. - */ - public CharacterSet getCharacterSet(String extension) { - return getMetadata(extension, CharacterSet.class); - } - - /** - * Returns the default character set for textual representations. - * - * @return The default character set for textual representations. - */ - public CharacterSet getDefaultCharacterSet() { - return this.defaultCharacterSet; - } - - /** - * Returns the default encoding for representations. - * - * @return The default encoding for representations. - */ - public Encoding getDefaultEncoding() { - return this.defaultEncoding; - } - - /** - * Returns the default language for representations. - * - * @return The default language for representations. - */ - public Language getDefaultLanguage() { - return this.defaultLanguage; - } - - /** - * Returns the default media type for representations. - * - * @return The default media type for representations. - */ - public MediaType getDefaultMediaType() { - return this.defaultMediaType; - } - - /** - * Returns the encoding associated to this extension. It returns null if the - * extension was not declared or if it corresponds to another type of medatata - * such as a media type. If some metadata are associated to the same - * extension then only the first matching metadata is returned. - * - * @param extension The extension name without any delimiter. - * @return The encoding associated to this extension. - */ - public Encoding getEncoding(String extension) { - return getMetadata(extension, Encoding.class); - } - - /** - * Returns the first extension mapping to this metadata. - * - * @param metadata The metadata to find. - * @return The first extension mapping to this metadata. - */ - public String getExtension(Metadata metadata) { - if (metadata != null) { - // Look for the first registered convenient mapping. - for (final MetadataExtension metadataExtension : this.mappings) { - if (metadata.equals(metadataExtension.getMetadata())) { - return metadataExtension.getName(); - } - } - } - return null; - } - - /** - * Returns the language associated to this extension. It returns null if the - * extension was not declared of it is corresponds to another type of medatata - * such as a media type. If several metadata are associated to the same - * extension then only the first matching metadata is returned. - * - * @param extension The extension name without any delimiter. - * @return The language associated to this extension. - */ - public Language getLanguage(String extension) { - return getMetadata(extension, Language.class); - } - - /** - * Returns the mediatype associated to this extension. It returns null if the - * extension was not declared of it is corresponds to another type of medatata - * such as a language. If several metadata are associated to the same extension - * (ex: 'xml' for both 'text/xml' and 'application/xml') then only the first - * matching metadata is returned. - * - * - * @param extension The extension name without any delimiter. - * @return The media type associated to this extension. - */ - public MediaType getMediaType(String extension) { - return getMetadata(extension, MediaType.class); - } - - /** - * Returns the metadata associated to this extension. It returns null if the - * extension was not declared. If several metadata are associated to the same - * extension (ex: 'xml' for both 'text/xml' and 'application/xml') then only the - * first matching metadata is returned. - * - * @param extension The extension name without any delimiter. - * @return The metadata associated to this extension. - */ - public Metadata getMetadata(String extension) { - if (extension != null) { - // Look for the first registered convenient mapping. - for (final MetadataExtension metadataExtension : this.mappings) { - if (extension.equals(metadataExtension.getName())) { - return metadataExtension.getMetadata(); - } - } - } - - return null; - } - - /** - * Returns the metadata associated to this extension. It returns null if the - * extension was not declared or is not of the target metadata type. - * - * @param - * @param extension The extension name without any delimiter. - * @param metadataType The target metadata type. - * @return The metadata associated to this extension. - */ - public T getMetadata(String extension, Class metadataType) { - Metadata metadata = getMetadata(extension); - - if (metadata != null && metadataType.isAssignableFrom(metadata.getClass())) { - return metadataType.cast(metadata); - } - - return null; - } - - /** - * Sets the default character set for local representations. - * - * @param defaultCharacterSet The default character set for local - * representations. - */ - public void setDefaultCharacterSet(CharacterSet defaultCharacterSet) { - this.defaultCharacterSet = defaultCharacterSet; - } - - /** - * Sets the default encoding for local representations. - * - * @param defaultEncoding The default encoding for local representations. - */ - public void setDefaultEncoding(Encoding defaultEncoding) { - this.defaultEncoding = defaultEncoding; - } - - /** - * Sets the default language for local representations. - * - * @param defaultLanguage The default language for local representations. - */ - public void setDefaultLanguage(Language defaultLanguage) { - this.defaultLanguage = defaultLanguage; - } - - /** - * Sets the default media type for local representations. - * - * @param defaultMediaType The default media type for local representations. - */ - public void setDefaultMediaType(MediaType defaultMediaType) { - this.defaultMediaType = defaultMediaType; - } - + /** The default character set for textual representations. */ + private volatile CharacterSet defaultCharacterSet; + + /** The default encoding for representations. */ + private volatile Encoding defaultEncoding; + + /** The default language for representations. */ + private volatile Language defaultLanguage; + + /** The default media type for representations. */ + private volatile MediaType defaultMediaType; + + /** The list of mappings between extension names and metadata. */ + private final List mappings; + + /** + * Constructor. Sets the default language to {@link Language#ENGLISH_US}, the default encoding + * to {@link Encoding#IDENTITY} (no encoding) and the default media type to {@link + * MediaType#APPLICATION_OCTET_STREAM}. It also calls the {@link #addCommonExtensions()} method. + */ + public MetadataService() { + this.defaultCharacterSet = CharacterSet.DEFAULT; + this.defaultEncoding = Encoding.IDENTITY; + this.defaultLanguage = Language.DEFAULT; + this.defaultMediaType = MediaType.APPLICATION_OCTET_STREAM; + this.mappings = new CopyOnWriteArrayList<>(); + addCommonExtensions(); + } + + /** + * Adds a common list of associations from extensions to metadata.
+ * The list of languages extensions:
+ * + *
    + *
  • en: English + *
  • es: Spanish + *
  • fr: French + *
+ * + *
+ * The list of character set extensions:
+ * + *
    + *
  • ascii: US-ASCII + *
+ * + *
+ * The list of media type extensions:
+ * + *
    + *
  • ai: PostScript document + *
  • atom: Atom syndication document + *
  • au: AU audio file + *
  • bin: Binary file + *
  • bmp: Bitmap graphics + *
  • class: Java bytecode + *
  • css: CSS stylesheet + *
  • csv: Comma-separated Values + *
  • dat: Fixed-width Values + *
  • dib: Device-Independent Bitmap Graphics + *
  • doc: Microsoft Word document + *
  • docx: Microsoft Office Word 2007 document + *
  • docm: Office Word 2007 macro-enabled document + *
  • dotx: Office Word 2007 template + *
  • dotm: Office Word 2007 macro-enabled document template + *
  • dtd: XML Document Type Definition + *
  • eps: Encapsulated PostScript + *
  • exe: Executable File (Microsoft Corporation) + *
  • fmt: FreeMarker encoding + *
  • form: Web forms (URL encoded) + *
  • ftl: FreeMarker encoding + *
  • gif: GIF image + *
  • gwt: Java serialized object (using GWT-RPC encoder) + *
  • hqx: BinHex 4 Compressed Archive (Macintosh) + *
  • htm, html: HTML document + *
  • ico: Windows icon (Favicon) + *
  • jad: Java Application Descriptor file + *
  • jar: Java Archive + *
  • java: Java source code + *
  • jnlp: Java Web start launch file + *
  • jpe, jpeg, jpg: JPEG image + *
  • js: JavaScript document + *
  • jsf: Java Server Faces file + *
  • json: JavaScript Object Notation document + *
  • jsonsmile: JavaScript Object Notation smile document + *
  • kar: Karaoke MIDI file + *
  • latex: LaTeX document + *
  • man: Manual file + *
  • mathml: Mathml XML document + *
  • mid, midi: MIDI Audio + *
  • mov, qt: QuickTime video clip (Apple Computer, Inc.) + *
  • mp2, mp3: MPEG Audio Stream file + *
  • mp4: MPEG-4 video file + *
  • mpe, mpeg, mpg: MPEG video clip + *
  • n3: RDF N3 document + *
  • nt: RDF N-Triples document + *
  • odb: OpenDocument Database + *
  • odc: OpenDocument Chart + *
  • odf: OpenDocument Formula + *
  • odg: OpenDocument Drawing + *
  • odi: OpenDocument Image + *
  • odm: OpenDocument Master Document + *
  • odp: OpenDocument Presentation + *
  • ods: OpenDocument Spreadsheet + *
  • odt: OpenDocument Text + *
  • onetoc: Microsoft Office OneNote 2007 TOC + *
  • onetoc2: Office OneNote 2007 TOC + *
  • otg: OpenDocument Drawing Template + *
  • oth: HTML Document Template + *
  • otp: OpenDocument Presentation Template + *
  • ots: OpenDocument Spreadsheet Template + *
  • ott: OpenDocument Text Template + *
  • oxt: OpenOffice.org extension + *
  • pdf: Adobe PDF document + *
  • png: PNG image + *
  • potm: Office PowerPoint 2007 macro-enabled presentation template + *
  • potx: Office PowerPoint 2007 template + *
  • ppam: Office PowerPoint 2007 add-in + *
  • pps, ppt: Microsoft Powerpoint document + *
  • ppsm: Office PowerPoint 2007 macro-enabled slide show + *
  • ppsx: Office PowerPoint 2007 slide show + *
  • pptm: Office PowerPoint 2007 macro-enabled presentation + *
  • pptx: Microsoft Office PowerPoint 2007 presentation + *
  • ps: PostScript document + *
  • rdf: Description Framework document + *
  • rnc: Relax NG Schema document, Compact syntax + *
  • rng: Relax NG Schema document, XML syntax + *
  • rss: RSS file + *
  • rtf: Rich Text Format document + *
  • sav: SPSS Data + *
  • sit: StuffIt compressed archive file + *
  • sldm: Office PowerPoint 2007 macro-enabled slide + *
  • sldx: Office PowerPoint 2007 slide + *
  • snd: Amiga sound + *
  • sps: SPSS Script Syntax + *
  • sta: Stata data file + *
  • svg: Scalable Vector Graphics file + *
  • swf: Adobe Flash file + *
  • tar: Tape Archive file + *
  • tex: Tex file + *
  • tif, tiff: Tagged Image Format File + *
  • tsv: Tab-separated Values + *
  • txt: Plain text + *
  • ulw: MU-LAW (US telephony format) + *
  • vm: Velocity encoding + *
  • vrml: Virtual Reality Modeling Language file + *
  • vxml: VoiceXML source file + *
  • wadl: Web Application Description Language document + *
  • wav: Waveform audio + *
  • wrl: Plain text VRML file + *
  • xht, xhtml: XHTML document + *
  • xlam: Office Excel 2007 add-in + *
  • xls: Microsoft Excel document + *
  • xlsb: Office Excel 2007 binary workbook + *
  • xlsm: Office Excel 2007 macro-enabled workbook + *
  • xlsx: Microsoft Office Excel 2007 workbook + *
  • xltm: Office Excel 2007 macro-enabled workbook template + *
  • xltx: Office Excel 2007 template + *
  • xmi: XMI document + *
  • xml: XML document + *
  • xsd: W3C XML Schema document + *
  • xsl, xslt: XSL Transform file + *
  • xul: XML User Interface Language file + *
  • yaml: YAML text format + *
  • z: UNIX compressed archive file + *
  • zip: Zip archive + *
+ */ + public void addCommonExtensions() { + List dm = new ArrayList(); + + ext(dm, "en", Language.ENGLISH); + ext(dm, "es", Language.SPANISH); + ext(dm, "fr", Language.FRENCH); + + ext(dm, "ascii", CharacterSet.US_ASCII); + + ext(dm, "ai", MediaType.APPLICATION_POSTSCRIPT); + ext(dm, "atom", MediaType.APPLICATION_ATOM); + ext(dm, "atomcat", MediaType.APPLICATION_ATOMPUB_CATEGORY); + ext(dm, "atomsvc", MediaType.APPLICATION_ATOMPUB_SERVICE); + ext(dm, "au", MediaType.AUDIO_BASIC); + ext(dm, "bin", MediaType.APPLICATION_OCTET_STREAM); + ext(dm, "bmp", MediaType.IMAGE_BMP); + ext(dm, "class", MediaType.APPLICATION_JAVA); + ext(dm, "css", MediaType.TEXT_CSS); + ext(dm, "csv", MediaType.TEXT_CSV); + ext(dm, "dat", MediaType.TEXT_DAT); + ext(dm, "dib", MediaType.IMAGE_BMP); + ext(dm, "doc", MediaType.APPLICATION_WORD); + ext(dm, "docm", MediaType.APPLICATION_MSOFFICE_DOCM); + ext(dm, "docx", MediaType.APPLICATION_MSOFFICE_DOCX); + ext(dm, "dotm", MediaType.APPLICATION_MSOFFICE_DOTM); + ext(dm, "dotx", MediaType.APPLICATION_MSOFFICE_DOTX); + ext(dm, "dtd", MediaType.APPLICATION_XML_DTD); + ext(dm, "ecore", MediaType.APPLICATION_ECORE); + ext(dm, "eps", MediaType.APPLICATION_POSTSCRIPT); + ext(dm, "exe", MediaType.APPLICATION_OCTET_STREAM); + ext(dm, "fmt", Encoding.FREEMARKER); + ext(dm, "form", MediaType.APPLICATION_WWW_FORM); + ext(dm, "ftl", Encoding.FREEMARKER, true); + ext(dm, "gif", MediaType.IMAGE_GIF); + ext(dm, "gwt", MediaType.APPLICATION_JAVA_OBJECT_GWT); + ext(dm, "hqx", MediaType.APPLICATION_MAC_BINHEX40); + ext(dm, "ico", MediaType.IMAGE_ICON); + ext(dm, "jad", MediaType.TEXT_J2ME_APP_DESCRIPTOR); + ext(dm, "jar", MediaType.APPLICATION_JAVA_ARCHIVE); + ext(dm, "java", MediaType.TEXT_PLAIN); + ext(dm, "jnlp", MediaType.APPLICATION_JNLP); + ext(dm, "jpe", MediaType.IMAGE_JPEG); + ext(dm, "jpeg", MediaType.IMAGE_JPEG); + ext(dm, "jpg", MediaType.IMAGE_JPEG); + ext(dm, "js", MediaType.APPLICATION_JAVASCRIPT); + ext(dm, "jsf", MediaType.TEXT_PLAIN); + ext(dm, "kar", MediaType.AUDIO_MIDI); + ext(dm, "latex", MediaType.APPLICATION_LATEX); + ext(dm, "latin1", CharacterSet.ISO_8859_1); + ext(dm, "mac", CharacterSet.MACINTOSH); + ext(dm, "man", MediaType.APPLICATION_TROFF_MAN); + ext(dm, "mathml", MediaType.APPLICATION_MATHML); + ext(dm, "mid", MediaType.AUDIO_MIDI); + ext(dm, "midi", MediaType.AUDIO_MIDI); + ext(dm, "mov", MediaType.VIDEO_QUICKTIME); + ext(dm, "mp2", MediaType.AUDIO_MPEG); + ext(dm, "mp3", MediaType.AUDIO_MPEG); + ext(dm, "mp4", MediaType.VIDEO_MP4); + ext(dm, "mpe", MediaType.VIDEO_MPEG); + ext(dm, "mpeg", MediaType.VIDEO_MPEG); + ext(dm, "mpg", MediaType.VIDEO_MPEG); + ext(dm, "n3", MediaType.TEXT_RDF_N3); + ext(dm, "nt", MediaType.TEXT_PLAIN); + ext(dm, "odb", MediaType.APPLICATION_OPENOFFICE_ODB); + ext(dm, "odc", MediaType.APPLICATION_OPENOFFICE_ODC); + ext(dm, "odf", MediaType.APPLICATION_OPENOFFICE_ODF); + ext(dm, "odi", MediaType.APPLICATION_OPENOFFICE_ODI); + ext(dm, "odm", MediaType.APPLICATION_OPENOFFICE_ODM); + ext(dm, "odg", MediaType.APPLICATION_OPENOFFICE_ODG); + ext(dm, "odp", MediaType.APPLICATION_OPENOFFICE_ODP); + ext(dm, "ods", MediaType.APPLICATION_OPENOFFICE_ODS); + ext(dm, "odt", MediaType.APPLICATION_OPENOFFICE_ODT); + ext(dm, "onetoc", MediaType.APPLICATION_MSOFFICE_ONETOC); + ext(dm, "onetoc2", MediaType.APPLICATION_MSOFFICE_ONETOC2); + ext(dm, "otg", MediaType.APPLICATION_OPENOFFICE_OTG); + ext(dm, "oth", MediaType.APPLICATION_OPENOFFICE_OTH); + ext(dm, "otp", MediaType.APPLICATION_OPENOFFICE_OTP); + ext(dm, "ots", MediaType.APPLICATION_OPENOFFICE_OTS); + ext(dm, "ott", MediaType.APPLICATION_OPENOFFICE_OTT); + ext(dm, "oxt", MediaType.APPLICATION_OPENOFFICE_OXT); + ext(dm, "pdf", MediaType.APPLICATION_PDF); + ext(dm, "png", MediaType.IMAGE_PNG); + ext(dm, "potx", MediaType.APPLICATION_MSOFFICE_POTX); + ext(dm, "potm", MediaType.APPLICATION_MSOFFICE_POTM); + ext(dm, "ppam", MediaType.APPLICATION_MSOFFICE_PPAM); + ext(dm, "pps", MediaType.APPLICATION_POWERPOINT); + ext(dm, "ppsm", MediaType.APPLICATION_MSOFFICE_PPSM); + ext(dm, "ppsx", MediaType.APPLICATION_MSOFFICE_PPSX); + ext(dm, "ppt", MediaType.APPLICATION_POWERPOINT); + ext(dm, "pptm", MediaType.APPLICATION_MSOFFICE_PPTM); + ext(dm, "pptx", MediaType.APPLICATION_MSOFFICE_PPTX); + ext(dm, "ps", MediaType.APPLICATION_POSTSCRIPT); + ext(dm, "qt", MediaType.VIDEO_QUICKTIME); + ext(dm, "rdf", MediaType.APPLICATION_RDF_XML); + ext(dm, "rnc", MediaType.APPLICATION_RELAXNG_COMPACT); + ext(dm, "rng", MediaType.APPLICATION_RELAXNG_XML); + ext(dm, "rss", MediaType.APPLICATION_RSS); + ext(dm, "rtf", MediaType.APPLICATION_RTF); + ext(dm, "sav", MediaType.APPLICATION_SPSS_SAV); + ext(dm, "sit", MediaType.APPLICATION_STUFFIT); + ext(dm, "sldm", MediaType.APPLICATION_MSOFFICE_SLDM); + ext(dm, "sldx", MediaType.APPLICATION_MSOFFICE_SLDX); + ext(dm, "snd", MediaType.AUDIO_BASIC); + ext(dm, "sps", MediaType.APPLICATION_SPSS_SPS); + ext(dm, "sta", MediaType.APPLICATION_STATA_STA); + ext(dm, "svg", MediaType.IMAGE_SVG); + ext(dm, "swf", MediaType.APPLICATION_FLASH); + ext(dm, "tar", MediaType.APPLICATION_TAR); + ext(dm, "tex", MediaType.APPLICATION_TEX); + ext(dm, "tif", MediaType.IMAGE_TIFF); + ext(dm, "tiff", MediaType.IMAGE_TIFF); + ext(dm, "tsv", MediaType.TEXT_TSV); + ext(dm, "ulw", MediaType.AUDIO_BASIC); + ext(dm, "utf16", CharacterSet.UTF_16); + ext(dm, "utf8", CharacterSet.UTF_8); + ext(dm, "vm", Encoding.VELOCITY); + ext(dm, "vrml", MediaType.MODEL_VRML); + ext(dm, "vxml", MediaType.APPLICATION_VOICEXML); + ext(dm, "wadl", MediaType.APPLICATION_WADL); + ext(dm, "wav", MediaType.AUDIO_WAV); + ext(dm, "win", CharacterSet.WINDOWS_1252); + ext(dm, "wrl", MediaType.MODEL_VRML); + ext(dm, "xht", MediaType.APPLICATION_XHTML); + ext(dm, "xls", MediaType.APPLICATION_EXCEL); + ext(dm, "xlsx", MediaType.APPLICATION_MSOFFICE_XLSX); + ext(dm, "xlsm", MediaType.APPLICATION_MSOFFICE_XLSM); + ext(dm, "xltx", MediaType.APPLICATION_MSOFFICE_XLTX); + ext(dm, "xltm", MediaType.APPLICATION_MSOFFICE_XLTM); + ext(dm, "xlsb", MediaType.APPLICATION_MSOFFICE_XLSB); + ext(dm, "xlam", MediaType.APPLICATION_MSOFFICE_XLAM); + ext(dm, "xmi", MediaType.APPLICATION_XMI); + ext(dm, "xsd", MediaType.APPLICATION_W3C_SCHEMA); + ext(dm, "xsl", MediaType.APPLICATION_W3C_XSLT); + ext(dm, "xslt", MediaType.APPLICATION_W3C_XSLT); + ext(dm, "xul", MediaType.APPLICATION_XUL); + ext(dm, "yaml", MediaType.APPLICATION_YAML); + ext(dm, "yaml", MediaType.TEXT_YAML); + ext(dm, "z", MediaType.APPLICATION_COMPRESS); + ext(dm, "zip", MediaType.APPLICATION_ZIP); + ext(dm, "htm", MediaType.TEXT_HTML); + ext(dm, "html", MediaType.TEXT_HTML); + ext(dm, "json", MediaType.APPLICATION_JSON); + ext(dm, "jsonsmile", MediaType.APPLICATION_JSON_SMILE); + ext(dm, "txt", MediaType.TEXT_PLAIN, true); + ext(dm, "xhtml", MediaType.APPLICATION_XHTML); + ext(dm, "xml", MediaType.TEXT_XML); + ext(dm, "xml", MediaType.APPLICATION_XML); + + // Add all those mappings + this.mappings.addAll(dm); + } + + /** + * Maps an extension to some metadata (media type, language or character set) to an extension. + * + * @param extension The extension name. + * @param metadata The metadata to map. + */ + public void addExtension(String extension, Metadata metadata) { + addExtension(extension, metadata, false); + } + + /** + * Maps an extension to some metadata (media type, language or character set) to an extension. + * + * @param extension The extension name. + * @param metadata The metadata to map. + * @param preferred indicates if this mapping is the preferred one. + */ + public void addExtension(String extension, Metadata metadata, boolean preferred) { + if (preferred) { + // Add the mapping at the beginning of the list + this.mappings.add(0, new MetadataExtension(extension, metadata)); + } else { + // Add the mapping at the end of the list + this.mappings.add(new MetadataExtension(extension, metadata)); + } + } + + /** clears the mappings for all extensions. */ + public void clearExtensions() { + this.mappings.clear(); + } + + /** + * Creates a new extension mapping. + * + * @param extensions The extensions list to update. + * @param extension The extension name. + * @param metadata The associated metadata. + */ + private void ext(List extensions, String extension, Metadata metadata) { + ext(extensions, extension, metadata, false); + } + + /** + * Creates a new extension mapping. + * + * @param extensions The extensions list to update. + * @param extension The extension name. + * @param metadata The associated metadata. + * @param preferred indicates if this mapping is the preferred one. + */ + private void ext( + List extensions, + String extension, + Metadata metadata, + boolean preferred) { + if (preferred) { + // Add the mapping at the beginning of the list + extensions.add(0, new MetadataExtension(extension, metadata)); + } else { + // Add the mapping at the end of the list + extensions.add(new MetadataExtension(extension, metadata)); + } + } + + /** + * Return the ordered list of extension names mapped to a character set. + * + * @return The ordered list of extension names mapped to a character set. + */ + public List getAllCharacterSetExtensionNames() { + List result = new ArrayList(); + + for (MetadataExtension mapping : this.mappings) { + if ((mapping.getMetadata() instanceof CharacterSet) + && !result.contains(mapping.getName())) { + result.add(mapping.getName()); + } + } + + return result; + } + + /** + * Returns all the character sets associated with this extension. It returns null if the + * extension was not declared. + * + * @param extension The extension name without any delimiter. + * @return The list of character sets associated with this extension. + */ + public List getAllCharacterSets(String extension) { + List result = null; + + if (extension != null) { + // Look for all registered convenient mapping. + for (MetadataExtension metadataExtension : this.mappings) { + if (extension.equals(metadataExtension.getName()) + && (metadataExtension.getMetadata() instanceof CharacterSet)) { + if (result == null) { + result = new ArrayList<>(); + } + + result.add(metadataExtension.getCharacterSet()); + } + } + } + + return result; + } + + /** + * Return the ordered list of extension names mapped to encodings. + * + * @return The ordered list of extension names mapped to encodings. + */ + public List getAllEncodingExtensionNames() { + List result = new ArrayList(); + + for (MetadataExtension mapping : this.mappings) { + if ((mapping.getMetadata() instanceof Encoding) + && !result.contains(mapping.getName())) { + result.add(mapping.getName()); + } + } + + return result; + } + + /** + * Return the ordered list of extension names. + * + * @return The ordered list of extension names. + */ + public List getAllExtensionNames() { + List result = new ArrayList(); + + for (MetadataExtension mapping : this.mappings) { + if (!result.contains(mapping.getName())) { + result.add(mapping.getName()); + } + } + + return result; + } + + /** + * Return the ordered list of extension names mapped to languages. + * + * @return The ordered list of extension names mapped to languages. + */ + public List getAllLanguageExtensionNames() { + List result = new ArrayList(); + + for (MetadataExtension mapping : this.mappings) { + if ((mapping.getMetadata() instanceof Language) + && !result.contains(mapping.getName())) { + result.add(mapping.getName()); + } + } + + return result; + } + + /** + * Returns all the languages associated with this extension. It returns null if the extension + * was not declared. + * + * @param extension The extension name without any delimiter. + * @return The list of languages associated with this extension. + */ + public List getAllLanguages(String extension) { + List result = null; + + if (extension != null) { + // Look for all registered convenient mapping. + for (MetadataExtension metadataExtension : this.mappings) { + if (extension.equals(metadataExtension.getName()) + && (metadataExtension.getMetadata() instanceof Language)) { + if (result == null) { + result = new ArrayList<>(); + } + + result.add(metadataExtension.getLanguage()); + } + } + } + + return result; + } + + /** + * Return the ordered list of extension names mapped to media types. + * + * @return The ordered list of extension names mapped to media types. + */ + public List getAllMediaTypeExtensionNames() { + List result = new ArrayList<>(); + + for (MetadataExtension mapping : this.mappings) { + if ((mapping.getMetadata() instanceof MediaType) + && !result.contains(mapping.getName())) { + result.add(mapping.getName()); + } + } + + return result; + } + + /** + * Returns all the media types associated with this extension. It returns null if the extension + * was not declared. + * + * @param extension The extension name without any delimiter. + * @return The list of media type associated with this extension. + */ + public List getAllMediaTypes(String extension) { + List result = null; + + if (extension != null) { + // Look for all registered convenient mapping. + for (MetadataExtension metadataExtension : this.mappings) { + if (extension.equals(metadataExtension.getName()) + && (metadataExtension.getMetadata() instanceof MediaType)) { + if (result == null) { + result = new ArrayList<>(); + } + + result.add(metadataExtension.getMediaType()); + } + } + } + + return result; + } + + /** + * Returns all the metadata associated with this extension. It returns null if the extension was + * not declared. + * + * @param extension The extension name without any delimiter. + * @return The list of metadata associated with this extension. + */ + public List getAllMetadata(String extension) { + List result = null; + + if (extension != null) { + // Look for all registered convenient mapping. + for (MetadataExtension metadataExtension : this.mappings) { + if (extension.equals(metadataExtension.getName())) { + if (result == null) { + result = new ArrayList(); + } + + result.add(metadataExtension.getMetadata()); + } + } + } + + return result; + } + + /** + * Returns the character set associated with this extension. It returns null if the extension + * was not declared of it is corresponding to another type of metadata such as a media type. If + * some metadata is associated with the same extension, then only the first matching metadata is + * returned. + * + * @param extension The extension name without any delimiter. + * @return The character set associated with this extension. + */ + public CharacterSet getCharacterSet(String extension) { + return getMetadata(extension, CharacterSet.class); + } + + /** + * Returns the default character set for textual representations. + * + * @return The default character set for textual representations. + */ + public CharacterSet getDefaultCharacterSet() { + return this.defaultCharacterSet; + } + + /** + * Returns the default encoding for representations. + * + * @return The default encoding for representations. + */ + public Encoding getDefaultEncoding() { + return this.defaultEncoding; + } + + /** + * Returns the default language for representations. + * + * @return The default language for representations. + */ + public Language getDefaultLanguage() { + return this.defaultLanguage; + } + + /** + * Returns the default media type for representations. + * + * @return The default media type for representations. + */ + public MediaType getDefaultMediaType() { + return this.defaultMediaType; + } + + /** + * Returns the encoding associated with this extension. It returns null if the extension was not + * declared or if it corresponds to another type of metadata such as a media type. If some + * metadata is associated with the same extension, then only the first matching metadata is + * returned. + * + * @param extension The extension name without any delimiter. + * @return The encoding associated with this extension. + */ + public Encoding getEncoding(String extension) { + return getMetadata(extension, Encoding.class); + } + + /** + * Returns the first extension mapping to this metadata. + * + * @param metadata The metadata to find. + * @return The first extension mapping to this metadata. + */ + public String getExtension(Metadata metadata) { + if (metadata != null) { + // Look for the first registered convenient mapping. + for (final MetadataExtension metadataExtension : this.mappings) { + if (metadata.equals(metadataExtension.getMetadata())) { + return metadataExtension.getName(); + } + } + } + return null; + } + + /** + * Returns the language associated with this extension. It returns null if the extension was not + * declared of it is corresponding to another type of metadata such as a media type. If some + * metadata is associated with the same extension, then only the first matching metadata is + * returned. + * + * @param extension The extension name without any delimiter. + * @return The language associated with this extension. + */ + public Language getLanguage(String extension) { + return getMetadata(extension, Language.class); + } + + /** + * Returns the media type associated with this extension. It returns null if the extension was + * not declared of it is corresponding to another type of metadata such as a language. If some + * metadata is associated with the same extension (ex: 'xml' for both 'text/xml' and + * 'application/xml'), then only the first matching metadata is returned. + * + * @param extension The extension name without any delimiter. + * @return The media type associated with this extension. + */ + public MediaType getMediaType(String extension) { + return getMetadata(extension, MediaType.class); + } + + /** + * Returns the metadata associated with this extension. It returns null if the extension was not + * declared. If some metadata is associated with the same extension (ex: 'xml' for both + * 'text/xml' and 'application/xml'), then only the first matching metadata is returned. + * + * @param extension The extension name without any delimiter. + * @return The metadata associated with this extension. + */ + public Metadata getMetadata(String extension) { + if (extension != null) { + // Look for the first registered convenient mapping. + for (final MetadataExtension metadataExtension : this.mappings) { + if (extension.equals(metadataExtension.getName())) { + return metadataExtension.getMetadata(); + } + } + } + + return null; + } + + /** + * Returns the metadata associated with this extension. It returns null if the extension was not + * declared or is not of the target metadata type. + * + * @param + * @param extension The extension name without any delimiter. + * @param metadataType The target metadata type. + * @return The metadata associated with this extension. + */ + public T getMetadata(String extension, Class metadataType) { + Metadata metadata = getMetadata(extension); + + if (metadata != null && metadataType.isAssignableFrom(metadata.getClass())) { + return metadataType.cast(metadata); + } + + return null; + } + + /** + * Sets the default character set for local representations. + * + * @param defaultCharacterSet The default character set for local representations. + */ + public void setDefaultCharacterSet(CharacterSet defaultCharacterSet) { + this.defaultCharacterSet = defaultCharacterSet; + } + + /** + * Sets the default encoding for local representations. + * + * @param defaultEncoding The default encoding for local representations. + */ + public void setDefaultEncoding(Encoding defaultEncoding) { + this.defaultEncoding = defaultEncoding; + } + + /** + * Sets the default language for local representations. + * + * @param defaultLanguage The default language for local representations. + */ + public void setDefaultLanguage(Language defaultLanguage) { + this.defaultLanguage = defaultLanguage; + } + + /** + * Sets the default media type for local representations. + * + * @param defaultMediaType The default media type for local representations. + */ + public void setDefaultMediaType(MediaType defaultMediaType) { + this.defaultMediaType = defaultMediaType; + } } diff --git a/org.restlet/src/main/java/org/restlet/service/RangeService.java b/org.restlet/src/main/java/org/restlet/service/RangeService.java index 816bf2ffe4..5f141016c5 100644 --- a/org.restlet/src/main/java/org/restlet/service/RangeService.java +++ b/org.restlet/src/main/java/org/restlet/service/RangeService.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; import org.restlet.Context; @@ -14,35 +13,31 @@ import org.restlet.routing.Filter; /** - * Application service automatically exposes ranges of response entities. This - * allows resources to not care for requested ranges and return full - * representations that will then be transparently wrapped in partial - * representations by this service, allowing the client to benefit from partial + * Application service automatically exposes ranges of response entities. This allows resources to + * not care for requested ranges and return full representations that will then be transparently + * wrapped in partial representations by this service, allowing the client to benefit from partial * downloads. - * + * * @author Jerome Louvel */ public class RangeService extends Service { - /** - * Constructor. - */ - public RangeService() { - super(); - } - - /** - * Constructor. - * - * @param enabled True if the service has been enabled. - */ - public RangeService(boolean enabled) { - super(enabled); - } + /** Constructor. */ + public RangeService() { + super(); + } - @Override - public Filter createInboundFilter(Context context) { - return new RangeFilter(context); - } + /** + * Constructor. + * + * @param enabled True if the service has been enabled. + */ + public RangeService(boolean enabled) { + super(enabled); + } + @Override + public Filter createInboundFilter(Context context) { + return new RangeFilter(context); + } } diff --git a/org.restlet/src/main/java/org/restlet/service/Service.java b/org.restlet/src/main/java/org/restlet/service/Service.java index bd15bd04c0..cdfa1c4a11 100644 --- a/org.restlet/src/main/java/org/restlet/service/Service.java +++ b/org.restlet/src/main/java/org/restlet/service/Service.java @@ -1,141 +1,136 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; import org.restlet.Context; /** - * Generic service associated to a component or an application. The life cycle - * of a service is tightly related to the one of the associated component or - * application.
+ * Generic service associated with a component or an application. The life cycle of a service is + * tightly related to the one of the associated component or application.
*
- * If you want to use a specific service, you can always disable it before it is - * actually started via the {@link #setEnabled(boolean)} method. - * + * If you want to use a specific service, you can always disable it before it is actually started + * via the {@link #setEnabled(boolean)} method. + * * @author Jerome Louvel */ public abstract class Service { - /** The context. */ - private volatile Context context; - - /** Indicates if the service has been enabled. */ - private volatile boolean enabled; - - /** Indicates if the service was started. */ - private volatile boolean started; - - /** - * Constructor. Enables the service by default. - */ - public Service() { - this(true); - } - - /** - * Constructor. - * - * @param enabled True if the service has been enabled. - */ - public Service(boolean enabled) { - this.context = null; - this.enabled = enabled; - } - - /** - * Create the filter that should be invoked for incoming calls. - * - * @param context The current context. - * @return The new filter or null. - */ - public org.restlet.routing.Filter createInboundFilter(org.restlet.Context context) { - return null; - } - - /** - * Create the filter that should be invoked for outgoing calls. - * - * @param context The current context. - * @return The new filter or null. - * @see Context#getClientDispatcher() - */ - public org.restlet.routing.Filter createOutboundFilter(org.restlet.Context context) { - return null; - } - - /** - * Returns the context. - * - * @return The context. - */ - public Context getContext() { - return this.context; - } - - /** - * Indicates if the service should be enabled. - * - * @return True if the service should be enabled. - */ - public boolean isEnabled() { - return this.enabled; - } - - /** - * Indicates if the service is started. - * - * @return True if the service is started. - */ - public boolean isStarted() { - return this.started; - } - - /** - * Indicates if the service is stopped. - * - * @return True if the service is stopped. - */ - public boolean isStopped() { - return !this.started; - } - - /** - * Sets the context. - * - * @param context The context. - */ - public void setContext(Context context) { - this.context = context; - } - - /** - * Indicates if the service should be enabled. - * - * @param enabled True if the service should be enabled. - */ - public synchronized void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - /** Starts the Restlet. */ - public synchronized void start() throws Exception { - if (isEnabled()) { - this.started = true; - } - } - - /** Stops the Restlet. */ - public synchronized void stop() throws Exception { - if (isEnabled()) { - this.started = false; - } - } - + /** The context. */ + private volatile Context context; + + /** Indicates if the service has been enabled. */ + private volatile boolean enabled; + + /** Indicates if the service was started. */ + private volatile boolean started; + + /** Constructor. Enables the service by default. */ + protected Service() { + this(true); + } + + /** + * Constructor. + * + * @param enabled True if the service has been enabled. + */ + protected Service(boolean enabled) { + this.context = null; + this.enabled = enabled; + } + + /** + * Create the filter that should be invoked for incoming calls. + * + * @param context The current context. + * @return The new filter or null. + */ + public org.restlet.routing.Filter createInboundFilter(org.restlet.Context context) { + return null; + } + + /** + * Create the filter that should be invoked for outgoing calls. + * + * @param context The current context. + * @return The new filter or null. + * @see Context#getClientDispatcher() + */ + public org.restlet.routing.Filter createOutboundFilter(org.restlet.Context context) { + return null; + } + + /** + * Returns the context. + * + * @return The context. + */ + public Context getContext() { + return this.context; + } + + /** + * Indicates if the service should be enabled. + * + * @return True if the service should be enabled. + */ + public boolean isEnabled() { + return this.enabled; + } + + /** + * Indicates if the service is started. + * + * @return True if the service is started. + */ + public boolean isStarted() { + return this.started; + } + + /** + * Indicates if the service is stopped. + * + * @return True if the service is stopped. + */ + public boolean isStopped() { + return !this.started; + } + + /** + * Sets the context. + * + * @param context The context. + */ + public void setContext(Context context) { + this.context = context; + } + + /** + * Indicates if the service should be enabled. + * + * @param enabled True if the service should be enabled. + */ + public synchronized void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** Starts the Restlet. */ + public synchronized void start() throws Exception { + if (isEnabled()) { + this.started = true; + } + } + + /** Stops the Restlet. */ + public synchronized void stop() throws Exception { + if (isEnabled()) { + this.started = false; + } + } } diff --git a/org.restlet/src/main/java/org/restlet/service/StatusService.java b/org.restlet/src/main/java/org/restlet/service/StatusService.java index 0d94b40066..12923d714c 100644 --- a/org.restlet/src/main/java/org/restlet/service/StatusService.java +++ b/org.restlet/src/main/java/org/restlet/service/StatusService.java @@ -1,14 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; import org.restlet.Application; import org.restlet.Context; import org.restlet.Request; @@ -21,342 +23,346 @@ import org.restlet.resource.Resource; import org.restlet.resource.ResourceException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; - /** - * Service to handle error statuses. If an exception is thrown within your - * application or Restlet code, it will be intercepted by this service if it is - * enabled.
+ * Service to handle error statuses. If an exception is thrown within your application or Restlet + * code, it will be intercepted by this service if it is enabled.
*
- * When an exception or an error is caught, the - * {@link #getStatus(Throwable, Request, Response)} method is first invoked to - * obtain the status that you want to set on the response. If this method isn't - * overridden or returns null, the {@link Status#SERVER_ERROR_INTERNAL} constant - * will be set by default.
+ * When an exception or an error is caught, the {@link #toStatus(Throwable, Request, Response)} + * method is first invoked to obtain the status that you want to set on the response. If this method + * isn't overridden or returns null, the {@link Status#SERVER_ERROR_INTERNAL} constant will be set + * by default.
*
- * Also, when the status of a response returned is an error status (see - * {@link Status#isError()}, the - * {@link #getRepresentation(Status, Request, Response)} method is then invoked - * to give your service a chance to override the default error page.
+ * Also, when the status of a response returned is an error status (see {@link Status#isError()}, + * the {@link #toRepresentation(Status, Request, Response)} method is then invoked to give your + * service a chance to override the default error page.
*
- * If you want to customize the default behavior, you need to create a subclass - * of StatusService that overrides some or all of the methods mentioned above. - * Then, just create a instance of your class and set it on your Component or - * Application via the setStatusService() methods.
+ * If you want to customize the default behavior, you need to create a subclass of StatusService + * that overrides some or all of the methods mentioned above. Then, just create a instance of your + * class and set it on your Component or Application via the setStatusService() methods.
*
- * In case the response's entity has already been set, the status service does - * not generate an error representation. You can turn off this default behavior - * by calling the {@link #setOverwriting(boolean)} method. - * + * In case the response's entity has already been set, the status service does not generate an error + * representation. You can turn off this default behavior by calling the {@link + * #setOverwriting(boolean)} method. + * * @author Jerome Louvel */ public class StatusService extends Service { - /** The service used to select the preferred variant. */ - private volatile ConnegService connegService; - - /** The email address to contact in case of error. */ - private volatile String contactEmail; - - /** The service used to convert between status/throwable and representation. */ - private volatile ConverterService converterService; - - /** The home URI to propose in case of error. */ - private volatile Reference homeRef; - - /** The service used to select the preferred variant. */ - private volatile MetadataService metadataService; - - /** True if an existing entity should be overwritten. */ - private volatile boolean overwriting; - - /** - * Constructor. By default, it creates the necessary services. - */ - public StatusService() { - this(true); - } - - /** - * Constructor. By default, it creates the necessary services. - * - * @param enabled True if the service has been enabled. - * - */ - public StatusService(boolean enabled) { - this(enabled, new ConverterService(), new MetadataService(), new ConnegService()); - } - - /** - * Constructor. - * - * @param enabled True if the service has been enabled. - * @param converterService The service used to convert between status/throwable - * and representation. - * @param metadataService The service used to select the preferred variant. - * @param connegService The service used to select the preferred variant. - */ - public StatusService(boolean enabled, ConverterService converterService, MetadataService metadataService, - ConnegService connegService) { - super(enabled); - this.converterService = converterService; - this.metadataService = metadataService; - this.connegService = connegService; - this.contactEmail = null; - this.homeRef = new Reference("/"); - this.overwriting = false; - } - - @Override - public org.restlet.routing.Filter createInboundFilter(Context context) { - return new org.restlet.engine.application.StatusFilter(context, this); - } - - /** - * Returns the service used to select the preferred variant. - * - * @return The service used to select the preferred variant. - */ - public ConnegService getConnegService() { - return connegService; - } - - /** - * Returns the email address to contact in case of error. This is typically used - * when creating the status representations. - * - * @return The email address to contact in case of error. - */ - public String getContactEmail() { - return this.contactEmail; - } - - /** - * Returns the service used to convert between status/throwable and - * representation. - * - * @return The service used to convert between status/throwable and - * representation. - */ - public ConverterService getConverterService() { - return converterService; - } - - /** - * Returns the home URI to propose in case of error. - * - * @return The home URI to propose in case of error. - */ - public Reference getHomeRef() { - return this.homeRef; - } - - /** - * Returns the service used to select the preferred variant. - * - * @return The service used to select the preferred variant. - */ - public MetadataService getMetadataService() { - return metadataService; - } - - /** - * Indicates if an existing entity should be overwritten. False by default. - * - * @return True if an existing entity should be overwritten. - */ - public boolean isOverwriting() { - return this.overwriting; - } - - /** - * Sets the service used to select the preferred variant. - * - * @param connegService The service used to select the preferred variant. - */ - public void setConnegService(ConnegService connegService) { - this.connegService = connegService; - } - - /** - * Sets the email address to contact in case of error. This is typically used - * when creating the status representations. - * - * @param contactEmail The email address to contact in case of error. - */ - public void setContactEmail(String contactEmail) { - this.contactEmail = contactEmail; - } - - /** - * Sets the service used to convert between status/throwable and representation. - * - * @param converterService The service used to convert between status/throwable - * and representation. - */ - public void setConverterService(ConverterService converterService) { - this.converterService = converterService; - } - - /** - * Sets the home URI to propose in case of error. - * - * @param homeRef The home URI to propose in case of error. - */ - public void setHomeRef(Reference homeRef) { - this.homeRef = homeRef; - } - - /** - * Sets the service used to select the preferred variant. - * - * @param metadataService The service used to select the preferred variant. - */ - public void setMetadataService(MetadataService metadataService) { - this.metadataService = metadataService; - } - - /** - * Indicates if an existing entity should be overwritten. - * - * @param overwriting True if an existing entity should be overwritten. - */ - public void setOverwriting(boolean overwriting) { - this.overwriting = overwriting; - } - - /** - * Returns a representation for the given status. In order to customize the - * default representation, this method can be overridden. It returns a - * {@link org.restlet.data.Status} representation by default or a - * {@link java.lang.Throwable} representation if the throwable is annotated with - * {@link org.restlet.resource.Status}. - * - * @param status The status to represent. - * @param request The request handled. - * @param response The response updated. - * @return The representation of the given status. - */ - public Representation toRepresentation(Status status, Request request, Response response) { - Representation result = null; - - // Do content negotiation for status - if (converterService != null && connegService != null && metadataService != null) { - Object representationObject = null; - - // Serialize exception if any and if {@link org.restlet.resource.Status} annotation asks for it - Throwable cause = status.getThrowable(); - - if (cause != null) { - org.restlet.engine.resource.ThrowableAnnotationInfo tai = org.restlet.engine.resource.AnnotationUtils - .getInstance().getThrowableAnnotationInfo(cause.getClass()); - - if (tai != null && tai.isSerializable()) { - if (Application.getCurrent() != null && !Application.getCurrent().isDebugging()) { - // We clear the stack trace to prevent technical - // information leak - cause.setStackTrace(new StackTraceElement[] {}); - - if (cause.getCause() != null) { - Context.getCurrentLogger().log(Level.WARNING, - "The cause of the exception should be null except in debug mode"); - } - } - - representationObject = cause; - } - } - - try { - // Default representation match with the status properties - if (representationObject == null) { - representationObject = new StatusInfo(status, getContactEmail(), getHomeRef().toString()); - } - - List variants = org.restlet.engine.converter.ConverterUtils - .getVariants(representationObject.getClass(), null); - if (variants == null) { - variants = new ArrayList<>(); - } - - Variant variant = connegService.getPreferredVariant(variants, request, metadataService); - result = converterService.toRepresentation(representationObject, variant); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, - "Could not serialize throwable class " + ((cause == null) ? null : cause.getClass()), e); - } - } - - return result; - } - - /** - * Returns a representation for the given status.
- * In order to customize the default representation, this method can be - * overridden. By default, it invokes - * {@link #toRepresentation(Status, Request, Response)} - * - * @param status The status to represent. - * @param resource The parent resource. - * @return The representation of the given status. - */ - public Representation toRepresentation(Status status, Resource resource) { - return toRepresentation(status, resource.getRequest(), resource.getResponse()); - } - - /** - * Returns a status for a given exception or error. By default it unwraps the - * status of {@link ResourceException}. For other exceptions or errors, it - * returns an {@link Status#SERVER_ERROR_INTERNAL} status.
- *
- * In order to customize the default behavior, this method can be overridden. - * - * @param throwable The exception or error caught. - * @param request The request handled. - * @param response The response updated. - * @return The representation of the given status. - */ - public Status toStatus(Throwable throwable, Request request, Response response) { - Status result; - - Status defaultStatus = Status.SERVER_ERROR_INTERNAL; - Throwable t = throwable; - - // If throwable is a ResourceException, use its status and the cause. - if (throwable instanceof ResourceException) { - defaultStatus = ((ResourceException) throwable).getStatus(); - - if (throwable.getCause() != null && throwable.getCause() != throwable) { - t = throwable.getCause(); - } - } - - // look for Status annotation - org.restlet.engine.resource.ThrowableAnnotationInfo tai = org.restlet.engine.resource.AnnotationUtils - .getInstance().getThrowableAnnotationInfo(t.getClass()); - - if (tai != null) { - result = new Status(tai.getStatus(), t); - } else { - result = new Status(defaultStatus, t); - } - - return result; - } - - /** - * Returns a status for a given exception or error. By default it returns an - * {@link Status#SERVER_ERROR_INTERNAL} status and logs a severe message.
- * In order to customize the default behavior, this method can be overridden. - * - * @param throwable The exception or error caught. - * @param resource The parent resource. - * @return The representation of the given status. - */ - public Status toStatus(Throwable throwable, Resource resource) { - return toStatus(throwable, (resource == null) ? null : resource.getRequest(), - (resource == null) ? null : resource.getResponse()); - } + /** The service used to select the preferred variant. */ + private volatile ConnegService connegService; + + /** The email address to contact in case of error. */ + private volatile String contactEmail; + + /** The service used to convert between status/throwable and representation. */ + private volatile ConverterService converterService; + + /** The home URI to propose in case of error. */ + private volatile Reference homeRef; + + /** The service used to select the preferred variant. */ + private volatile MetadataService metadataService; + + /** True if an existing entity should be overwritten. */ + private volatile boolean overwriting; + + /** Constructor. By default, it creates the necessary services. */ + public StatusService() { + this(true); + } + + /** + * Constructor. By default, it creates the necessary services. + * + * @param enabled True if the service has been enabled. + */ + public StatusService(boolean enabled) { + this(enabled, new ConverterService(), new MetadataService(), new ConnegService()); + } + + /** + * Constructor. + * + * @param enabled True if the service has been enabled. + * @param converterService The service used to convert between status/throwable and + * representation. + * @param metadataService The service used to select the preferred variant. + * @param connegService The service used to select the preferred variant. + */ + public StatusService( + boolean enabled, + ConverterService converterService, + MetadataService metadataService, + ConnegService connegService) { + super(enabled); + this.converterService = converterService; + this.metadataService = metadataService; + this.connegService = connegService; + this.contactEmail = null; + this.homeRef = new Reference("/"); + this.overwriting = false; + } + + @Override + public org.restlet.routing.Filter createInboundFilter(Context context) { + return new org.restlet.engine.application.StatusFilter(context, this); + } + + /** + * Returns the service used to select the preferred variant. + * + * @return The service used to select the preferred variant. + */ + public ConnegService getConnegService() { + return connegService; + } + + /** + * Returns the email address to contact in case of error. This is typically used when creating + * the status representations. + * + * @return The email address to contact in case of error. + */ + public String getContactEmail() { + return this.contactEmail; + } + + /** + * Returns the service used to convert between status/throwable and representation. + * + * @return The service used to convert between status/throwable and representation. + */ + public ConverterService getConverterService() { + return converterService; + } + + /** + * Returns the home URI to propose in case of error. + * + * @return The home URI to propose in case of error. + */ + public Reference getHomeRef() { + return this.homeRef; + } + + /** + * Returns the service used to select the preferred variant. + * + * @return The service used to select the preferred variant. + */ + public MetadataService getMetadataService() { + return metadataService; + } + + /** + * Indicates if an existing entity should be overwritten. False by default. + * + * @return True if an existing entity should be overwritten. + */ + public boolean isOverwriting() { + return this.overwriting; + } + + /** + * Sets the service used to select the preferred variant. + * + * @param connegService The service used to select the preferred variant. + */ + public void setConnegService(ConnegService connegService) { + this.connegService = connegService; + } + + /** + * Sets the email address to contact in case of error. This is typically used when creating the + * status representations. + * + * @param contactEmail The email address to contact in case of error. + */ + public void setContactEmail(String contactEmail) { + this.contactEmail = contactEmail; + } + + /** + * Sets the service used to convert between status/throwable and representation. + * + * @param converterService The service used to convert between status/throwable and + * representation. + */ + public void setConverterService(ConverterService converterService) { + this.converterService = converterService; + } + + /** + * Sets the home URI to propose in case of error. + * + * @param homeRef The home URI to propose in case of error. + */ + public void setHomeRef(Reference homeRef) { + this.homeRef = homeRef; + } + + /** + * Sets the service used to select the preferred variant. + * + * @param metadataService The service used to select the preferred variant. + */ + public void setMetadataService(MetadataService metadataService) { + this.metadataService = metadataService; + } + + /** + * Indicates if an existing entity should be overwritten. + * + * @param overwriting True if an existing entity should be overwritten. + */ + public void setOverwriting(boolean overwriting) { + this.overwriting = overwriting; + } + + /** + * Returns a representation for the given status. To customize the default representation, this + * method can be overridden. It returns a {@link org.restlet.data.Status} representation by + * default or a {@link java.lang.Throwable} representation if the throwable is annotated with + * {@link org.restlet.resource.Status}. + * + * @param status The status to represent. + * @param request The request handled. + * @param response The response updated. + * @return The representation of the given status. + */ + public Representation toRepresentation(Status status, Request request, Response response) { + Representation result = null; + + // Do content negotiation for status + if (converterService != null && connegService != null && metadataService != null) { + Object representationObject = null; + + // Serialize exception if any and if {@link org.restlet.resource.Status} annotation asks + // for it + Throwable cause = status.getThrowable(); + + if (cause != null) { + org.restlet.engine.resource.ThrowableAnnotationInfo tai = + org.restlet.engine.resource.AnnotationUtils.getInstance() + .getThrowableAnnotationInfo(cause.getClass()); + + if (tai != null && tai.isSerializable()) { + if (Application.getCurrent() != null + && !Application.getCurrent().isDebugging()) { + // We clear the stack trace to prevent technical + // information leak + cause.setStackTrace(new StackTraceElement[] {}); + + if (cause.getCause() != null) { + Context.getCurrentLogger() + .log( + Level.WARNING, + "The cause of the exception should be null except in debug mode"); + } + } + + representationObject = cause; + } + } + + try { + // Default representation match with the status properties + if (representationObject == null) { + representationObject = + new StatusInfo(status, getContactEmail(), getHomeRef().toString()); + } + + List variants = + org.restlet.engine.converter.ConverterUtils.getVariants( + representationObject.getClass(), null); + if (variants == null) { + variants = new ArrayList<>(); + } + + Variant variant = + connegService.getPreferredVariant(variants, request, metadataService); + result = converterService.toRepresentation(representationObject, variant); + } catch (Exception e) { + Context.getCurrentLogger() + .log( + Level.WARNING, + e, + () -> + "Could not serialize throwable class " + + ((cause == null) ? null : cause.getClass())); + } + } + + return result; + } + + /** + * Returns a representation for the given status.
+ * To customize the default representation, this method can be overridden. By default, it + * invokes {@link #toRepresentation(Status, Request, Response)} + * + * @param status The status to represent. + * @param resource The parent resource. + * @return The representation of the given status. + */ + public Representation toRepresentation(Status status, Resource resource) { + return toRepresentation(status, resource.getRequest(), resource.getResponse()); + } + + /** + * Returns a status for a given exception or error. By default, it unwraps the status of {@link + * ResourceException}. For other exceptions or errors, it returns an {@link + * Status#SERVER_ERROR_INTERNAL} status.
+ *
+ * To customize the default behavior, this method can be overridden. + * + * @param throwable The exception or error caught. + * @param request The request handled. + * @param response The response updated. + * @return The representation of the given status. + */ + public Status toStatus(Throwable throwable, Request request, Response response) { + Status result; + + Status defaultStatus = Status.SERVER_ERROR_INTERNAL; + Throwable t = throwable; + + // If throwable is a ResourceException, use its status and the cause. + if (throwable instanceof ResourceException resourceException) { + defaultStatus = resourceException.getStatus(); + + if (throwable.getCause() != null && throwable.getCause() != throwable) { + t = throwable.getCause(); + } + } + + // look for Status annotation + org.restlet.engine.resource.ThrowableAnnotationInfo tai = + org.restlet.engine.resource.AnnotationUtils.getInstance() + .getThrowableAnnotationInfo(t.getClass()); + + if (tai != null) { + result = new Status(tai.getStatus(), t); + } else { + result = new Status(defaultStatus, t); + } + + return result; + } + + /** + * Returns a status for a given exception or error. By default, it returns an {@link + * Status#SERVER_ERROR_INTERNAL} status and logs a severe message.
+ * To customize the default behavior, this method can be overridden. + * + * @param throwable The exception or error caught. + * @param resource The parent resource. + * @return The representation of the given status. + */ + public Status toStatus(Throwable throwable, Resource resource) { + return toStatus( + throwable, + (resource == null) ? null : resource.getRequest(), + (resource == null) ? null : resource.getResponse()); + } } diff --git a/org.restlet/src/main/java/org/restlet/service/TaskService.java b/org.restlet/src/main/java/org/restlet/service/TaskService.java index a11c717c9e..6849c85d13 100644 --- a/org.restlet/src/main/java/org/restlet/service/TaskService.java +++ b/org.restlet/src/main/java/org/restlet/service/TaskService.java @@ -1,14 +1,28 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; import org.restlet.Application; import org.restlet.Context; import org.restlet.Response; @@ -16,652 +30,622 @@ import org.restlet.engine.util.ContextualRunnable; import org.restlet.routing.VirtualHost; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.*; -import java.util.logging.Level; - /** - * Application service capable of running and scheduling tasks asynchronously. - * The service instance returned will not invoke the runnable task in the - * current thread.
+ * Application service capable of running and scheduling tasks asynchronously. The service instance + * returned will not invoke the runnable task in the current thread.
*
- * In addition to allowing pooling, this method will ensure that the threads - * executing the tasks will have the thread local variables copied from the - * calling thread. This will ensure that call to static methods like - * {@link Application#getCurrent()} still work.
+ * In addition to allowing pooling, this method will ensure that the threads executing the tasks + * will have the thread local variables copied from the calling thread. This will ensure that call + * to static methods like {@link Application#getCurrent()} still work.
*
- * Also, note that this executor service will be shared among all Restlets and - * Resources that are part of your context. In general this context corresponds - * to a parent Application's context. If you want to have your own service - * instance, you can use the {@link TaskService#wrap(ScheduledExecutorService)} - * method to ensure that thread local variables are correctly set. - * + * Also, note that this executor service will be shared among all Restlets and Resources that are + * part of your context. In general this context corresponds to a parent Application's context. If + * you want to have your own service instance, you can use the {@link + * TaskService#wrap(ScheduledExecutorService)} method to ensure that thread local variables are + * correctly set. + * * @author Jerome Louvel * @author Doug Lea (docs of ExecutorService in public domain) * @author Tim Peierls */ public class TaskService extends Service implements ScheduledExecutorService { - /** - * The default thread factory. - * - * @author Jerome Louvel - * @author Tim Peierls - */ - private static class RestletThreadFactory implements ThreadFactory { - - /** - * Indicates whether or not the thread is a daemon thread. True by default. - */ - private boolean daemon; - - final ThreadFactory factory = Executors.defaultThreadFactory(); - - public RestletThreadFactory(boolean daemon) { - this.daemon = daemon; - } - - public Thread newThread(Runnable runnable) { - Thread t = factory.newThread(runnable); - - // Default factory is documented as producing names of the - // form "pool-N-thread-M". - t.setName(t.getName().replaceFirst("pool", "restlet")); - t.setDaemon(daemon); - return t; - } - } - - /** - * Wraps a JDK executor service to ensure that the threads executing the tasks - * will have the thread local variables copied from the calling thread. This - * will ensure that call to static methods like {@link Application#getCurrent()} - * still work. - * - * @param executorService The JDK service to wrap. - * @return The wrapper service to use. - */ - public static ScheduledExecutorService wrap(final ScheduledExecutorService executorService) { - return new ScheduledExecutorService() { - - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return executorService.awaitTermination(timeout, unit); - } - - public void execute(final Runnable runnable) { - // Save the thread local variables - final Application currentApplication = Application.getCurrent(); - final Context currentContext = Context.getCurrent(); - final Integer currentVirtualHost = VirtualHost.getCurrent(); - final Response currentResponse = Response.getCurrent(); - - executorService.execute(new Runnable() { - public void run() { - // Copy the thread local variables - Response.setCurrent(currentResponse); - Context.setCurrent(currentContext); - VirtualHost.setCurrent(currentVirtualHost); - Application.setCurrent(currentApplication); - - if (runnable instanceof ContextualRunnable) { - ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - try { - // Run the user task - Thread.currentThread() - .setContextClassLoader(((ContextualRunnable) runnable).getContextClassLoader()); - runnable.run(); - } finally { - Engine.clearThreadLocalVariables(); - Thread.currentThread().setContextClassLoader(tccl); - } - } else { - try { - // Run the user task - runnable.run(); - } finally { - Engine.clearThreadLocalVariables(); - } - } - } - }); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public List invokeAll(Collection tasks) throws InterruptedException { - return executorService.invokeAll(tasks); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public List invokeAll(Collection tasks, long timeout, TimeUnit unit) throws InterruptedException { - return executorService.invokeAll(tasks, timeout, unit); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Object invokeAny(Collection tasks) throws InterruptedException, ExecutionException { - return executorService.invokeAny(tasks); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Object invokeAny(Collection tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return executorService.invokeAny(tasks, timeout, unit); - } - - public boolean isShutdown() { - return executorService.isShutdown(); - } - - public boolean isTerminated() { - return executorService.isTerminated(); - } - - public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - return executorService.schedule(callable, delay, unit); - } - - public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - return executorService.schedule(command, delay, unit); - } - - public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, - TimeUnit unit) { - return executorService.scheduleAtFixedRate(command, initialDelay, period, unit); - } - - public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, - TimeUnit unit) { - return executorService.scheduleWithFixedDelay(command, initialDelay, delay, unit); - } - - public void shutdown() { - executorService.shutdown(); - } - - public List shutdownNow() { - return executorService.shutdownNow(); - } - - public Future submit(Callable task) { - return executorService.submit(task); - } - - public Future submit(Runnable task) { - return executorService.submit(task); - } - - public Future submit(Runnable task, T result) { - return executorService.submit(task, result); - } - }; - } - - /** The core pool size defining the maximum number of threads. */ - private volatile int corePoolSize; - - /** - * Indicates whether or not the threads are daemon threads. True by default. - */ - private volatile boolean daemon; - - /** - * Allow {@link #shutdown()} and {@link #shutdownNow()} methods to effectively - * shutdown the wrapped executor service. - */ - private volatile boolean shutdownAllowed; - - /** The wrapped JDK executor service. */ - private volatile ScheduledExecutorService wrapped; - - /** - * Constructor. Enables the service and set the core pool size to 4 by default. - */ - public TaskService() { - this(true); - } - - /** - * Constructor. Set the core pool size to 4 by default. - * - * @param enabled True if the service has been enabled. - */ - public TaskService(boolean enabled) { - this(enabled, true); - } - - /** - * Constructor. Set the core pool size to 4 by default. - * - * @param enabled True if the service has been enabled. - * @param daemon True if the threads are created as daemon threads. - */ - public TaskService(boolean enabled, boolean daemon) { - this(enabled, 4); - this.daemon = daemon; - } - - /** - * Constructor. The default minimum size - * - * @param enabled True if the service has been enabled. - * @param corePoolSize The core pool size defining the maximum number of - * threads. - */ - public TaskService(boolean enabled, int corePoolSize) { - super(enabled); - this.corePoolSize = corePoolSize; - this.shutdownAllowed = false; - } - - /** - * Constructor. - * - * @param corePoolSize The core pool size defining the maximum number of - * threads. - */ - public TaskService(int corePoolSize) { - this(true, corePoolSize); - } - - /** - * Blocks until all tasks have completed execution after a shutdown request, or - * the timeout occurs, or the current thread is interrupted, whichever happens - * first. - * - * @param timeout The maximum time to wait. - * @param unit The time unit. - * @return True if this executor terminated and false if the timeout elapsed - * before termination. - */ - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - startIfNeeded(); - return getWrapped().awaitTermination(timeout, unit); - } - - /** - * Creates a new JDK executor service that will be wrapped. By default it calls - * {@link Executors#newCachedThreadPool(ThreadFactory)}, passing the result of - * {@link #createThreadFactory()} as a parameter. - * - * @param corePoolSize The core pool size defining the maximum number of - * threads. - * @return A new JDK executor service. - */ - protected ScheduledExecutorService createExecutorService(int corePoolSize) { - return Executors.newScheduledThreadPool(corePoolSize, createThreadFactory()); - } - - /** - * Creates a new thread factory that will properly name the Restlet created - * threads with a "restlet-" prefix. - * - * @return A new thread factory. - */ - protected ThreadFactory createThreadFactory() { - return new RestletThreadFactory(daemon); - } - - /** - * Executes the given command asynchronously. - * - * @param command The command to execute. - */ - public void execute(Runnable command) { - startIfNeeded(); - getWrapped().execute(command); - } - - /** - * Returns the core pool size defining the maximum number of threads. - * - * @return The core pool size defining the maximum number of threads. - */ - public int getCorePoolSize() { - return corePoolSize; - } - - /** - * Returns the wrapped JDK executor service. - * - * @return The wrapped JDK executor service. - */ - private ScheduledExecutorService getWrapped() { - return wrapped; - } - - /** - * Executes the given tasks, returning a list of Futures holding their status - * and results when all complete.
- *
- * Due to a breaking change between Java SE versions 5 and 6, and in order to - * maintain compatibility both at the source and binary level, we have removed - * the generic information from this method. You can check the - * {@link ExecutorService} interface for typing details. - * - * @param tasks The task to execute. - * @return The list of futures. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public List invokeAll(Collection tasks) throws InterruptedException { - startIfNeeded(); - return getWrapped().invokeAll(tasks); - } - - /** - * Executes the given tasks, returning a list of Futures holding their status - * and results when all complete or the timeout expires, whichever happens - * first. Future.isDone() is true for each element of the returned list. Upon - * return, tasks that have not completed are canceled. Note that a completed - * task could have terminated either normally or by throwing an exception. The - * results of this method are undefined if the given collection is modified - * while this operation is in progress.
- *
- * Due to a breaking change between Java SE versions 5 and 6, and in order to - * maintain compatibility both at the source and binary level, we have removed - * the generic information from this method. You can check the - * {@link ExecutorService} interface for typing details. - * - * @param tasks The task to execute. - * @param timeout The maximum time to wait. - * @param unit The time unit. - * @return The list of futures. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public List invokeAll(Collection tasks, long timeout, TimeUnit unit) throws InterruptedException { - startIfNeeded(); - return getWrapped().invokeAll(tasks, timeout, unit); - } - - /** - * Executes the given tasks, returning the result of one that has completed - * successfully (i.e., without throwing an exception), if any do. Upon normal or - * exceptional return, tasks that have not completed are cancelled. The results - * of this method are undefined if the given collection is modified while this - * operation is in progress. - * - * Due to a breaking change between Java SE versions 5 and 6, and in order to - * maintain compatibility both at the source and binary level, we have removed - * the generic information from this method. You can check the - * {@link ExecutorService} interface for typing details. - * - * @param tasks The task to execute. - * @return The result returned by one of the tasks. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Object invokeAny(Collection tasks) throws InterruptedException, ExecutionException { - startIfNeeded(); - return getWrapped().invokeAny(tasks); - } - - /** - * Executes the given tasks, returning the result of one that has completed - * successfully (i.e., without throwing an exception), if any do before the - * given timeout elapses. Upon normal or exceptional return, tasks that have not - * completed are cancelled. The results of this method are undefined if the - * given collection is modified while this operation is in progress. - * - * Due to a breaking change between Java SE versions 5 and 6, and in order to - * maintain compatibility both at the source and binary level, we have removed - * the generic information from this method. You can check the - * {@link ExecutorService} interface for typing details. - * - * @param tasks The task to execute. - * @param timeout The maximum time to wait. - * @param unit The time unit. - * @return The result returned by one of the tasks. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Object invokeAny(Collection tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - startIfNeeded(); - return getWrapped().invokeAny(tasks, timeout, unit); - } - - /** - * Indicates whether the threads are created as daemon threads. - * - * @return True if the threads are created as daemon threads. - */ - public boolean isDaemon() { - return daemon; - } - - /** - * Returns true if this executor has been shut down. - * - * @return True if this executor has been shut down. - */ - public boolean isShutdown() { - return (getWrapped() == null) || getWrapped().isShutdown(); - } - - /** - * Indicates if the {@link #shutdown()} and {@link #shutdownNow()} methods are - * allowed to effectively shutdown the wrapped executor service. Return false by - * default. - * - * @return True if shutdown is allowed. - */ - public boolean isShutdownAllowed() { - return shutdownAllowed; - } - - /** - * Returns true if all tasks have completed following shut down. Note that - * isTerminated is never true unless either shutdown or shutdownNow was called - * first. - * - * @return True if all tasks have completed following shut down. - */ - public boolean isTerminated() { - return (getWrapped() == null) || getWrapped().isTerminated(); - } - - /** - * Creates and executes a ScheduledFuture that becomes enabled after the given - * delay. - * - * @param callable The function to execute. - * @param delay The time from now to delay execution. - * @param unit The time unit of the delay parameter. - * @return a ScheduledFuture that can be used to extract result or cancel. - * @throws RejectedExecutionException if task cannot be scheduled for execution. - * @throws NullPointerException if callable is null - */ - public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { - startIfNeeded(); - return getWrapped().schedule(callable, delay, unit); - } - - /** - * Creates and executes a one-shot action that becomes enabled after the given - * delay. - * - * @param command The task to execute. - * @param delay The time from now to delay execution. - * @param unit The time unit of the delay parameter. - * @return a Future representing pending completion of the task, and whose get() - * method will return null upon completion. - * @throws RejectedExecutionException if task cannot be scheduled for execution. - * @throws NullPointerException if command is null - */ - public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { - startIfNeeded(); - return getWrapped().schedule(command, delay, unit); - } - - /** - * Creates and executes a periodic action that becomes enabled first after the - * given initial delay, and subsequently with the given period; that is - * executions will commence after initialDelay then initialDelay+period, then - * initialDelay + 2 * period, and so on. If any execution of the task encounters - * an exception, subsequent executions are suppressed. Otherwise, the task will - * only terminate via cancellation or termination of the executor. - * - * @param command The task to execute. - * @param initialDelay The time to delay first execution. - * @param period The period between successive executions. - * @param unit The time unit of the initialDelay and period parameters - * @return a Future representing pending completion of the task, and whose get() - * method will throw an exception upon cancellation. - * @throws RejectedExecutionException if task cannot be scheduled for execution. - * @throws NullPointerException if command is null - * @throws IllegalArgumentException if period less than or equal to zero. - */ - public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { - startIfNeeded(); - return getWrapped().scheduleAtFixedRate(command, initialDelay, period, unit); - } - - /** - * Creates and executes a periodic action that becomes enabled first after the - * given initial delay, and subsequently with the given delay between the - * termination of one execution and the commencement of the next. If any - * execution of the task encounters an exception, subsequent executions are - * suppressed. Otherwise, the task will only terminate via cancellation or - * termination of the executor. - * - * @param command The task to execute. - * @param initialDelay The time to delay first execution. - * @param delay The delay between the termination of one execution and - * the commencement of the next. - * @param unit The time unit of the initialDelay and delay parameters - * @return a Future representing pending completion of the task, and whose - * get() method will throw an exception upon cancellation. - * @throws RejectedExecutionException if task cannot be scheduled for execution. - * @throws NullPointerException if command is null - * @throws IllegalArgumentException if delay less than or equal to zero. - */ - public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { - startIfNeeded(); - return getWrapped().scheduleWithFixedDelay(command, initialDelay, delay, unit); - } - - /** - * Sets the core pool size defining the maximum number of threads. - * - * @param corePoolSize The core pool size defining the maximum number of - * threads. - */ - public void setCorePoolSize(int corePoolSize) { - this.corePoolSize = corePoolSize; - } - - /** - * Indicates whether or not the threads are daemon threads. True by default. - * - * @param daemon True if the threads are daemon threads. - */ - public void setDaemon(boolean daemon) { - this.daemon = daemon; - } - - /** - * Indicates if the {@link #shutdown()} and {@link #shutdownNow()} methods are - * allowed to effectively shutdown the wrapped executor service. - * - * @param allowShutdown True if shutdown is allowed. - */ - public void setShutdownAllowed(boolean allowShutdown) { - this.shutdownAllowed = allowShutdown; - } - - /** - * Sets the wrapped JDK executor service. - * - * @param wrapped The wrapped JDK executor service. - */ - private void setWrapped(ScheduledExecutorService wrapped) { - this.wrapped = wrapped; - } - - /** - * Initiates an orderly shutdown in which previously submitted tasks are - * executed, but no new tasks will be accepted. - */ - public void shutdown() { - if (isShutdownAllowed() && (getWrapped() != null)) { - getWrapped().shutdown(); - } - } - - /** - * Attempts to stop all actively executing tasks, halts the processing of - * waiting tasks, and returns a list of the tasks that were awaiting execution. - * - * @return The list of tasks that never commenced execution; - */ - public List shutdownNow() { - return isShutdownAllowed() && (getWrapped() != null) ? getWrapped().shutdownNow() - : Collections.emptyList(); - } - - @Override - public synchronized void start() throws Exception { - if ((getWrapped() == null) || getWrapped().isShutdown()) { - setWrapped(wrap(createExecutorService(getCorePoolSize()))); - } - - super.start(); - } - - /** - * Starts the task service if needed. - */ - private void startIfNeeded() { - if (!isStarted()) { - try { - start(); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to start the task service", e); - } - } - } - - @Override - public synchronized void stop() throws Exception { - super.stop(); - - if ((getWrapped() != null) && !getWrapped().isShutdown()) { - getWrapped().shutdown(); - } - } - - /** - * Submits a value-returning task for execution and returns a Future - * representing the pending results of the task. - * - * @param task The task to submit. - * @return A Future representing pending completion of the task, and whose get() - * method will return the given result upon completion. - */ - public Future submit(Callable task) { - startIfNeeded(); - return getWrapped().submit(task); - } - - /** - * - * @param task The task to submit. - * @return A Future representing pending completion of the task, and whose get() - * method will return the given result upon completion. - */ - public Future submit(Runnable task) { - startIfNeeded(); - return getWrapped().submit(task); - } - - /** - * - * @param task The task to submit. - * @param result The result to return. - * @return A Future representing pending completion of the task, and whose get() - * method will return the given result upon completion. - */ - public Future submit(Runnable task, T result) { - startIfNeeded(); - return getWrapped().submit(task, result); - } - + /** + * The default thread factory. + * + * @author Jerome Louvel + * @author Tim Peierls + */ + private static class RestletThreadFactory implements ThreadFactory { + + /** Indicates whether the thread is a daemon thread. True by default. */ + private boolean daemon; + + final ThreadFactory factory = Executors.defaultThreadFactory(); + + public RestletThreadFactory(boolean daemon) { + this.daemon = daemon; + } + + public Thread newThread(Runnable runnable) { + Thread t = factory.newThread(runnable); + + // Default factory is documented as producing names of the + // form "pool-N-thread-M". + t.setName(t.getName().replaceFirst("pool", "restlet")); + t.setDaemon(daemon); + return t; + } + } + + /** + * Wraps a JDK executor service to ensure that the threads executing the tasks will have the + * thread local variables copied from the calling thread. This will ensure that call to static + * methods like {@link Application#getCurrent()} still work. + * + * @param executorService The JDK service to wrap. + * @return The wrapper service to use. + */ + public static ScheduledExecutorService wrap(final ScheduledExecutorService executorService) { + return new ScheduledExecutorService() { + + public boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException { + return executorService.awaitTermination(timeout, unit); + } + + public void execute(final Runnable runnable) { + // Save the thread local variables + final Application currentApplication = Application.getCurrent(); + final Context currentContext = Context.getCurrent(); + final Integer currentVirtualHost = VirtualHost.getCurrent(); + final Response currentResponse = Response.getCurrent(); + + executorService.execute( + () -> { + // Copy the thread local variables + Response.setCurrent(currentResponse); + Context.setCurrent(currentContext); + VirtualHost.setCurrent(currentVirtualHost); + Application.setCurrent(currentApplication); + + if (runnable instanceof ContextualRunnable contextualRunnable) { + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + try { + // Run the user task + Thread.currentThread() + .setContextClassLoader( + contextualRunnable.getContextClassLoader()); + runnable.run(); + } finally { + Engine.clearThreadLocalVariables(); + Thread.currentThread().setContextClassLoader(tccl); + } + } else { + try { + // Run the user task + runnable.run(); + } finally { + Engine.clearThreadLocalVariables(); + } + } + }); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public List invokeAll(Collection tasks) throws InterruptedException { + return executorService.invokeAll(tasks); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public List invokeAll(Collection tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return executorService.invokeAll(tasks, timeout, unit); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public Object invokeAny(Collection tasks) + throws InterruptedException, ExecutionException { + return executorService.invokeAny(tasks); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public Object invokeAny(Collection tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return executorService.invokeAny(tasks, timeout, unit); + } + + public boolean isShutdown() { + return executorService.isShutdown(); + } + + public boolean isTerminated() { + return executorService.isTerminated(); + } + + public ScheduledFuture schedule( + Callable callable, long delay, TimeUnit unit) { + return executorService.schedule(callable, delay, unit); + } + + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return executorService.schedule(command, delay, unit); + } + + public ScheduledFuture scheduleAtFixedRate( + Runnable command, long initialDelay, long period, TimeUnit unit) { + return executorService.scheduleAtFixedRate(command, initialDelay, period, unit); + } + + public ScheduledFuture scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + return executorService.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + + public void shutdown() { + executorService.shutdown(); + } + + public List shutdownNow() { + return executorService.shutdownNow(); + } + + public Future submit(Callable task) { + return executorService.submit(task); + } + + public Future submit(Runnable task) { + return executorService.submit(task); + } + + public Future submit(Runnable task, T result) { + return executorService.submit(task, result); + } + }; + } + + /** The core pool size defining the maximum number of threads. */ + private volatile int corePoolSize; + + /** Indicates whether the threads are daemon threads. True by default. */ + private volatile boolean daemon; + + /** + * Allow {@link #shutdown()} and {@link #shutdownNow()} methods to effectively shutdown the + * wrapped executor service. + */ + private volatile boolean shutdownAllowed; + + /** The wrapped JDK executor service. */ + private volatile ScheduledExecutorService wrapped; + + /** Constructor. Enables the service and set the core pool size to 4 by default. */ + public TaskService() { + this(true); + } + + /** + * Constructor. Set the core pool size to 4 by default. + * + * @param enabled True if the service has been enabled. + */ + public TaskService(boolean enabled) { + this(enabled, true); + } + + /** + * Constructor. Set the core pool size to 4 by default. + * + * @param enabled True if the service has been enabled. + * @param daemon True if the threads are created as daemon threads. + */ + public TaskService(boolean enabled, boolean daemon) { + this(enabled, 4); + this.daemon = daemon; + } + + /** + * Constructor. The default minimum size + * + * @param enabled True if the service has been enabled. + * @param corePoolSize The core pool size defining the maximum number of threads. + */ + public TaskService(boolean enabled, int corePoolSize) { + super(enabled); + this.corePoolSize = corePoolSize; + this.shutdownAllowed = false; + } + + /** + * Constructor. + * + * @param corePoolSize The core pool size defining the maximum number of threads. + */ + public TaskService(int corePoolSize) { + this(true, corePoolSize); + } + + /** + * Blocks until all tasks have completed execution after a shutdown request, or the timeout + * occurs, or the current thread is interrupted, whichever happens first. + * + * @param timeout The maximum time to wait. + * @param unit The time unit. + * @return True if this executor terminated and false if the timeout elapsed before termination. + */ + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + startIfNeeded(); + return getWrapped().awaitTermination(timeout, unit); + } + + /** + * Creates a new JDK executor service that will be wrapped. By default, it calls {@link + * Executors#newCachedThreadPool(ThreadFactory)}, passing the result of {@link + * #createThreadFactory()} as a parameter. + * + * @param corePoolSize The core pool size defining the maximum number of threads. + * @return A new JDK executor service. + */ + protected ScheduledExecutorService createExecutorService(int corePoolSize) { + return Executors.newScheduledThreadPool(corePoolSize, createThreadFactory()); + } + + /** + * Creates a new thread factory that will properly name the Restlet created threads with a + * "restlet-" prefix. + * + * @return A new thread factory. + */ + protected ThreadFactory createThreadFactory() { + return new RestletThreadFactory(daemon); + } + + /** + * Executes the given command asynchronously. + * + * @param command The command to execute. + */ + public void execute(Runnable command) { + startIfNeeded(); + getWrapped().execute(command); + } + + /** + * Returns the core pool size defining the maximum number of threads. + * + * @return The core pool size defining the maximum number of threads. + */ + public int getCorePoolSize() { + return corePoolSize; + } + + /** + * Returns the wrapped JDK executor service. + * + * @return The wrapped JDK executor service. + */ + private ScheduledExecutorService getWrapped() { + return wrapped; + } + + /** + * Executes the given tasks, returning a list of Futures holding their status and results when + * all complete.
+ *
+ * Due to a breaking change between Java SE versions 5 and 6, and to maintain compatibility both + * at the source and binary level, we have removed the generic information from this method. You + * can check the {@link ExecutorService} interface for typing details. + * + * @param tasks The task to execute. + * @return The list of futures. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public List invokeAll(Collection tasks) throws InterruptedException { + startIfNeeded(); + return getWrapped().invokeAll(tasks); + } + + /** + * Executes the given tasks, returning a list of Futures holding their status and results when + * all complete or the timeout expires, whichever happens first. Future.isDone() is true for + * each element of the returned list. Upon return, tasks that have not completed are canceled. + * Note that a completed task could have terminated either normally or by throwing an exception. + * The results of this method are undefined if the given collection is modified while this + * operation is in progress.
+ *
+ * Due to a breaking change between Java SE versions 5 and 6, and to maintain compatibility both + * at the source and binary level, we have removed the generic information from this method. You + * can check the {@link ExecutorService} interface for typing details. + * + * @param tasks The task to execute. + * @param timeout The maximum time to wait. + * @param unit The time unit. + * @return The list of futures. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public List invokeAll(Collection tasks, long timeout, TimeUnit unit) + throws InterruptedException { + startIfNeeded(); + return getWrapped().invokeAll(tasks, timeout, unit); + } + + /** + * Executes the given tasks, returning the result of one that has completed successfully (i.e., + * without throwing an exception) if any do. Upon normal or exceptional return, tasks that have + * not completed are canceled. The results of this method are undefined if the given collection + * is modified while this operation is in progress. + * + *

Due to a breaking change between Java SE versions 5 and 6, and to maintain compatibility + * both at the source and binary level, we have removed the generic information from this + * method. You can check the {@link ExecutorService} interface for typing details. + * + * @param tasks The task to execute. + * @return The result returned by one of the tasks. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public Object invokeAny(Collection tasks) throws InterruptedException, ExecutionException { + startIfNeeded(); + return getWrapped().invokeAny(tasks); + } + + /** + * Executes the given tasks, returning the result of one that has completed successfully (i.e., + * without throwing an exception) if any do before the given timeout elapses. Upon normal or + * exceptional return, tasks that have not completed are canceled. The results of this method + * are undefined if the given collection is modified while this operation is in progress. + * + *

Due to a breaking change between Java SE versions 5 and 6, and to maintain compatibility + * both at the source and binary level, we have removed the generic information from this + * method. You can check the {@link ExecutorService} interface for typing details. + * + * @param tasks The task to execute. + * @param timeout The maximum time to wait. + * @param unit The time unit. + * @return The result returned by one of the tasks. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public Object invokeAny(Collection tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + startIfNeeded(); + return getWrapped().invokeAny(tasks, timeout, unit); + } + + /** + * Indicates whether the threads are created as daemon threads. + * + * @return True if the threads are created as daemon threads. + */ + public boolean isDaemon() { + return daemon; + } + + /** + * Returns true if this executor has been shut down. + * + * @return True if this executor has been shut down. + */ + public boolean isShutdown() { + return (getWrapped() == null) || getWrapped().isShutdown(); + } + + /** + * Indicates if the {@link #shutdown()} and {@link #shutdownNow()} methods are allowed to + * effectively shut down the wrapped executor service. Return false by default. + * + * @return True if shutdown is allowed. + */ + public boolean isShutdownAllowed() { + return shutdownAllowed; + } + + /** + * Returns true if all tasks have completed following shut down. Note that isTerminated is never + * true unless either shutdown or shutdownNow was called first. + * + * @return True if all tasks have completed following shut down. + */ + public boolean isTerminated() { + return (getWrapped() == null) || getWrapped().isTerminated(); + } + + /** + * Creates and executes a ScheduledFuture that becomes enabled after the given delay. + * + * @param callable The function to execute. + * @param delay The time from now to delay execution. + * @param unit The time unit of the delay parameter. + * @return a ScheduledFuture that can be used to extract result or cancel. + * @throws RejectedExecutionException if a task cannot be scheduled for execution. + * @throws NullPointerException if callable is null + */ + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + startIfNeeded(); + return getWrapped().schedule(callable, delay, unit); + } + + /** + * Creates and executes a one-shot action that becomes enabled after the given delay. + * + * @param command The task to execute. + * @param delay The time from now to delay execution. + * @param unit The time unit of the delay parameter. + * @return a Future representing pending completion of the task, and whose get() method will + * return null upon completion. + * @throws RejectedExecutionException if a task cannot be scheduled for execution. + * @throws NullPointerException if the command is null + */ + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + startIfNeeded(); + return getWrapped().schedule(command, delay, unit); + } + + /** + * Creates and executes a periodic action that becomes enabled first after the given initial + * delay, and subsequently with the given period; that is executions will begin after + * initialDelay then initialDelay+period, then initialDelay + 2 * period, and so on. If any + * execution of the task encounters an exception, later executions are suppressed. Otherwise, + * the task will only terminate via cancellation or termination of the executor. + * + * @param command The task to execute. + * @param initialDelay The time to delay the first execution. + * @param period The period between successive executions. + * @param unit The time unit of the initialDelay and period parameters + * @return a Future representing pending completion of the task, and whose get() method will + * throw an exception upon cancellation. + * @throws RejectedExecutionException if a task cannot be scheduled for execution. + * @throws NullPointerException if the command is null + * @throws IllegalArgumentException if period less than or equal to zero. + */ + public ScheduledFuture scheduleAtFixedRate( + Runnable command, long initialDelay, long period, TimeUnit unit) { + startIfNeeded(); + return getWrapped().scheduleAtFixedRate(command, initialDelay, period, unit); + } + + /** + * Creates and executes a periodic action that becomes enabled first after the given initial + * delay, and subsequently with the given delay between the termination of one execution and the + * commencement of the next. If any execution of the task encounters an exception, later + * executions are suppressed. Otherwise, the task will only terminate via cancellation or + * termination of the executor. + * + * @param command The task to execute. + * @param initialDelay The time to delay the first execution. + * @param delay The delay between the termination of one execution and the commencement of the + * next. + * @param unit The time unit of the initialDelay and delay parameters + * @return a Future representing pending completion of the task, and whose get() method + * will throw an exception upon cancellation. + * @throws RejectedExecutionException if a task cannot be scheduled for execution. + * @throws NullPointerException if the command is null + * @throws IllegalArgumentException if delay less than or equal to zero. + */ + public ScheduledFuture scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + startIfNeeded(); + return getWrapped().scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + + /** + * Sets the core pool size defining the maximum number of threads. + * + * @param corePoolSize The core pool size defining the maximum number of threads. + */ + public void setCorePoolSize(int corePoolSize) { + this.corePoolSize = corePoolSize; + } + + /** + * Indicates whether or not the threads are daemon threads. True by default. + * + * @param daemon True if the threads are daemon threads. + */ + public void setDaemon(boolean daemon) { + this.daemon = daemon; + } + + /** + * Indicates if the {@link #shutdown()} and {@link #shutdownNow()} methods are allowed to + * effectively shutdown the wrapped executor service. + * + * @param allowShutdown True if shutdown is allowed. + */ + public void setShutdownAllowed(boolean allowShutdown) { + this.shutdownAllowed = allowShutdown; + } + + /** + * Sets the wrapped JDK executor service. + * + * @param wrapped The wrapped JDK executor service. + */ + private void setWrapped(ScheduledExecutorService wrapped) { + this.wrapped = wrapped; + } + + /** + * Initiates an orderly shutdown in which previously submitted tasks are executed, but no new + * tasks will be accepted. + */ + public void shutdown() { + if (isShutdownAllowed() && (getWrapped() != null)) { + getWrapped().shutdown(); + } + } + + /** + * Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and + * returns a list of the tasks that were awaiting execution. + * + * @return The list of tasks that never began execution; + */ + public List shutdownNow() { + return isShutdownAllowed() && (getWrapped() != null) + ? getWrapped().shutdownNow() + : Collections.emptyList(); + } + + @Override + public synchronized void start() throws Exception { + if ((getWrapped() == null) || getWrapped().isShutdown()) { + setWrapped(wrap(createExecutorService(getCorePoolSize()))); + } + + super.start(); + } + + /** Starts the task service if needed. */ + private void startIfNeeded() { + if (!isStarted()) { + try { + start(); + } catch (Exception e) { + Context.getCurrentLogger() + .log(Level.WARNING, "Unable to start the task service", e); + } + } + } + + @Override + public synchronized void stop() throws Exception { + super.stop(); + + if ((getWrapped() != null) && !getWrapped().isShutdown()) { + getWrapped().shutdown(); + } + } + + /** + * Submits a value-returning task for execution and returns a Future representing the pending + * results of the task. + * + * @param task The task to submit. + * @return A Future representing pending completion of the task, and whose get() method will + * return the given result upon completion. + */ + public Future submit(Callable task) { + startIfNeeded(); + return getWrapped().submit(task); + } + + /** + * @param task The task to submit. + * @return A Future representing pending completion of the task, and whose get() method will + * return the given result upon completion. + */ + public Future submit(Runnable task) { + startIfNeeded(); + return getWrapped().submit(task); + } + + /** + * @param task The task to submit. + * @param result The result to return. + * @return A Future representing pending completion of the task, and whose get() method will + * return the given result upon completion. + */ + public Future submit(Runnable task, T result) { + startIfNeeded(); + return getWrapped().submit(task, result); + } } diff --git a/org.restlet/src/main/java/org/restlet/service/TunnelService.java b/org.restlet/src/main/java/org/restlet/service/TunnelService.java index 38b38d40dc..83565db487 100644 --- a/org.restlet/src/main/java/org/restlet/service/TunnelService.java +++ b/org.restlet/src/main/java/org/restlet/service/TunnelService.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; import org.restlet.Context; @@ -17,12 +16,12 @@ import org.restlet.routing.Filter; /** - * Application service tunneling request method or client preferences. The - * tunneling can use query parameters, file-like extensions, and specific - * headers. This is particularly useful for browser-based applications that - * can't fully control the HTTP requests sent.
+ * Application service tunneling request method or client preferences. The tunneling can use query + * parameters, file-like extensions, and specific headers. This is particularly useful for + * browser-based applications that can't fully control the HTTP requests sent.
*
* Here is the list of the default parameter names supported: + * * * * @@ -78,427 +77,417 @@ * MOVE, etc.). * *
list of the default parameter names supported
+ * *
- * The client preferences can also be updated according to the user agent - * properties (its name, version, operating system, or other) available via the - * {@link ClientInfo#getAgentAttributes()} method. Check the - * {@link #isUserAgentTunnel()} method.
+ * The client preferences can also be updated according to the user agent properties (its name, + * version, operating system, or other) available via the {@link ClientInfo#getAgentAttributes()} + * method. Check the {@link #isUserAgentTunnel()} method.
*
- * The list of new media type preferences is loaded from a property file called - * "accept.properties" located in the classpath in the subdirectory - * "org/restlet/service". This property file is composed of blocks of - * properties. One "block" of properties starts either with the beginning of the - * properties file or with the end of the previous block. One block ends with - * the "acceptNew" property which contains the value of the new Accept header. - * Here is a sample block.
- * + * The list of new media type preferences is loaded from a property file called "accept.properties" + * located in the classpath in the subdirectory "org/restlet/service". This property file is + * composed of blocks of properties. One "block" of properties starts either with the beginning of + * the properties file or with the end of the previous block. One block ends with the "acceptNew" + * property which contains the value of the new Accept header. Here is a sample block.
+ * *

  * agentName: firefox
  * acceptOld: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,\*\/\*;q=0.5
  * acceptNew: application/xhtml+xml,text/html,text/xml;q=0.9,application/xml;q=0.9,text/plain;q=0.8,image/png,\*\/\*;q=0.5
  * 
- * - * Each declared property is a condition that must be filled to update - * the client preferences. For example, "agentName: firefox" expresses the fact - * this block concerns only "firefox" clients.
+ * + * Each declared property is a condition that must be filled to update the client preferences. For + * example, "agentName: firefox" expresses the fact this block concerns only "firefox" clients.
*
- * The "acceptOld" property allows checking the value of the current "Accept" - * header. If it equals to the value of the "acceptOld" property or if the - * "acceptOld" property is empty, then the preferences will be updated. This can - * be useful for AJAX clients that look like their browser (same agentName, - * agentVersion, etc.) but can provide their own "Accept" header. - * + * The "acceptOld" property allows checking the value of the current "Accept" header. If it equals + * to the value of the "acceptOld" property or if the "acceptOld" property is empty, then the + * preferences will be updated. This can be useful for AJAX clients that look like their browser + * (same agentName, agentVersion, etc.) but can provide their own "Accept" header. + * * @author Jerome Louvel */ public class TunnelService extends Service { - /** The name of the parameter containing the accepted character set. */ - private volatile String characterSetParameter; - - /** The name of the parameter containing the accepted encoding. */ - private volatile String encodingParameter; - - /** - * Indicates if the client preferences can be tunneled via file-like extensions. - */ - private volatile boolean extensionsTunnel; - - /** Indicates if the method can be tunneled via the header. */ - private volatile boolean headersTunnel; - - /** The name of the parameter containing the accepted language. */ - private volatile String languageParameter; - - /** The name of the parameter containing the accepted media type. */ - private volatile String mediaTypeParameter; - - /** The name of the header that contains the method name. */ - private volatile String methodHeader; - - /** The name of the parameter containing the method name. */ - private volatile String methodParameter; - - /** Indicates if the method name can be tunneled. */ - private volatile boolean methodTunnel; - - /** Indicates if the client preferences can be tunneled. */ - private volatile boolean preferencesTunnel; - - /** - * Indicates if the method and client preferences can be tunneled via query - * parameters. - */ - private volatile boolean queryTunnel; - - /** - * Indicates if the client preferences can be tunneled via the user agent - * string. - */ - private volatile boolean userAgentTunnel; - - /** - * Constructor that enables the query tunnel and disables the extensions and - * user agent tunnels. - * - * @param methodTunnel Indicates if the method name can be tunneled. - * @param preferencesTunnel Indicates if the client preferences can be tunneled - * by query parameters or file-like extensions or user - * agent string. - */ - public TunnelService(boolean methodTunnel, boolean preferencesTunnel) { - this(true, methodTunnel, preferencesTunnel); - } - - /** - * Constructor that enables the query tunnel and disables the extensions and - * user agent tunnels. - * - * @param enabled True if the service has been enabled. - * @param methodTunnel Indicates if the method name can be tunneled. - * @param preferencesTunnel Indicates if the client preferences can be tunneled - * by query parameters or file-like extensions or user - * agent string. - */ - public TunnelService(boolean enabled, boolean methodTunnel, boolean preferencesTunnel) { - this(enabled, methodTunnel, preferencesTunnel, true, false); - } - - /** - * Constructor that disables the user agent tunnel. - * - * @param enabled True if the service has been enabled. - * @param methodTunnel Indicates if the method can be tunneled using a - * query parameter. - * @param preferencesTunnel Indicates if the client preferences can be tunneled - * using query parameters or file-like extensions or - * user agent string. - * @param queryTunnel Indicates if tunneling can use query parameters. - * @param extensionsTunnel Indicates if tunneling can use file-like extensions. - */ - public TunnelService(boolean enabled, boolean methodTunnel, boolean preferencesTunnel, boolean queryTunnel, - boolean extensionsTunnel) { - this(enabled, methodTunnel, preferencesTunnel, queryTunnel, extensionsTunnel, false); - } - - /** - * Constructor that enables the header tunneling. - * - * @param enabled True if the service has been enabled. - * @param methodTunnel Indicates if the method can be tunneled using a - * query parameter. - * @param preferencesTunnel Indicates if the client preferences can be tunneled - * using query parameters or file-like extensions or - * user agent string. - * @param queryTunnel Indicates if tunneling can use query parameters. - * @param extensionsTunnel Indicates if tunneling can use file-like extensions. - * @param userAgentTunnel Indicates if tunneling can use user agent string. - */ - public TunnelService(boolean enabled, boolean methodTunnel, boolean preferencesTunnel, boolean queryTunnel, - boolean extensionsTunnel, boolean userAgentTunnel) { - this(enabled, methodTunnel, preferencesTunnel, queryTunnel, extensionsTunnel, userAgentTunnel, true); - } - - /** - * Constructor. - * - * @param enabled True if the service has been enabled. - * @param methodTunnel Indicates if the method can be tunneled using a - * query parameter. - * @param preferencesTunnel Indicates if the client preferences can be tunneled - * using query parameters or file-like extensions or - * user agent string. - * @param queryTunnel Indicates if tunneling can use query parameters. - * @param extensionsTunnel Indicates if tunneling can use file-like extensions. - * @param userAgentTunnel Indicates if tunneling can use user agent string. - * @param headersTunnel Indicates if method can be tunneled via a specific - * header. - */ - public TunnelService(boolean enabled, boolean methodTunnel, boolean preferencesTunnel, boolean queryTunnel, - boolean extensionsTunnel, boolean userAgentTunnel, boolean headersTunnel) { - super(enabled); - - this.extensionsTunnel = extensionsTunnel; - this.methodTunnel = methodTunnel; - this.preferencesTunnel = preferencesTunnel; - this.queryTunnel = queryTunnel; - this.userAgentTunnel = userAgentTunnel; - this.headersTunnel = headersTunnel; - - this.characterSetParameter = "charset"; - this.encodingParameter = "encoding"; - this.languageParameter = "language"; - this.mediaTypeParameter = "media"; - this.methodParameter = "method"; - this.methodHeader = HeaderConstants.HEADER_X_HTTP_METHOD_OVERRIDE; - } - - /** - * Indicates if the request from a given client can be tunneled. The default - * implementation always returns true. This could be customized to restrict the - * usage of the tunnel service. - * - * @param client The client to test. - * @return True if the request from a given client can be tunneled. - */ - public boolean allowClient(ClientInfo client) { - return true; - } - - @Override - public Filter createInboundFilter(Context context) { - return new TunnelFilter(context); - } - - /** - * Returns the character set parameter name. - * - * @return The character set parameter name. - */ - public String getCharacterSetParameter() { - return this.characterSetParameter; - } - - /** - * Returns the name of the parameter containing the accepted encoding. - * - * @return The name of the parameter containing the accepted encoding. - */ - public String getEncodingParameter() { - return this.encodingParameter; - } - - /** - * Returns the name of the parameter containing the accepted language. - * - * @return The name of the parameter containing the accepted language. - */ - public String getLanguageParameter() { - return this.languageParameter; - } - - /** - * Returns the name of the parameter containing the accepted media type. - * - * @return The name of the parameter containing the accepted media type. - */ - public String getMediaTypeParameter() { - return this.mediaTypeParameter; - } - - /** - * Returns the name of the header containing the method name. - * - * @return the name of the header containing the method name. - */ - public String getMethodHeader() { - return methodHeader; - } - - /** - * Returns the method parameter name. - * - * @return The method parameter name. - */ - public String getMethodParameter() { - return this.methodParameter; - } - - /** - * Indicates if the client preferences can be tunneled via the extensions. - * Returns false by default. - * - * @return True if the client preferences can be tunneled via the extensions - * @see Request#getOriginalRef() - */ - public boolean isExtensionsTunnel() { - return this.extensionsTunnel; - } - - /** - * Indicates if the method can be tunneled via the header. Returns true by - * default. - * - * @return True if the method can be tunneled via the header. - */ - public boolean isHeadersTunnel() { - return headersTunnel; - } - - /** - * Indicates if the method name can be tunneled. Returns true by default. - * - * @return True if the method name can be tunneled. - */ - public boolean isMethodTunnel() { - return this.methodTunnel; - } - - /** - * Indicates if the client preferences can be tunneled via the query parameters - * or via file extensions. Returns true by default. - * - * @return True if the client preferences can be tunneled. - */ - public boolean isPreferencesTunnel() { - return this.preferencesTunnel; - } - - /** - * Indicates if the method and client preferences can be tunneled via query - * parameters or file extensions. Returns true by default. - * - * @return True if the method and client preferences can be tunneled. - */ - public boolean isQueryTunnel() { - return this.queryTunnel; - } - - /** - * Indicates if the client preferences can be tunneled according to the user - * agent. Returns false by default. - * - * @return True if the client preferences can be tunneled according to the user - * agent. - */ - public boolean isUserAgentTunnel() { - return this.userAgentTunnel; - } - - /** - * Sets the character set parameter name. - * - * @param parameterName The character set parameter name. - */ - public void setCharacterSetParameter(String parameterName) { - this.characterSetParameter = parameterName; - } - - /** - * Sets the name of the parameter containing the accepted encoding. - * - * @param parameterName The name of the parameter containing the accepted - * encoding. - */ - public void setEncodingParameter(String parameterName) { - this.encodingParameter = parameterName; - } - - /** - * Indicates if the client preferences can be tunneled via the extensions. - * - * @param extensionTunnel True if the client preferences can be tunneled via the - * extensions. - * @see Request#getOriginalRef() - */ - public void setExtensionsTunnel(boolean extensionTunnel) { - this.extensionsTunnel = extensionTunnel; - } - - /** - * Indicates if the method can be tunneled via the header. - * - * @param headersTunnel True if the method can be tunneled via the header. - */ - public void setHeadersTunnel(boolean headersTunnel) { - this.headersTunnel = headersTunnel; - } - - /** - * Sets the name of the parameter containing the accepted language. - * - * @param parameterName The name of the parameter containing the accepted - * language. - */ - public void setLanguageParameter(String parameterName) { - this.languageParameter = parameterName; - } - - /** - * Sets the name of the parameter containing the accepted media type. - * - * @param parameterName The name of the parameter containing the accepted media - * type. - */ - public void setMediaTypeParameter(String parameterName) { - this.mediaTypeParameter = parameterName; - } - - /** - * Sets the name of the header containing the method name. - * - * @param methodHeader The name of the header containing the method name. - */ - public void setMethodHeader(String methodHeader) { - this.methodHeader = methodHeader; - } - - /** - * Sets the method parameter name. - * - * @param parameterName The method parameter name. - */ - public void setMethodParameter(String parameterName) { - this.methodParameter = parameterName; - } - - /** - * Indicates if the method name can be tunneled. - * - * @param methodTunnel True if the method name can be tunneled. - */ - public void setMethodTunnel(boolean methodTunnel) { - this.methodTunnel = methodTunnel; - } - - /** - * Indicates if the client preferences can be tunneled via the query parameters. - * - * @param preferencesTunnel True if the client preferences can be tunneled via - * the query parameters. - */ - public void setPreferencesTunnel(boolean preferencesTunnel) { - this.preferencesTunnel = preferencesTunnel; - } - - /** - * Indicates if the method and client preferences can be tunneled via query - * parameters. - * - * @param queryTunnel True if the method and client preferences can be tunneled - * via query parameters. - */ - public void setQueryTunnel(boolean queryTunnel) { - this.queryTunnel = queryTunnel; - } - - /** - * Indicates if the client preferences can be tunneled according to the user - * agent. - * - * @param userAgentTunnel True if the client preferences can be tunneled - * according to the user agent. - */ - public void setUserAgentTunnel(boolean userAgentTunnel) { - this.userAgentTunnel = userAgentTunnel; - } + /** The name of the parameter containing the accepted character set. */ + private volatile String characterSetParameter; + + /** The name of the parameter containing the accepted encoding. */ + private volatile String encodingParameter; + + /** Indicates if the client preferences can be tunneled via file-like extensions. */ + private volatile boolean extensionsTunnel; + + /** Indicates if the method can be tunneled via the header. */ + private volatile boolean headersTunnel; + + /** The name of the parameter containing the accepted language. */ + private volatile String languageParameter; + + /** The name of the parameter containing the accepted media type. */ + private volatile String mediaTypeParameter; + + /** The name of the header that contains the method name. */ + private volatile String methodHeader; + + /** The name of the parameter containing the method name. */ + private volatile String methodParameter; + + /** Indicates if the method name can be tunneled. */ + private volatile boolean methodTunnel; + + /** Indicates if the client preferences can be tunneled. */ + private volatile boolean preferencesTunnel; + + /** Indicates if the method and client preferences can be tunneled via query parameters. */ + private volatile boolean queryTunnel; + + /** Indicates if the client preferences can be tunneled via the user agent string. */ + private volatile boolean userAgentTunnel; + + /** + * Constructor that enables the query tunnel and disables the extensions and user agent tunnels. + * + * @param methodTunnel Indicates if the method name can be tunneled. + * @param preferencesTunnel Indicates if the client preferences can be tunneled by query + * parameters or file-like extensions or user agent string. + */ + public TunnelService(boolean methodTunnel, boolean preferencesTunnel) { + this(true, methodTunnel, preferencesTunnel); + } + + /** + * Constructor that enables the query tunnel and disables the extensions and user agent tunnels. + * + * @param enabled True if the service has been enabled. + * @param methodTunnel Indicates if the method name can be tunneled. + * @param preferencesTunnel Indicates if the client preferences can be tunneled by query + * parameters or file-like extensions or user agent string. + */ + public TunnelService(boolean enabled, boolean methodTunnel, boolean preferencesTunnel) { + this(enabled, methodTunnel, preferencesTunnel, true, false); + } + + /** + * Constructor that disables the user agent tunnel. + * + * @param enabled True if the service has been enabled. + * @param methodTunnel Indicates if the method can be tunneled using a query parameter. + * @param preferencesTunnel Indicates if the client preferences can be tunneled using query + * parameters or file-like extensions or user agent string. + * @param queryTunnel Indicates if tunneling can use query parameters. + * @param extensionsTunnel Indicates if tunneling can use file-like extensions. + */ + public TunnelService( + boolean enabled, + boolean methodTunnel, + boolean preferencesTunnel, + boolean queryTunnel, + boolean extensionsTunnel) { + this(enabled, methodTunnel, preferencesTunnel, queryTunnel, extensionsTunnel, false); + } + + /** + * Constructor that enables the header tunneling. + * + * @param enabled True if the service has been enabled. + * @param methodTunnel Indicates if the method can be tunneled using a query parameter. + * @param preferencesTunnel Indicates if the client preferences can be tunneled using query + * parameters or file-like extensions or user agent string. + * @param queryTunnel Indicates if tunneling can use query parameters. + * @param extensionsTunnel Indicates if tunneling can use file-like extensions. + * @param userAgentTunnel Indicates if tunneling can use user agent string. + */ + public TunnelService( + boolean enabled, + boolean methodTunnel, + boolean preferencesTunnel, + boolean queryTunnel, + boolean extensionsTunnel, + boolean userAgentTunnel) { + this( + enabled, + methodTunnel, + preferencesTunnel, + queryTunnel, + extensionsTunnel, + userAgentTunnel, + true); + } + + /** + * Constructor. + * + * @param enabled True if the service has been enabled. + * @param methodTunnel Indicates if the method can be tunneled using a query parameter. + * @param preferencesTunnel Indicates if the client preferences can be tunneled using query + * parameters or file-like extensions or user agent string. + * @param queryTunnel Indicates if tunneling can use query parameters. + * @param extensionsTunnel Indicates if tunneling can use file-like extensions. + * @param userAgentTunnel Indicates if tunneling can use user agent string. + * @param headersTunnel Indicates if method can be tunneled via a specific header. + */ + public TunnelService( + boolean enabled, + boolean methodTunnel, + boolean preferencesTunnel, + boolean queryTunnel, + boolean extensionsTunnel, + boolean userAgentTunnel, + boolean headersTunnel) { + super(enabled); + + this.extensionsTunnel = extensionsTunnel; + this.methodTunnel = methodTunnel; + this.preferencesTunnel = preferencesTunnel; + this.queryTunnel = queryTunnel; + this.userAgentTunnel = userAgentTunnel; + this.headersTunnel = headersTunnel; + + this.characterSetParameter = "charset"; + this.encodingParameter = "encoding"; + this.languageParameter = "language"; + this.mediaTypeParameter = "media"; + this.methodParameter = "method"; + this.methodHeader = HeaderConstants.HEADER_X_HTTP_METHOD_OVERRIDE; + } + + /** + * Indicates if the request from a given client can be tunneled. The default implementation + * always returns true. This could be customized to restrict the usage of the tunnel service. + * + * @param client The client to test. + * @return True if the request from a given client can be tunneled. + */ + public boolean allowClient(ClientInfo client) { + return true; + } + + @Override + public Filter createInboundFilter(Context context) { + return new TunnelFilter(context); + } + + /** + * Returns the character set parameter name. + * + * @return The character set parameter name. + */ + public String getCharacterSetParameter() { + return this.characterSetParameter; + } + + /** + * Returns the name of the parameter containing the accepted encoding. + * + * @return The name of the parameter containing the accepted encoding. + */ + public String getEncodingParameter() { + return this.encodingParameter; + } + + /** + * Returns the name of the parameter containing the accepted language. + * + * @return The name of the parameter containing the accepted language. + */ + public String getLanguageParameter() { + return this.languageParameter; + } + + /** + * Returns the name of the parameter containing the accepted media type. + * + * @return The name of the parameter containing the accepted media type. + */ + public String getMediaTypeParameter() { + return this.mediaTypeParameter; + } + + /** + * Returns the name of the header containing the method name. + * + * @return the name of the header containing the method name. + */ + public String getMethodHeader() { + return methodHeader; + } + + /** + * Returns the method parameter name. + * + * @return The method parameter name. + */ + public String getMethodParameter() { + return this.methodParameter; + } + + /** + * Indicates if the client preferences can be tunneled via the extensions. Returns false by + * default. + * + * @return True if the client preferences can be tunneled via the extensions + * @see Request#getOriginalRef() + */ + public boolean isExtensionsTunnel() { + return this.extensionsTunnel; + } + + /** + * Indicates if the method can be tunneled via the header. Returns true by default. + * + * @return True if the method can be tunneled via the header. + */ + public boolean isHeadersTunnel() { + return headersTunnel; + } + + /** + * Indicates if the method name can be tunneled. Returns true by default. + * + * @return True if the method name can be tunneled. + */ + public boolean isMethodTunnel() { + return this.methodTunnel; + } + + /** + * Indicates if the client preferences can be tunneled via the query parameters or via file + * extensions. Returns true by default. + * + * @return True if the client preferences can be tunneled. + */ + public boolean isPreferencesTunnel() { + return this.preferencesTunnel; + } + + /** + * Indicates if the method and client preferences can be tunneled via query parameters or file + * extensions. Returns true by default. + * + * @return True if the method and client preferences can be tunneled. + */ + public boolean isQueryTunnel() { + return this.queryTunnel; + } + + /** + * Indicates if the client preferences can be tunneled according to the user agent. Returns + * false by default. + * + * @return True if the client preferences can be tunneled according to the user agent. + */ + public boolean isUserAgentTunnel() { + return this.userAgentTunnel; + } + + /** + * Sets the character set parameter name. + * + * @param parameterName The character set parameter name. + */ + public void setCharacterSetParameter(String parameterName) { + this.characterSetParameter = parameterName; + } + + /** + * Sets the name of the parameter containing the accepted encoding. + * + * @param parameterName The name of the parameter containing the accepted encoding. + */ + public void setEncodingParameter(String parameterName) { + this.encodingParameter = parameterName; + } + + /** + * Indicates if the client preferences can be tunneled via the extensions. + * + * @param extensionTunnel True if the client preferences can be tunneled via the extensions. + * @see Request#getOriginalRef() + */ + public void setExtensionsTunnel(boolean extensionTunnel) { + this.extensionsTunnel = extensionTunnel; + } + + /** + * Indicates if the method can be tunneled via the header. + * + * @param headersTunnel True if the method can be tunneled via the header. + */ + public void setHeadersTunnel(boolean headersTunnel) { + this.headersTunnel = headersTunnel; + } + + /** + * Sets the name of the parameter containing the accepted language. + * + * @param parameterName The name of the parameter containing the accepted language. + */ + public void setLanguageParameter(String parameterName) { + this.languageParameter = parameterName; + } + + /** + * Sets the name of the parameter containing the accepted media type. + * + * @param parameterName The name of the parameter containing the accepted media type. + */ + public void setMediaTypeParameter(String parameterName) { + this.mediaTypeParameter = parameterName; + } + + /** + * Sets the name of the header containing the method name. + * + * @param methodHeader The name of the header containing the method name. + */ + public void setMethodHeader(String methodHeader) { + this.methodHeader = methodHeader; + } + + /** + * Sets the method parameter name. + * + * @param parameterName The method parameter name. + */ + public void setMethodParameter(String parameterName) { + this.methodParameter = parameterName; + } + + /** + * Indicates if the method name can be tunneled. + * + * @param methodTunnel True if the method name can be tunneled. + */ + public void setMethodTunnel(boolean methodTunnel) { + this.methodTunnel = methodTunnel; + } + + /** + * Indicates if the client preferences can be tunneled via the query parameters. + * + * @param preferencesTunnel True if the client preferences can be tunneled via the query + * parameters. + */ + public void setPreferencesTunnel(boolean preferencesTunnel) { + this.preferencesTunnel = preferencesTunnel; + } + + /** + * Indicates if the method and client preferences can be tunneled via query parameters. + * + * @param queryTunnel True if the method and client preferences can be tunneled via query + * parameters. + */ + public void setQueryTunnel(boolean queryTunnel) { + this.queryTunnel = queryTunnel; + } + + /** + * Indicates if the client preferences can be tunneled according to the user agent. + * + * @param userAgentTunnel True if the client preferences can be tunneled according to the user + * agent. + */ + public void setUserAgentTunnel(boolean userAgentTunnel) { + this.userAgentTunnel = userAgentTunnel; + } } diff --git a/org.restlet/src/main/java/org/restlet/service/package-info.java b/org.restlet/src/main/java/org/restlet/service/package-info.java new file mode 100644 index 0000000000..c61859f763 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/service/package-info.java @@ -0,0 +1,9 @@ +/** + * Services used by applications and components. + * + * @since Restlet 1.0 + * @see User + * Guide - Service package + */ +package org.restlet.service; diff --git a/org.restlet/src/main/java/org/restlet/service/package.html b/org.restlet/src/main/java/org/restlet/service/package.html deleted file mode 100644 index 5c2be8f415..0000000000 --- a/org.restlet/src/main/java/org/restlet/service/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - - Services used by applications and components. -

- @since Restlet 1.0 - @see User Guide - Service package - - diff --git a/org.restlet/src/main/java/org/restlet/util/ClientList.java b/org.restlet/src/main/java/org/restlet/util/ClientList.java index 7e88b4883d..58209f767d 100644 --- a/org.restlet/src/main/java/org/restlet/util/ClientList.java +++ b/org.restlet/src/main/java/org/restlet/util/ClientList.java @@ -1,78 +1,76 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; +import java.util.concurrent.CopyOnWriteArrayList; import org.restlet.Client; import org.restlet.Context; import org.restlet.data.Protocol; -import java.util.concurrent.CopyOnWriteArrayList; - /** * Modifiable list of client connectors. - * + * * @author Jerome Louvel */ public final class ClientList extends WrapperList { - /** The context. */ - private volatile Context context; + /** The context. */ + private volatile Context context; - /** - * Constructor. - * - * @param context The context. - */ - public ClientList(Context context) { - super(new CopyOnWriteArrayList()); - this.context = context; - } + /** + * Constructor. + * + * @param context The context. + */ + public ClientList(Context context) { + super(new CopyOnWriteArrayList<>()); + this.context = context; + } - @Override - public boolean add(Client client) { - // Set the client's context, if the client does not have already one. - if (client.getContext() == null) { - client.setContext(getContext().createChildContext()); - } + @Override + public boolean add(Client client) { + // Set the client's context if the client does not already have one. + if (client.getContext() == null) { + client.setContext(getContext().createChildContext()); + } - return super.add(client); - } + return super.add(client); + } - /** - * Adds a new client connector in the map supporting the given protocol. - * - * @param protocol The connector protocol. - * @return The added client. - */ - public Client add(Protocol protocol) { - final Client result = new Client(protocol); - result.setContext(getContext().createChildContext()); - add(result); - return result; - } + /** + * Adds a new client connector in the map supporting the given protocol. + * + * @param protocol The connector protocol. + * @return The added client. + */ + public Client add(Protocol protocol) { + final Client result = new Client(protocol); + result.setContext(getContext().createChildContext()); + add(result); + return result; + } - /** - * Returns the context. - * - * @return The context. - */ - public Context getContext() { - return this.context; - } + /** + * Returns the context. + * + * @return The context. + */ + public Context getContext() { + return this.context; + } - /** - * Sets the context. - * - * @param context The context. - */ - public void setContext(Context context) { - this.context = context; - } + /** + * Sets the context. + * + * @param context The context. + */ + public void setContext(Context context) { + this.context = context; + } } diff --git a/org.restlet/src/main/java/org/restlet/util/NamedValue.java b/org.restlet/src/main/java/org/restlet/util/NamedValue.java index 6c86411423..72072a27bd 100644 --- a/org.restlet/src/main/java/org/restlet/util/NamedValue.java +++ b/org.restlet/src/main/java/org/restlet/util/NamedValue.java @@ -1,40 +1,38 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; /** * String couple between a name and a value. - * + * * @author Jerome Louvel */ public interface NamedValue { - /** - * Returns the name of this parameter. - * - * @return The name of this parameter. - */ - abstract String getName(); - - /** - * Returns the value. - * - * @return The value. - */ - abstract V getValue(); + /** + * Returns the name of this parameter. + * + * @return The name of this parameter. + */ + String getName(); - /** - * Sets the value. - * - * @param value The value. - */ - abstract void setValue(V value); + /** + * Returns the value. + * + * @return The value. + */ + V getValue(); + /** + * Sets the value. + * + * @param value The value. + */ + void setValue(V value); } diff --git a/org.restlet/src/main/java/org/restlet/util/NamedValuesCollector.java b/org.restlet/src/main/java/org/restlet/util/NamedValuesCollector.java new file mode 100644 index 0000000000..6e2758c6fd --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/util/NamedValuesCollector.java @@ -0,0 +1,85 @@ +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. + */ +package org.restlet.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Collects named values inside a given map. Only the values related to the existing keys will be + * added. + * + *

In case of multiple values for the same name, the map will contain a list of values. + */ +public class NamedValuesCollector { + + private final Map collector; + + /** + * Constructor. + * + * @param mapToUpdate The map to update + */ + public NamedValuesCollector(final Map mapToUpdate) { + this.collector = mapToUpdate; + } + + /** + * Constructor. + * + * @param names The list of names to collect. + */ + public NamedValuesCollector(final String... names) { + this.collector = new HashMap<>(); + for (String name : names) { + collector.put(name, null); + } + } + + @SuppressWarnings("unchecked") + public void collect(final NamedValue parameter) { + if (collector.containsKey(parameter.getName())) { + Object currentValue = collector.get(parameter.getName()); + + if (currentValue != null) { + List values; + + if (currentValue instanceof List) { // Multiple values already found for this entry + values = (List) currentValue; + } else { + // Second value found for this entry + // Create a list of values + values = new ArrayList<>(); + values.add(currentValue); + collector.put(parameter.getName(), values); + } + + values.add( + parameter.getValue() == null ? Series.EMPTY_VALUE : parameter.getValue()); + } else { + collector.put( + parameter.getName(), + parameter.getValue() == null ? Series.EMPTY_VALUE : parameter.getValue()); + } + } + } + + /** + * Returns either the map transmitted to the constructor or a new map containing the collected + * values. + * + * @return Either the map transmitted to the constructor or a new map containing the collected + * values + */ + public Map getCollectedValues() { + return collector; + } +} diff --git a/org.restlet/src/main/java/org/restlet/util/NonNullItemsList.java b/org.restlet/src/main/java/org/restlet/util/NonNullItemsList.java new file mode 100644 index 0000000000..34b300cc21 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/util/NonNullItemsList.java @@ -0,0 +1,94 @@ +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. + */ +package org.restlet.util; + +import java.util.Collection; +import java.util.Objects; + +/** + * A list that prevents adding null elements. + * + * @param + */ +public class NonNullItemsList extends WrapperList { + + private final String exceptionMessage; + + public NonNullItemsList(final String exceptionMessage) { + this.exceptionMessage = exceptionMessage; + } + + @Override + public boolean add(final T element) { + validate(element); + return super.add(element); + } + + @Override + public void add(final int index, final T element) { + validate(element); + super.add(index, element); + } + + @Override + public void addFirst(final T element) { + validate(element); + super.addFirst(element); + } + + @Override + public void addLast(final T element) { + validate(element); + super.addLast(element); + } + + @Override + public boolean addAll(final Collection elements) { + validate(elements); + return super.addAll(elements); + } + + @Override + public boolean addAll(final int index, final Collection elements) { + validate(elements); + return super.addAll(index, elements); + } + + @Override + public boolean equals(final Object obj) { + return super.equals(obj) + && Objects.equals(exceptionMessage, ((NonNullItemsList) obj).exceptionMessage); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + private void validate(final T element) { + if (element == null) { + throw new IllegalArgumentException(this.exceptionMessage); + } + } + + private void validate(final Collection elements) { + boolean addNull = (elements == null); + if (!addNull) { + for (T element : elements) { + if (element == null) { + addNull = true; + break; + } + } + } + if (addNull) { + throw new IllegalArgumentException(exceptionMessage); + } + } +} diff --git a/org.restlet/src/main/java/org/restlet/util/Resolver.java b/org.restlet/src/main/java/org/restlet/util/Resolver.java index 48002b277e..41571c8328 100644 --- a/org.restlet/src/main/java/org/restlet/util/Resolver.java +++ b/org.restlet/src/main/java/org/restlet/util/Resolver.java @@ -1,27 +1,25 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; +import java.util.Map; import org.restlet.Request; import org.restlet.Response; import org.restlet.engine.util.CallResolver; import org.restlet.engine.util.MapResolver; -import java.util.Map; - /** - * Resolves a name into a value. By default, the {@link #createResolver(Map)} - * static method can adapt a Java map into a resolver. Another useful method is - * {@link #createResolver(Request, Response)}, which can expose a Restlet call - * into a compact data model, with the following variables: - * + * Resolves a name into a value. By default, the {@link #createResolver(Map)} static method can + * adapt a Java map into a resolver. Another useful method is {@link #createResolver(Request, + * Response)}, which can expose a Restlet call into a compact data model, with the following + * variables: + * * * * @@ -200,12 +198,12 @@ * * *
list of supported variables
Integer
+ * *
- * - * Below is the list of name sub-parts, for Reference variables, that can - * replace the asterix in the variable names above:
+ * Below is the list of name sub-parts, for Reference variables, that can replace the asterix in the + * variable names above:
*
- * + * * * @@ -265,40 +263,39 @@ * * *
list of name sub-parts, for Reference variables, that can replace * the asterix in the variable names above
String
- * + * * @author Jerome Louvel */ public abstract class Resolver { - /** - * Creates a resolver that is based on a given map. - * - * @param map Map between names and values. - * @return The map resolver. - */ - public static Resolver createResolver(Map map) { - return new MapResolver(map); - } - - /** - * Creates a resolver that is based on a call (request, response couple). It - * first looks up the response attributes, then the request attributes and - * finally the variables listed in this class Javadocs above. - * - * @param request The request. - * @param response The response. - * @return The call resolver. - */ - public static Resolver createResolver(Request request, Response response) { - return new CallResolver(request, response); - } + /** + * Creates a resolver based on a given map. + * + * @param map Map between names and values. + * @return The map resolver. + */ + public static Resolver createResolver(Map map) { + return new MapResolver(map); + } - /** - * Resolves a name into a value. - * - * @param name The name to resolve. - * @return The resolved value. - */ - public abstract T resolve(String name); + /** + * Creates a resolver based on a call (request, response couple). It first looks up the response + * attributes, then the request attributes, and finally the variables listed in this class + * Javadocs above. + * + * @param request The request. + * @param response The response. + * @return The call resolver. + */ + public static Resolver createResolver(Request request, Response response) { + return new CallResolver(request, response); + } + /** + * Resolves a name into a value. + * + * @param name The name to resolve. + * @return The resolved value. + */ + public abstract T resolve(String name); } diff --git a/org.restlet/src/main/java/org/restlet/util/RouteList.java b/org.restlet/src/main/java/org/restlet/util/RouteList.java index 7b4c234ff5..bf3eefd69f 100644 --- a/org.restlet/src/main/java/org/restlet/util/RouteList.java +++ b/org.restlet/src/main/java/org/restlet/util/RouteList.java @@ -1,214 +1,210 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; -import org.restlet.Request; -import org.restlet.Response; -import org.restlet.Restlet; -import org.restlet.routing.Route; - import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadLocalRandom; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.routing.Route; /** - * Modifiable list of routes with some helper methods. Note that this class - * implements the {@link List} interface using the Route class as the generic - * type. This allows you to use an instance of this class as any other - * {@link List}, in particular all the helper methods in - * {@link Collections}.
+ * Modifiable list of routes with some helper methods. Note that this class implements the {@link + * List} interface using the Route class as the generic type. This allows you to use an instance of + * this class as any other {@link List}, in particular all the helper methods in {@link + * Collections}.
*
- * Note that structural changes to this list are thread-safe, using an - * underlying {@link CopyOnWriteArrayList}. - * + * Note that structural changes to this list are thread-safe, using an underlying {@link + * CopyOnWriteArrayList}. + * * @author Jerome Louvel * @see java.util.Collections * @see java.util.List */ public final class RouteList extends WrapperList { - /** The index of the last route used in the round-robin mode. */ - private volatile int lastIndex; - - /** - * Constructor. - */ - public RouteList() { - super(new CopyOnWriteArrayList<>()); - this.lastIndex = -1; - } - - /** - * Constructor. - * - * @param delegate The delegate list. - */ - public RouteList(List delegate) { - super(new CopyOnWriteArrayList<>(delegate)); - this.lastIndex = -1; - } - - /** - * Returns the best route match for a given call. - * - * @param request The request to score. - * @param response The response to score. - * @param requiredScore The minimum score required to have a match. - * @return The best route match or null. - */ - public Route getBest(Request request, Response response, float requiredScore) { - Route result = null; - float bestScore = 0F; - float score; - - for (Route current : this) { - score = current.score(request, response); - - if ((score > bestScore) && (score >= requiredScore)) { - bestScore = score; - result = current; - } - } - - return result; - } - - /** - * Returns the first route match for a given call. - * - * @param request The request to score. - * @param response The response to score. - * @param requiredScore The minimum score required to have a match. - * @return The first route match or null. - */ - public Route getFirst(Request request, Response response, float requiredScore) { - for (Route current : this) { - if (current.score(request, response) >= requiredScore) { - return current; - } - } - - // No match found - return null; - } - - /** - * Returns the last route match for a given call. - * - * @param request The request to score. - * @param response The response to score. - * @param requiredScore The minimum score required to have a match. - * @return The last route match or null. - */ - public synchronized Route getLast(Request request, Response response, float requiredScore) { - for (int j = size() - 1; (j >= 0); j--) { - final Route route = get(j); - if (route.score(request, response) >= requiredScore) { - return route; - } - } - - // No match found - return null; - } - - /** - * Returns a next route match in a round robin mode for a given call. - * - * @param request The request to score. - * @param response The response to score. - * @param requiredScore The minimum score required to have a match. - * @return A next route or null. - */ - public synchronized Route getNext(Request request, Response response, float requiredScore) { - if (!isEmpty()) { - for (final int initialIndex = this.lastIndex++; initialIndex != this.lastIndex; this.lastIndex++) { - if (this.lastIndex >= size()) { - this.lastIndex = 0; - } - - final Route route = get(this.lastIndex); - if (route.score(request, response) >= requiredScore) { - return route; - } - } - } - - // No match found - return null; - } - - /** - * Returns a random route match for a given call. Note that the current - * implementation doesn't uniformly return routes unless they all score above - * the required score. - * - * @param request The request to score. - * @param response The response to score. - * @param requiredScore The minimum score required to have a match. - * @return A random route or null. - */ - public synchronized Route getRandom(Request request, Response response, float requiredScore) { - int length = size(); - - if (length > 0) { - int j = ThreadLocalRandom.current().nextInt(length); - Route route = get(j); - - if (route.score(request, response) >= requiredScore) { - return route; - } - - boolean loopedAround = false; - - do { - if ((j == length) && !loopedAround) { - j = 0; - loopedAround = true; - } - - route = get(j++); - - if (route.score(request, response) >= requiredScore) { - return route; - } - } while ((j < length) || !loopedAround); - } - - // No match found - return null; - } - - /** - * Removes all routes to a given target. - * - * @param target The target Restlet to detach. - */ - public synchronized void removeAll(Restlet target) { - for (int i = size() - 1; i >= 0; i--) { - if (get(i).getNext() == target) { - remove(i); - } - } - } - - /** - * Returns a view of the portion of this list between the specified fromIndex, - * inclusive, and toIndex, exclusive. - * - * @param fromIndex The start position. - * @param toIndex The end position (exclusive). - * @return The sub-list. - */ - @Override - public RouteList subList(int fromIndex, int toIndex) { - return new RouteList(getDelegate().subList(fromIndex, toIndex)); - } + /** The index of the last route used in the round-robin mode. */ + private volatile int lastIndex; + + /** Constructor. */ + public RouteList() { + super(new CopyOnWriteArrayList<>()); + this.lastIndex = -1; + } + + /** + * Constructor. + * + * @param delegate The delegate list. + */ + public RouteList(List delegate) { + super(new CopyOnWriteArrayList<>(delegate)); + this.lastIndex = -1; + } + + /** + * Returns the best route match for a given call. + * + * @param request The request to score. + * @param response The response to score. + * @param requiredScore The minimum score required to have a match. + * @return The best route match or null. + */ + public Route getBest(Request request, Response response, float requiredScore) { + Route result = null; + float bestScore = 0F; + float score; + + for (Route current : this) { + score = current.score(request, response); + + if ((score > bestScore) && (score >= requiredScore)) { + bestScore = score; + result = current; + } + } + + return result; + } + + /** + * Returns the first route match for a given call. + * + * @param request The request to score. + * @param response The response to score. + * @param requiredScore The minimum score required to have a match. + * @return The first route match or null. + */ + public Route getFirst(Request request, Response response, float requiredScore) { + for (Route current : this) { + if (current.score(request, response) >= requiredScore) { + return current; + } + } + + // No match found + return null; + } + + /** + * Returns the last route match for a given call. + * + * @param request The request to score. + * @param response The response to score. + * @param requiredScore The minimum score required to have a match. + * @return The last route match or null. + */ + public synchronized Route getLast(Request request, Response response, float requiredScore) { + for (int j = size() - 1; (j >= 0); j--) { + final Route route = get(j); + if (route.score(request, response) >= requiredScore) { + return route; + } + } + + // No match found + return null; + } + + /** + * Returns a next route match in a round-robin mode for a given call. + * + * @param request The request to score. + * @param response The response to score. + * @param requiredScore The minimum score required to have a match. + * @return A next route or null. + */ + public synchronized Route getNext(Request request, Response response, float requiredScore) { + if (!isEmpty()) { + for (final int initialIndex = this.lastIndex++; + initialIndex != this.lastIndex; + this.lastIndex++) { + if (this.lastIndex >= size()) { + this.lastIndex = 0; + } + + final Route route = get(this.lastIndex); + if (route.score(request, response) >= requiredScore) { + return route; + } + } + } + + // No match found + return null; + } + + /** + * Returns a random route match for a given call. Note that the current implementation doesn't + * uniformly return routes unless they all score above the required score. + * + * @param request The request to score. + * @param response The response to score. + * @param requiredScore The minimum score required to have a match. + * @return A random route or null. + */ + public synchronized Route getRandom(Request request, Response response, float requiredScore) { + int length = size(); + + if (length > 0) { + int j = ThreadLocalRandom.current().nextInt(length); + Route route = get(j); + + if (route.score(request, response) >= requiredScore) { + return route; + } + + boolean loopedAround = false; + + do { + if ((j == length) && !loopedAround) { + j = 0; + loopedAround = true; + } + + route = get(j++); + + if (route.score(request, response) >= requiredScore) { + return route; + } + } while ((j < length) || !loopedAround); + } + + // No match found + return null; + } + + /** + * Removes all routes to a given target. + * + * @param target The target Restlet to detach. + */ + public synchronized void removeAll(Restlet target) { + for (int i = size() - 1; i >= 0; i--) { + if (get(i).getNext() == target) { + remove(i); + } + } + } + + /** + * Returns a view of the portion of this list between the specified fromIndex, inclusive, and + * toIndex, exclusive. + * + * @param fromIndex The start position. + * @param toIndex The end position (exclusive). + * @return The sub-list. + */ + @Override + public RouteList subList(int fromIndex, int toIndex) { + return new RouteList(getDelegate().subList(fromIndex, toIndex)); + } } diff --git a/org.restlet/src/main/java/org/restlet/util/Series.java b/org.restlet/src/main/java/org/restlet/util/Series.java index e622ed12e8..52b674ceff 100644 --- a/org.restlet/src/main/java/org/restlet/util/Series.java +++ b/org.restlet/src/main/java/org/restlet/util/Series.java @@ -1,25 +1,27 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; -import org.restlet.Context; - -import java.util.*; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.logging.Level; +import org.restlet.Context; /** - * Modifiable list of entries with many helper methods. Note that this class - * uses the Parameter class as the template type. This allows you to use an - * instance of this class as any other java.util.List, in particular all the - * helper methods in java.util.Collections. - * + * Modifiable list of entries with many helper methods. Note that this class uses the Parameter + * class as the template type. This allows you to use an instance of this class as any other + * java.util.List, in particular all the helper methods in java.util.Collections. + * * @author Jerome Louvel * @param The contained type * @see org.restlet.data.Parameter @@ -27,534 +29,493 @@ * @see java.util.List */ public class Series> extends WrapperList { - /** - * A marker for empty values to differentiate from non existing values (null). - */ - public static final Object EMPTY_VALUE = new Object(); - - /** - * Returns an unmodifiable view of the specified series. Attempts to call a - * modification method will throw an UnsupportedOperationException. - * - * @param series The series for which an unmodifiable view should be returned. - * @return The unmodifiable view of the specified series. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static Series unmodifiableSeries(final Series series) { - return new Series(series.entryClass, java.util.Collections.unmodifiableList(series.getDelegate())); - } - - /** The entry class. */ - private final Class entryClass; - - /** - * Constructor. - */ - public Series(Class entryClass) { - super(); - this.entryClass = entryClass; - } - - /** - * Constructor. - * - * @param initialCapacity The initial list capacity. - */ - public Series(Class entryClass, int initialCapacity) { - super(initialCapacity); - this.entryClass = entryClass; - } - - /** - * Constructor. - * - * @param delegate The delegate list. - */ - public Series(Class entryClass, List delegate) { - super(delegate); - this.entryClass = entryClass; - } - - /** - * Creates then adds a parameter at the end of the list. - * - * @param name The parameter name. - * @param value The parameter value. - * @return True (as per the general contract of the Collection.add method). - */ - public boolean add(String name, String value) { - return add(createEntry(name, value)); - } - - /** - * Copies the parameters whose name is a key in the given map.
- * If a matching parameter is found, its value is put in the map.
- * If multiple values are found, a list is created and set in the map. - * - * @param params The map controlling the copy. - */ - @SuppressWarnings("unchecked") - public void copyTo(Map params) { - NamedValue param; - Object currentValue = null; + /** A marker for empty values to differentiate from non-existing values (null). */ + public static final Object EMPTY_VALUE = new Object(); + + /** + * Returns an unmodifiable view of the specified series. Attempts to call a modification method + * will throw an UnsupportedOperationException. + * + * @param series The series for which an unmodifiable view should be returned. + * @return The unmodifiable view of the specified series. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static Series unmodifiableSeries( + final Series series) { + return new Series( + series.entryClass, java.util.Collections.unmodifiableList(series.getDelegate())); + } + + /** The entry class. */ + private final Class entryClass; + + /** Constructor. */ + public Series(Class entryClass) { + super(); + this.entryClass = entryClass; + } + + /** + * Constructor. + * + * @param initialCapacity The initial list capacity. + */ + public Series(Class entryClass, int initialCapacity) { + super(initialCapacity); + this.entryClass = entryClass; + } + + /** + * Constructor. + * + * @param delegate The delegate list. + */ + public Series(Class entryClass, List delegate) { + super(delegate); + this.entryClass = entryClass; + } + + /** + * Creates then adds a parameter at the end of the list. + * + * @param name The parameter name. + * @param value The parameter value. + * @return True (as per the general contract of the {@link java.util.Collection#add(Object)} + * method). + */ + public boolean add(String name, String value) { + return add(createEntry(name, value)); + } + + /** + * Copies the parameters whose name is a key in the given map.
+ * If a matching parameter is found, its value is put in the map.
+ * If multiple values are found, a list is created and set in the map. + * + * @param parameters The map controlling the copy. + */ + public void copyTo(Map parameters) { + NamedValuesCollector collector = new NamedValuesCollector(parameters); for (T t : this) { - param = t; + collector.collect(t); + } + } + + /** + * Creates a new entry. + * + * @param name The name of the entry. + * @param value The value of the entry. + * @return A new entry. + */ + public T createEntry(String name, String value) { + try { + return this.entryClass + .getConstructor(String.class, String.class) + .newInstance(name, value); + } catch (Exception e) { + Context.getCurrentLogger().log(Level.WARNING, "Unable to create a series entry", e); + return null; + } + } + + /** + * Tests the equality of two string, potentially null, which a case sensitivity flag. + * + * @param value1 The first value. + * @param value2 The second value. + * @param ignoreCase Indicates if the test should be case-insensitive. + * @return True if both values are equal. + */ + private boolean equals(String value1, String value2, boolean ignoreCase) { + if ((value1 != null) && (value2 != null)) { + if (ignoreCase) { + return value1.equalsIgnoreCase(value2); + } else { + return value1.equals(value2); + } + } - if (params.containsKey(param.getName())) { - currentValue = params.get(param.getName()); + return false; + } + + /** + * Returns the first parameter found with the given name. + * + * @param name The parameter name (case-sensitive). + * @return The first parameter found with the given name. + */ + public T getFirst(String name) { + return getFirst(name, false); + } + + /** + * Returns the first parameter found with the given name. + * + * @param name The parameter name. + * @param ignoreCase Indicates if the name comparison is case-insensitive. + * @return The first parameter found with the given name. + */ + public T getFirst(String name, boolean ignoreCase) { + for (T param : this) { + if (equals(param.getName(), name, ignoreCase)) { + return param; + } + } - if (currentValue != null) { - List values = null; + return null; + } + + /** + * Returns the value of the first parameter found with the given name. + * + * @param name The parameter name (case-sensitive). + * @return The value of the first parameter found with the given name. + */ + public String getFirstValue(String name) { + return getFirstValue(name, false); + } + + /** + * Returns the value of the first parameter found with the given name. + * + * @param name The parameter name. + * @param ignoreCase Indicates if the name comparison is case-sensitive. + * @return The value of the first parameter found with the given name. + */ + public String getFirstValue(String name, boolean ignoreCase) { + return getFirstValue(name, ignoreCase, null); + } + + /** + * Returns the value of the first parameter found with the given name. + * + * @param name The parameter name. + * @param ignoreCase Indicates if the name comparison is case-sensitive. + * @param defaultValue The default value to return if no matching parameter found or if the + * parameter has a null value. + * @return The value of the first parameter found with the given name or the default value. + */ + public String getFirstValue(String name, boolean ignoreCase, String defaultValue) { + String result = defaultValue; + NamedValue param = getFirst(name, ignoreCase); + + if ((param != null) && (param.getValue() != null)) { + result = param.getValue(); + } - if (currentValue instanceof List) { - // Multiple values already found for this entry - values = (List) currentValue; - } else { - // Second value found for this entry - // Create a list of values - values = new ArrayList(); - values.add(currentValue); - params.put(param.getName(), values); - } + return result; + } + + /** + * Returns the value of the first parameter found with the given name. + * + * @param name The parameter name (case-sensitive). + * @param defaultValue The default value to return if no matching parameter found or if the + * parameter has a null value. + * @return The value of the first parameter found with the given name or the default value. + */ + public String getFirstValue(String name, String defaultValue) { + return getFirstValue(name, false, defaultValue); + } + + /** + * Returns the set of parameter names (case-sensitive). + * + * @return The set of parameter names. + */ + public Set getNames() { + Set result = new HashSet<>(); + + for (NamedValue param : this) { + result.add(param.getName()); + } - if (param.getValue() == null) { - values.add(Series.EMPTY_VALUE); + return result; + } + + /** + * Returns the values of the parameters with a given name. If multiple parameters with the same + * name are found, all values are concatenated and separated by a comma (like for HTTP message + * headers). + * + * @param name The parameter name (case-insensitive). + * @return The values of the parameters with a given name. + */ + public String getValues(String name) { + return getValues(name, ",", true); + } + + /** + * Returns the parameter values with a given name. If multiple parameters with the same name are + * found, all values are concatenated and separated by the given separator. + * + * @param name The parameter name. + * @param separator The separator character. + * @param ignoreCase Indicates if the name comparison is case-sensitive. + * @return The sequence of values. + */ + public String getValues(String name, String separator, boolean ignoreCase) { + String result = null; + StringBuilder sb = null; + + for (final T param : this) { + if ((ignoreCase && param.getName().equalsIgnoreCase(name)) + || param.getName().equals(name)) { + if (sb == null) { + if (result == null) { + result = param.getValue(); } else { - values.add(param.getValue()); + sb = new StringBuilder(); + sb.append(result).append(separator).append(param.getValue()); } } else { - if (param.getValue() == null) { - params.put(param.getName(), Series.EMPTY_VALUE); - } else { - params.put(param.getName(), param.getValue()); - } + sb.append(separator).append(param.getValue()); + } + } + } + + if (sb != null) { + result = sb.toString(); + } + + return result; + } + + /** + * Returns an array of all the values associated with the given parameter name. + * + * @param name The parameter name to match (case-sensitive). + * @return The array of values. + */ + public String[] getValuesArray(String name) { + return getValuesArray(name, false); + } + + /** + * Returns an array of all the values associated with the given parameter name. + * + * @param name The parameter name to match. + * @param ignoreCase Indicates if the name comparison is case-sensitive. + * @return The array of values. + */ + public String[] getValuesArray(String name, boolean ignoreCase) { + return getValuesArray(name, ignoreCase, null); + } + + /** + * Returns an array of all the values associated with the given parameter name. + * + * @param name The parameter name to match. + * @param ignoreCase Indicates if the name comparison is case-sensitive. + * @param defaultValue The default value to return if no matching parameter found or if the + * parameter has a null value. + * @return The array of values. + */ + public String[] getValuesArray(String name, boolean ignoreCase, String defaultValue) { + String[] result = null; + List params = subList(name, ignoreCase); + + if ((params.isEmpty()) && (defaultValue != null)) { + result = new String[1]; + result[0] = defaultValue; + } else { + result = new String[params.size()]; + + for (int i = 0; i < params.size(); i++) { + result[i] = params.get(i).getValue(); + } + } + + return result; + } + + /** + * Returns an array of all the values associated with the given parameter name. + * + * @param name The parameter name to match. + * @param defaultValue The default value to return if no matching parameter found or if the + * parameter has a null value. + * @return The array of values. + */ + public String[] getValuesArray(String name, String defaultValue) { + return getValuesArray(name, false, defaultValue); + } + + /** + * Returns a map of name, value pairs. The order of the map keys is respected based on the + * series order. When a name has multiple values, only the first one is returned. + * + * @return The map of name, value pairs. + */ + public Map getValuesMap() { + Map result = new LinkedHashMap<>(); + + for (NamedValue param : this) { + if (!result.containsKey(param.getName())) { + result.put(param.getName(), param.getValue()); + } + } + + return result; + } + + /** + * Removes all the parameters with a given name. + * + * @param name The parameter name (case-sensitive). + * @return True if the list changed. + */ + public boolean removeAll(String name) { + return removeAll(name, false); + } + + /** + * Removes all the parameters with a given name. + * + * @param name The parameter name. + * @param ignoreCase Indicates if the name comparison is case-insensitive. + * @return True if the list changed. + */ + public boolean removeAll(String name, boolean ignoreCase) { + boolean changed = false; + NamedValue param = null; + + for (Iterator iter = iterator(); iter.hasNext(); ) { + param = iter.next(); + + if (equals(param.getName(), name, ignoreCase)) { + iter.remove(); + changed = true; + } + } + + return changed; + } + + /** + * Removes from this list the first entry whose name equals the specified name ignoring the + * case. + * + * @param name The name of the entries to be removed (case-sensitive). + * @return false if no entry has been removed, true otherwise. + */ + public boolean removeFirst(String name) { + return removeFirst(name, false); + } + + /** + * Removes from this list the first entry whose name equals the specified name ignoring the case + * or not. + * + * @param name The name of the entries to be removed. + * @param ignoreCase Indicates if the name comparison is case-insensitive. + * @return false if no entry has been removed, true otherwise. + */ + public boolean removeFirst(String name, boolean ignoreCase) { + boolean changed = false; + NamedValue param = null; + + for (final Iterator iter = iterator(); iter.hasNext() && !changed; ) { + param = iter.next(); + if (equals(param.getName(), name, ignoreCase)) { + iter.remove(); + changed = true; + } + } + + return changed; + } + + /** + * Replaces the value of the first parameter with the given name and removes all other + * parameters with the same name. The name matching is case-sensitive. + * + * @param name The parameter name. + * @param value The value to set. + * @return The parameter set or added. + */ + public T set(String name, String value) { + return set(name, value, false); + } + + /** + * Replaces the value of the first parameter with the given name and removes all other + * parameters with the same name. + * + * @param name The parameter name. + * @param value The value to set. + * @param ignoreCase Indicates if the name comparison is case-insensitive. + * @return The parameter set or added. + */ + public T set(String name, String value, boolean ignoreCase) { + T result = null; + T param = null; + boolean found = false; + + for (final Iterator iter = iterator(); iter.hasNext(); ) { + param = iter.next(); + + if (equals(param.getName(), name, ignoreCase)) { + if (found) { + // Remove other entries with the same name + iter.remove(); + } else { + // Change the value of the first matching entry + found = true; + param.setValue(value); + result = param; } } } - } - - /** - * Creates a new entry. - * - * @param name The name of the entry. - * @param value The value of the entry. - * @return A new entry. - */ - public T createEntry(String name, String value) { - try { - return this.entryClass.getConstructor(String.class, String.class).newInstance(name, value); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to create a series entry", e); - return null; - } - } - - /** - * Tests the equality of two string, potentially null, which a case sensitivity - * flag. - * - * @param value1 The first value. - * @param value2 The second value. - * @param ignoreCase Indicates if the test should be case insensitive. - * @return True if both values are equal. - */ - private boolean equals(String value1, String value2, boolean ignoreCase) { - boolean result = (value1 == value2); - - if (!result) { - if ((value1 != null) && (value2 != null)) { - if (ignoreCase) { - result = value1.equalsIgnoreCase(value2); - } else { - result = value1.equals(value2); - } - } - } - - return result; - } - - /** - * Returns the first parameter found with the given name. - * - * @param name The parameter name (case sensitive). - * @return The first parameter found with the given name. - */ - public T getFirst(String name) { - return getFirst(name, false); - } - - /** - * Returns the first parameter found with the given name. - * - * @param name The parameter name. - * @param ignoreCase Indicates if the name comparison is case insensitive. - * @return The first parameter found with the given name. - */ - public T getFirst(String name, boolean ignoreCase) { - for (T param : this) { - if (equals(param.getName(), name, ignoreCase)) { - return param; - } - } - - return null; - } - - /** - * Returns the value of the first parameter found with the given name. - * - * @param name The parameter name (case-sensitive). - * @return The value of the first parameter found with the given name. - */ - public String getFirstValue(String name) { - return getFirstValue(name, false); - } - - /** - * Returns the value of the first parameter found with the given name. - * - * @param name The parameter name. - * @param ignoreCase Indicates if the name comparison is case-sensitive. - * @return The value of the first parameter found with the given name. - */ - public String getFirstValue(String name, boolean ignoreCase) { - return getFirstValue(name, ignoreCase, null); - } - - /** - * Returns the value of the first parameter found with the given name. - * - * @param name The parameter name. - * @param ignoreCase Indicates if the name comparison is case sensitive. - * @param defaultValue The default value to return if no matching parameter - * found or if the parameter has a null value. - * @return The value of the first parameter found with the given name or the - * default value. - */ - public String getFirstValue(String name, boolean ignoreCase, String defaultValue) { - String result = defaultValue; - NamedValue param = getFirst(name, ignoreCase); - - if ((param != null) && (param.getValue() != null)) { - result = param.getValue(); - } - - return result; - } - - /** - * Returns the value of the first parameter found with the given name. - * - * @param name The parameter name (case-sensitive). - * @param defaultValue The default value to return if no matching parameter - * found or if the parameter has a null value. - * @return The value of the first parameter found with the given name or the - * default value. - */ - public String getFirstValue(String name, String defaultValue) { - return getFirstValue(name, false, defaultValue); - } - - /** - * Returns the set of parameter names (case-sensitive). - * - * @return The set of parameter names. - */ - public Set getNames() { - Set result = new HashSet(); - - for (NamedValue param : this) { - result.add(param.getName()); - } - - return result; - } - - /** - * Returns the values of the parameters with a given name. If multiple - * parameters with the same name are found, all values are concatenated and - * separated by a comma (like for HTTP message headers). - * - * @param name The parameter name (case insensitive). - * @return The values of the parameters with a given name. - */ - public String getValues(String name) { - return getValues(name, ",", true); - } - - /** - * Returns the parameter values with a given name. If multiple parameters with - * the same name are found, all values are concatenated and separated by the - * given separator. - * - * @param name The parameter name. - * @param separator The separator character. - * @param ignoreCase Indicates if the name comparison is case sensitive. - * @return The sequence of values. - */ - public String getValues(String name, String separator, boolean ignoreCase) { - String result = null; - StringBuilder sb = null; - - for (final T param : this) { - if ((ignoreCase && param.getName().equalsIgnoreCase(name)) || param.getName().equals(name)) { - if (sb == null) { - if (result == null) { - result = param.getValue(); - } else { - sb = new StringBuilder(); - sb.append(result).append(separator).append(param.getValue()); - } - } else { - sb.append(separator).append(param.getValue()); - } - } - } - - if (sb != null) { - result = sb.toString(); - } - - return result; - } - - /** - * Returns an array of all the values associated to the given parameter name. - * - * @param name The parameter name to match (case sensitive). - * @return The array of values. - */ - public String[] getValuesArray(String name) { - return getValuesArray(name, false); - } - - /** - * Returns an array of all the values associated to the given parameter name. - * - * @param name The parameter name to match. - * @param ignoreCase Indicates if the name comparison is case sensitive. - * @return The array of values. - */ - public String[] getValuesArray(String name, boolean ignoreCase) { - return getValuesArray(name, ignoreCase, null); - } - - /** - * Returns an array of all the values associated to the given parameter name. - * - * @param name The parameter name to match. - * @param ignoreCase Indicates if the name comparison is case-sensitive. - * @param defaultValue The default value to return if no matching parameter - * found or if the parameter has a null value. - * @return The array of values. - */ - public String[] getValuesArray(String name, boolean ignoreCase, String defaultValue) { - String[] result = null; - List params = subList(name, ignoreCase); - - if ((params.isEmpty()) && (defaultValue != null)) { - result = new String[1]; - result[0] = defaultValue; - } else { - result = new String[params.size()]; - - for (int i = 0; i < params.size(); i++) { - result[i] = params.get(i).getValue(); - } - } - - return result; - } - - /** - * Returns an array of all the values associated to the given parameter name. - * - * @param name The parameter name to match. - * @param defaultValue The default value to return if no matching parameter - * found or if the parameter has a null value. - * @return The array of values. - */ - public String[] getValuesArray(String name, String defaultValue) { - return getValuesArray(name, false, defaultValue); - } - - /** - * Returns a map of name, value pairs. The order of the map keys is respected - * based on the series order. When a name has multiple values, only the first - * one is put in the map. - * - * @return The map of name, value pairs. - */ - public Map getValuesMap() { - Map result = new LinkedHashMap(); - - for (NamedValue param : this) { - if (!result.containsKey(param.getName())) { - result.put(param.getName(), param.getValue()); - } - } - - return result; - } - - /** - * Removes all the parameters with a given name. - * - * @param name The parameter name (case-sensitive). - * @return True if the list changed. - */ - public boolean removeAll(String name) { - return removeAll(name, false); - } - - /** - * Removes all the parameters with a given name. - * - * @param name The parameter name. - * @param ignoreCase Indicates if the name comparison is case-insensitive. - * @return True if the list changed. - */ - public boolean removeAll(String name, boolean ignoreCase) { - boolean changed = false; - NamedValue param = null; - - for (Iterator iter = iterator(); iter.hasNext();) { - param = iter.next(); - - if (equals(param.getName(), name, ignoreCase)) { - iter.remove(); - changed = true; - } - } - - return changed; - } - - /** - * Removes from this list the first entry whose name equals the specified name - * ignoring the case. - * - * @param name The name of the entries to be removed (case-sensitive). - * @return false if no entry has been removed, true otherwise. - */ - public boolean removeFirst(String name) { - return removeFirst(name, false); - } - - /** - * Removes from this list the first entry whose name equals the specified name - * ignoring the case or not. - * - * @param name The name of the entries to be removed. - * @param ignoreCase Indicates if the name comparison is case-insensitive. - * @return false if no entry has been removed, true otherwise. - */ - public boolean removeFirst(String name, boolean ignoreCase) { - boolean changed = false; - NamedValue param = null; - - for (final Iterator iter = iterator(); iter.hasNext() && !changed;) { - param = iter.next(); - if (equals(param.getName(), name, ignoreCase)) { - iter.remove(); - changed = true; - } - } - - return changed; - } - - /** - * Replaces the value of the first parameter with the given name and removes all - * other parameters with the same name. The name matching is case-sensitive. - * - * @param name The parameter name. - * @param value The value to set. - * @return The parameter set or added. - */ - public T set(String name, String value) { - return set(name, value, false); - } - - /** - * Replaces the value of the first parameter with the given name and removes all - * other parameters with the same name. - * - * @param name The parameter name. - * @param value The value to set. - * @param ignoreCase Indicates if the name comparison is case-insensitive. - * @return The parameter set or added. - */ - public T set(String name, String value, boolean ignoreCase) { - T result = null; - T param = null; - boolean found = false; - - for (final Iterator iter = iterator(); iter.hasNext();) { - param = iter.next(); - - if (equals(param.getName(), name, ignoreCase)) { - if (found) { - // Remove other entries with the same name - iter.remove(); - } else { - // Change the value of the first matching entry - found = true; - param.setValue(value); - result = param; - } - } - } - - if (!found) { - add(name, value); - } - - return result; - } - - /** - * Returns a view of the portion of this list between the specified fromIndex, - * inclusive, and toIndex, exclusive. - * - * @param fromIndex The start position. - * @param toIndex The end position (exclusive). - * @return The sub-list. - */ - @Override - public Series subList(int fromIndex, int toIndex) { - return new Series(this.entryClass, getDelegate().subList(fromIndex, toIndex)); - } - - /** - * Returns a list of all the values associated to the parameter name. - * - * @param name The parameter name (case-sensitive). - * @return The list of values. - */ - public Series subList(String name) { - return subList(name, false); - } - - /** - * Returns a list of all the values associated to the parameter name. - * - * @param name The parameter name. - * @param ignoreCase Indicates if the name comparison is case-insensitive. - * @return The list of values. - */ - public Series subList(String name, boolean ignoreCase) { - Series result = new Series(this.entryClass); - - for (T param : this) { - if (equals(param.getName(), name, ignoreCase)) { - result.add(param); - } - } - - return result; - } + if (!found) { + add(name, value); + } + + return result; + } + + /** + * Returns a view of the portion of this list between the specified fromIndex, inclusive, and + * toIndex, exclusive. + * + * @param fromIndex The start position. + * @param toIndex The end position (exclusive). + * @return The sub-list. + */ + @Override + public Series subList(int fromIndex, int toIndex) { + return new Series<>(this.entryClass, getDelegate().subList(fromIndex, toIndex)); + } + + /** + * Returns a list of all the values associated with the parameter name. + * + * @param name The parameter name (case-sensitive). + * @return The list of values. + */ + public Series subList(String name) { + return subList(name, false); + } + + /** + * Returns a list of all the values associated with the parameter name. + * + * @param name The parameter name. + * @param ignoreCase Indicates if the name comparison is case-insensitive. + * @return The list of values. + */ + public Series subList(String name, boolean ignoreCase) { + Series result = new Series<>(this.entryClass); + + for (T param : this) { + if (equals(param.getName(), name, ignoreCase)) { + result.add(param); + } + } + + return result; + } } diff --git a/org.restlet/src/main/java/org/restlet/util/ServerList.java b/org.restlet/src/main/java/org/restlet/util/ServerList.java index d6a25f00bc..758a3f1c64 100644 --- a/org.restlet/src/main/java/org/restlet/util/ServerList.java +++ b/org.restlet/src/main/java/org/restlet/util/ServerList.java @@ -1,138 +1,133 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; +import java.util.concurrent.CopyOnWriteArrayList; import org.restlet.Context; import org.restlet.Restlet; import org.restlet.Server; import org.restlet.data.Protocol; -import java.util.concurrent.CopyOnWriteArrayList; - /** * Modifiable list of server connectors. - * + * * @author Jerome Louvel */ public final class ServerList extends WrapperList { - /** The context. */ - private volatile Context context; - - /** The next Restlet of added servers. */ - private volatile Restlet next; - - /** - * Constructor. - * - * @param context The context. - * @param next The next Restlet of added servers. - */ - public ServerList(Context context, Restlet next) { - super(new CopyOnWriteArrayList<>()); - this.context = context; - this.next = next; - } - - /** - * Adds a new server connector in the map supporting the given protocol. - * - * @param protocol The connector protocol. - * @return The added server. - */ - public Server add(Protocol protocol) { - Server result = new Server(protocol, null, protocol.getDefaultPort(), getNext()); - add(result); - return result; - } - - /** - * Adds a new server connector in the map supporting the given protocol on the - * specified port. - * - * @param protocol The connector protocol. - * @param port The listening port. - * @return The added server. - */ - public Server add(Protocol protocol, int port) { - Server result = new Server(protocol, null, port, getNext()); - add(result); - return result; - } - - /** - * Adds a new server connector in the map supporting the given protocol on the - * specified IP address and port. - * - * @param protocol The connector protocol. - * @param address The optional listening IP address (useful if multiple IP - * addresses available). - * @param port The listening port. - * @return The added server. - */ - public Server add(Protocol protocol, String address, int port) { - Server result = new Server(protocol, address, port, getNext()); - add(result); - return result; - } - - /** - * Adds a server at the end of the list. - * - * @return True (as per the general contract of the Collection.add method). - */ - @Override - public boolean add(Server server) { - // Set the server's context, if the server does not have already one. - if (server.getContext() == null) { - server.setContext(getContext().createChildContext()); - } - - server.setNext(getNext()); - return super.add(server); - } - - /** - * Returns the context. - * - * @return The context. - */ - public Context getContext() { - return this.context; - } - - /** - * Returns the next Restlet. - * - * @return The next Restlet. - */ - public Restlet getNext() { - return this.next; - } - - /** - * Sets the context. - * - * @param context The context. - */ - public void setContext(Context context) { - this.context = context; - } - - /** - * Sets the next Restlet. - * - * @param next The next Restlet. - */ - public void setNext(Restlet next) { - this.next = next; - } - + /** The context. */ + private volatile Context context; + + /** The next Restlet of added servers. */ + private volatile Restlet next; + + /** + * Constructor. + * + * @param context The context. + * @param next The next Restlet of added servers. + */ + public ServerList(Context context, Restlet next) { + super(new CopyOnWriteArrayList<>()); + this.context = context; + this.next = next; + } + + /** + * Adds a new server connector in the map supporting the given protocol. + * + * @param protocol The connector protocol. + * @return The added server. + */ + public Server add(Protocol protocol) { + Server result = new Server(protocol, null, protocol.getDefaultPort(), getNext()); + add(result); + return result; + } + + /** + * Adds a new server connector in the map supporting the given protocol on the specified port. + * + * @param protocol The connector protocol. + * @param port The listening port. + * @return The added server. + */ + public Server add(Protocol protocol, int port) { + Server result = new Server(protocol, null, port, getNext()); + add(result); + return result; + } + + /** + * Adds a new server connector in the map supporting the given protocol on the specified IP + * address and port. + * + * @param protocol The connector protocol. + * @param address The optional listening IP address (useful if multiple IP addresses available). + * @param port The listening port. + * @return The added server. + */ + public Server add(Protocol protocol, String address, int port) { + Server result = new Server(protocol, address, port, getNext()); + add(result); + return result; + } + + /** + * Adds a server at the end of the list. + * + * @return True (as per the general contract of the Collection.add method). + */ + @Override + public boolean add(Server server) { + // Set the server's context if the server does not already have one. + if (server.getContext() == null) { + server.setContext(getContext().createChildContext()); + } + + server.setNext(getNext()); + return super.add(server); + } + + /** + * Returns the context. + * + * @return The context. + */ + public Context getContext() { + return this.context; + } + + /** + * Returns the next Restlet. + * + * @return The next Restlet. + */ + public Restlet getNext() { + return this.next; + } + + /** + * Sets the context. + * + * @param context The context. + */ + public void setContext(Context context) { + this.context = context; + } + + /** + * Sets the next Restlet. + * + * @param next The next Restlet. + */ + public void setNext(Restlet next) { + this.next = next; + } } diff --git a/org.restlet/src/main/java/org/restlet/util/ServiceList.java b/org.restlet/src/main/java/org/restlet/util/ServiceList.java index 83033213ae..9d7247b232 100644 --- a/org.restlet/src/main/java/org/restlet/util/ServiceList.java +++ b/org.restlet/src/main/java/org/restlet/util/ServiceList.java @@ -1,186 +1,183 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; -import org.restlet.Context; -import org.restlet.service.Service; - import java.util.Collection; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; +import org.restlet.Context; +import org.restlet.service.Service; /** * Modifiable list of services. - * + * * @author Jerome Louvel */ public final class ServiceList extends WrapperList { - /** The context. */ - private volatile Context context; - - /** - * Constructor. - * - * @param context The context. - */ - public ServiceList(Context context) { - super(new CopyOnWriteArrayList<>()); - this.context = context; - } - - @Override - public void add(int index, Service service) { - service.setContext(getContext()); - super.add(index, service); - } - - @Override - public boolean add(Service service) { - service.setContext(getContext()); - return super.add(service); - } - - @Override - public boolean addAll(Collection services) { - if (services != null) { - for (Service service : services) { - service.setContext(getContext()); - } - } - - return super.addAll(services); - } - - @Override - public boolean addAll(int index, Collection services) { - if (services != null) { - for (Service service : services) { - service.setContext(getContext()); - } - } - - return super.addAll(index, services); - } - - /** - * Returns a service matching a given service class. - * - * @param The service type. - * @param clazz The service class to match. - * @return The matched service instance. - */ - @SuppressWarnings("unchecked") - public T get(Class clazz) { - for (Service service : this) { - if (clazz.isAssignableFrom(service.getClass())) { - return (T) service; - } - } - - return null; - } - - /** - * Returns the context. - * - * @return The context. - */ - public Context getContext() { - return this.context; - } - - /** - * Sets the list of services. - * - * @param services The list of services. - */ - public synchronized void set(List services) { - clear(); - - if (services != null) { - addAll(services); - } - } - - /** - * Replaces or adds a service. The replacement is based on the service class. - * - * @param newService The new service to set. - */ - public synchronized void set(Service newService) { - List services = new CopyOnWriteArrayList(); - Service service; - boolean replaced = false; - - for (int i = 0; (i < size()); i++) { - service = get(i); - - if (service != null) { - if (service.getClass().isAssignableFrom(newService.getClass())) { - try { - service.stop(); - } catch (Exception e) { - Context.getCurrentLogger().log(Level.WARNING, "Unable to stop service replaced", e); - } - - services.add(newService); - replaced = true; - } else { - services.add(service); - } - } - } - - if (!replaced) { - services.add(newService); - } - - set(services); - } - - /** - * Sets the context. By default, it also updates the context of already - * registered services. - * - * @param context The context. - */ - public void setContext(Context context) { - this.context = context; - - for (Service service : this) { - service.setContext(context); - } - } - - /** - * Starts each service. - * - * @throws Exception - */ - public void start() throws Exception { - for (Service service : this) { - service.start(); - } - } - - /** - * Stops each service. - * - * @throws Exception - */ - public void stop() throws Exception { - for (Service service : this) { - service.stop(); - } - } - + /** The context. */ + private volatile Context context; + + /** + * Constructor. + * + * @param context The context. + */ + public ServiceList(Context context) { + super(new CopyOnWriteArrayList<>()); + this.context = context; + } + + @Override + public void add(int index, Service service) { + service.setContext(getContext()); + super.add(index, service); + } + + @Override + public boolean add(Service service) { + service.setContext(getContext()); + return super.add(service); + } + + @Override + public boolean addAll(Collection services) { + if (services != null) { + for (Service service : services) { + service.setContext(getContext()); + } + } + + return super.addAll(services); + } + + @Override + public boolean addAll(int index, Collection services) { + if (services != null) { + for (Service service : services) { + service.setContext(getContext()); + } + } + + return super.addAll(index, services); + } + + /** + * Returns a service matching a given service class. + * + * @param The service type. + * @param clazz The service class to match. + * @return The matched service instance. + */ + @SuppressWarnings("unchecked") + public T get(Class clazz) { + for (Service service : this) { + if (clazz.isAssignableFrom(service.getClass())) { + return (T) service; + } + } + + return null; + } + + /** + * Returns the context. + * + * @return The context. + */ + public Context getContext() { + return this.context; + } + + /** + * Sets the list of services. + * + * @param services The list of services. + */ + public synchronized void set(List services) { + clear(); + + if (services != null) { + addAll(services); + } + } + + /** + * Replaces or adds a service. The replacement is based on the service class. + * + * @param newService The new service to set. + */ + public synchronized void set(Service newService) { + List services = new CopyOnWriteArrayList(); + Service service; + boolean replaced = false; + + for (int i = 0; (i < size()); i++) { + service = get(i); + + if (service != null) { + if (service.getClass().isAssignableFrom(newService.getClass())) { + try { + service.stop(); + } catch (Exception e) { + Context.getCurrentLogger() + .log(Level.WARNING, "Unable to stop service replaced", e); + } + + services.add(newService); + replaced = true; + } else { + services.add(service); + } + } + } + + if (!replaced) { + services.add(newService); + } + + set(services); + } + + /** + * Sets the context. By default, it also updates the context of already registered services. + * + * @param context The context. + */ + public void setContext(Context context) { + this.context = context; + + for (Service service : this) { + service.setContext(context); + } + } + + /** + * Starts each service. + * + * @throws Exception + */ + public void start() throws Exception { + for (Service service : this) { + service.start(); + } + } + + /** + * Stops each service. + * + * @throws Exception + */ + public void stop() throws Exception { + for (Service service : this) { + service.stop(); + } + } } diff --git a/org.restlet/src/main/java/org/restlet/util/WrapperList.java b/org.restlet/src/main/java/org/restlet/util/WrapperList.java index 6a6e86558d..ba624cfcb7 100644 --- a/org.restlet/src/main/java/org/restlet/util/WrapperList.java +++ b/org.restlet/src/main/java/org/restlet/util/WrapperList.java @@ -1,329 +1,317 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Vector; /** - * List wrapper. Modifiable list that delegates all methods to a wrapped list. - * This allows an easy sub-classing. By default, it wraps a thread-safe - * {@link Vector} instance. - * + * List wrapper. Modifiable list that delegates all methods to a wrapped list. This allows an easy + * subclassing. By default, it wraps a thread-safe {@link Vector} instance. + * * @author Jerome Louvel - * @see The decorator (aka - * wrapper) pattern + * @see The decorator (aka wrapper) pattern * @see java.util.Collections * @see java.util.List */ public class WrapperList implements List, Iterable { - /** The delegate list. */ - private final List delegate; - - /** - * Constructor. Uses a default initial capacity of 10 items. - */ - public WrapperList() { - this(10); - } - - /** - * Constructor. - * - * @param initialCapacity The initial list capacity. - */ - public WrapperList(int initialCapacity) { - this(new Vector(initialCapacity)); - } - - /** - * Constructor. - * - * @param delegate The delegate list. - */ - public WrapperList(List delegate) { - this.delegate = delegate; - } - - /** - * Adds a element at the end of the list. - * - * @return True (as per the general contract of the Collection.add method). - */ - public boolean add(E element) { - return getDelegate().add(element); - } - - /** - * Inserts the specified element at the specified position in this list. - * - * @param index The insertion position. - * @param element The element to insert. - */ - public void add(int index, E element) { - getDelegate().add(index, element); - } - - /** - * Appends all of the elements in the specified collection to the end of this - * list. - * - * @param elements The collection of elements to append. - */ - public boolean addAll(Collection elements) { - return getDelegate().addAll(elements); - } - - /** - * Inserts all of the elements in the specified collection into this list at the - * specified position. - * - * @param index The insertion position. - * @param elements The collection of elements to insert. - */ - public boolean addAll(int index, Collection elements) { - return getDelegate().addAll(index, elements); - } - - /** - * Removes all of the elements from this list. - */ - public void clear() { - getDelegate().clear(); - } - - /** - * Returns true if this list contains the specified element. - * - * @param element The element to find. - * @return True if this list contains the specified element. - */ - public boolean contains(Object element) { - return getDelegate().contains(element); - } - - /** - * Returns true if this list contains all of the elements of the specified - * collection. - * - * @param elements The collection of elements to find. - * @return True if this list contains all of the elements of the specified - * collection. - */ - public boolean containsAll(Collection elements) { - return getDelegate().containsAll(elements); - } - - /** - * Compares the specified object with this list for equality. - * - * @param o The object to be compared for equality with this list. - * @return True if the specified object is equal to this list. - */ - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o instanceof List) { - List that = (List) o; - return Arrays.equals(this.toArray(), that.toArray()); - } - - return false; - } - - /** - * Returns the element at the specified position in this list. - * - * @param index The element position. - * @return The element at the specified position in this list. - */ - public E get(int index) { - return getDelegate().get(index); - } - - /** - * Returns the delegate list. - * - * @return The delegate list. - */ - protected List getDelegate() { - return this.delegate; - } - - /** - * Returns the hash code value for this list. - * - * @return The hash code value for this list. - */ - @Override - public int hashCode() { - return getDelegate().hashCode(); - } - - /** - * Returns the index in this list of the first occurrence of the specified - * element, or -1 if this list does not contain this element. - * - * @param element The element to find. - * @return The index of the first occurrence. - */ - public int indexOf(Object element) { - return getDelegate().indexOf(element); - } - - /** - * Returns true if this list contains no elements. - */ - public boolean isEmpty() { - return getDelegate().isEmpty(); - } - - /** - * Returns an iterator over the elements in this list in proper sequence. - * - * @return An iterator over the elements in this list in proper sequence. - */ - public Iterator iterator() { - return getDelegate().iterator(); - } - - /** - * Returns the index in this list of the last occurrence of the specified - * element, or -1 if this list does not contain this element. - */ - public int lastIndexOf(Object element) { - return getDelegate().lastIndexOf(element); - } - - /** - * Returns a list iterator of the elements in this list (in proper sequence). - * - * @return A list iterator of the elements in this list (in proper sequence). - */ - public ListIterator listIterator() { - return getDelegate().listIterator(); - } - - /** - * Returns a list iterator of the elements in this list (in proper sequence), - * starting at the specified position in this list. - * - * @param index The starting position. - */ - public ListIterator listIterator(int index) { - return getDelegate().listIterator(index); - } - - /** - * Removes the element at the specified position in this list. - * - * @return The removed element. - */ - public E remove(int index) { - return getDelegate().remove(index); - } - - /** - * Removes the first occurrence in this list of the specified element. - * - * @return True if the list was changed. - */ - public boolean remove(Object element) { - return getDelegate().remove(element); - } - - /** - * Removes from this list all the elements that are contained in the specified - * collection. - * - * @param elements The collection of element to remove. - * @return True if the list changed. - */ - public boolean removeAll(Collection elements) { - return getDelegate().removeAll(elements); - } - - /** - * RemovesRetains only the elements in this list that are contained in the - * specified collection. - * - * @param elements The collection of element to retain. - * @return True if the list changed. - */ - public boolean retainAll(Collection elements) { - return getDelegate().retainAll(elements); - } - - /** - * Replaces the element at the specified position in this list with the - * specified element. - * - * @param index The position of the element to replace. - * @param element The new element. - */ - public E set(int index, E element) { - return getDelegate().set(index, element); - } - - /** - * Returns the number of elements in this list. - * - * @return The number of elements in this list. - */ - public int size() { - return getDelegate().size(); - } - - /** - * Returns a view of the portion of this list between the specified fromIndex, - * inclusive, and toIndex, exclusive. - * - * @param fromIndex The start position. - * @param toIndex The end position (exclusive). - * @return The sub-list. - */ - public List subList(int fromIndex, int toIndex) { - return new WrapperList(getDelegate().subList(fromIndex, toIndex)); - } - - /** - * Returns an array containing all of the elements in this list in proper - * sequence. - * - * @return An array containing all of the elements in this list in proper - * sequence. - */ - public Object[] toArray() { - return getDelegate().toArray(); - } - - /** - * Returns an array containing all of the elements in this list in proper - * sequence; the runtime type of the returned array is that of the specified - * array. - * - * @param a The sample array. - */ - public T[] toArray(T[] a) { - return getDelegate().toArray(a); - } - - /** - * Returns a string representation of the list. - * - * @return A string representation of the list. - */ - @Override - public String toString() { - return getDelegate().toString(); - } + /** The delegate list. */ + private final List delegate; + + /** Constructor. Uses a default initial capacity of 10 items. */ + public WrapperList() { + this(10); + } + + /** + * Constructor. + * + * @param initialCapacity The initial list capacity. + */ + public WrapperList(int initialCapacity) { + this(new Vector<>(initialCapacity)); + } + + /** + * Constructor. + * + * @param delegate The delegate list. + */ + public WrapperList(List delegate) { + this.delegate = delegate; + } + + /** + * Adds a element at the end of the list. + * + * @return True (as per the general contract of the Collection.add method). + */ + public boolean add(E element) { + return getDelegate().add(element); + } + + /** + * Inserts the specified element at the specified position in this list. + * + * @param index The insertion position. + * @param element The element to insert. + */ + public void add(int index, E element) { + getDelegate().add(index, element); + } + + /** + * Appends the elements of the specified collection at the end of this list. + * + * @param elements The collection of elements to append. + */ + public boolean addAll(Collection elements) { + return getDelegate().addAll(elements); + } + + /** + * Inserts the elements of the specified collection into this list at the specified position. + * + * @param index The insertion position. + * @param elements The collection of elements to insert. + */ + public boolean addAll(int index, Collection elements) { + return getDelegate().addAll(index, elements); + } + + @Override + public void addFirst(final E e) { + getDelegate().addFirst(e); + } + + @Override + public void addLast(final E e) { + getDelegate().addLast(e); + } + + /** Removes the elements from this list. */ + public void clear() { + getDelegate().clear(); + } + + /** + * Returns true if this list contains the specified element. + * + * @param element The element to find. + * @return True if this list contains the specified element. + */ + public boolean contains(Object element) { + return getDelegate().contains(element); + } + + /** + * Returns true if this list contains all the elements of the specified collection. + * + * @param elements The collection of elements to find. + * @return True if this list contains all the elements of the specified collection. + */ + public boolean containsAll(Collection elements) { + return getDelegate().containsAll(elements); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof List that)) { + return false; + } + return Arrays.equals(this.toArray(), that.toArray()); + } + + /** + * Returns the element at the specified position in this list. + * + * @param index The element position. + * @return The element at the specified position in this list. + */ + public E get(int index) { + return getDelegate().get(index); + } + + /** + * Returns the delegate list. + * + * @return The delegate list. + */ + protected List getDelegate() { + return this.delegate; + } + + /** + * Returns the hash code value for this list. + * + * @return The hash code value for this list. + */ + @Override + public int hashCode() { + return getDelegate().hashCode(); + } + + /** + * Returns the index in this list of the first occurrence of the specified element, or -1 if + * this list does not contain this element. + * + * @param element The element to find. + * @return The index of the first occurrence. + */ + public int indexOf(Object element) { + return getDelegate().indexOf(element); + } + + /** Returns true if this list contains no elements. */ + public boolean isEmpty() { + return getDelegate().isEmpty(); + } + + /** + * Returns an iterator over the elements in this list in a proper sequence. + * + * @return An iterator over the elements in this list in a proper sequence. + */ + public Iterator iterator() { + return getDelegate().iterator(); + } + + /** + * Returns the index in this list of the last occurrence of the specified element, or -1 if this + * list does not contain this element. + */ + public int lastIndexOf(Object element) { + return getDelegate().lastIndexOf(element); + } + + /** + * Returns a list iterator of the elements in this list (in a proper sequence). + * + * @return A list iterator of the elements in this list (in a proper sequence). + */ + public ListIterator listIterator() { + return getDelegate().listIterator(); + } + + /** + * Returns a list iterator of the elements in this list (in a proper sequence), starting at the + * specified position in this list. + * + * @param index The starting position. + */ + public ListIterator listIterator(int index) { + return getDelegate().listIterator(index); + } + + /** + * Removes the element at the specified position in this list. + * + * @return The removed element. + */ + public E remove(int index) { + return getDelegate().remove(index); + } + + /** + * Removes the first occurrence in this list of the specified element. + * + * @return True if the list was changed. + */ + public boolean remove(Object element) { + return getDelegate().remove(element); + } + + /** + * Removes from this list all the elements that are contained in the specified collection. + * + * @param elements The collection of elements to remove. + * @return True if the list changed. + */ + public boolean removeAll(Collection elements) { + return getDelegate().removeAll(elements); + } + + /** + * RemovesRetains only the elements in this list that are contained in the specified collection. + * + * @param elements The collection of elements to retain. + * @return True if the list changed. + */ + public boolean retainAll(Collection elements) { + return getDelegate().retainAll(elements); + } + + /** + * Replaces the element at the specified position in this list with the specified element. + * + * @param index The position of the element to replace. + * @param element The new element. + */ + public E set(int index, E element) { + return getDelegate().set(index, element); + } + + /** + * Returns the number of elements in this list. + * + * @return The number of elements in this list. + */ + public int size() { + return getDelegate().size(); + } + + /** + * Returns a view of the portion of this list between the specified fromIndex, inclusive, and + * toIndex, exclusive. + * + * @param fromIndex The start position. + * @param toIndex The end position (exclusive). + * @return The sub-list. + */ + public List subList(int fromIndex, int toIndex) { + return new WrapperList<>(getDelegate().subList(fromIndex, toIndex)); + } + + /** + * Returns an array containing all the elements in this list in a proper sequence. + * + * @return An array containing all the elements in this list in a proper sequence. + */ + public Object[] toArray() { + return getDelegate().toArray(); + } + + /** + * Returns an array containing all the elements in this list in a proper sequence; the runtime + * type of the returned array is that of the specified array. + * + * @param a The sample array. + */ + public T[] toArray(T[] a) { + return getDelegate().toArray(a); + } + + /** + * Returns a string representation of the list. + * + * @return A string representation of the list. + */ + @Override + public String toString() { + return getDelegate().toString(); + } } diff --git a/org.restlet/src/main/java/org/restlet/util/WrapperMap.java b/org.restlet/src/main/java/org/restlet/util/WrapperMap.java index 2c46ea8352..a473a30758 100644 --- a/org.restlet/src/main/java/org/restlet/util/WrapperMap.java +++ b/org.restlet/src/main/java/org/restlet/util/WrapperMap.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; import java.util.Collection; @@ -15,183 +14,168 @@ import java.util.concurrent.ConcurrentHashMap; /** - * Map wrapper. Modifiable map that delegates all methods to a wrapped map. This - * allows an easy subclassing. - * + * Map wrapper. Modifiable map that delegates all methods to a wrapped map. This allows an easy + * subclassing. + * * @author Jerome Louvel - * @see The decorator (aka - * wrapper) pattern + * @see The decorator (aka wrapper) pattern * @see java.util.Collections * @see java.util.List */ public class WrapperMap implements Map { - /** The delegate map. */ - private final Map delegate; - - /** - * Constructor. - */ - public WrapperMap() { - this.delegate = new ConcurrentHashMap(); - } - - /** - * Constructor. - * - * @param delegate The delegate list. - */ - public WrapperMap(Map delegate) { - this.delegate = delegate; - } - - /** - * Removes all mappings from this Map. - */ - public void clear() { - getDelegate().clear(); - } - - /** - * Returns true if this map contains a mapping for the specified key. - * - * @param key The key to look up. - * @return True if this map contains a mapping for the specified key. - */ - public boolean containsKey(Object key) { - return getDelegate().containsKey(key); - } - - /** - * Returns true if this map maps one or more keys to the specified value. - * - * @param value The value to look up - * @return True if this map maps one or more keys to the specified value. - */ - public boolean containsValue(Object value) { - return getDelegate().containsValue(value); - } - - /** - * Returns a set view of the mappings contained in this map. - * - * @return A set view of the mappings contained in this map. - */ - public Set> entrySet() { - return getDelegate().entrySet(); - } - - /** - * Compares the specified object with this map for equality. - * - * @param o Object to be compared for equality with this map. - * @return True if the specified object is equal to this map. - */ - @Override - public boolean equals(Object o) { - return getDelegate().equals(o); - } - - /** - * Returns the value to which this map maps the specified key. - * - * @param key Key whose associated value is to be returned. - * @return The value to which this map maps the specified key, or null if the - * map contains no mapping for this key. - */ - public V get(Object key) { - return getDelegate().get(key); - } - - /** - * Returns the delegate list. - * - * @return The delegate list. - */ - protected Map getDelegate() { - return this.delegate; - } - - /** - * Returns the hash code value for this map. - * - * @return The hash code value for this map. - */ - @Override - public int hashCode() { - return getDelegate().hashCode(); - } - - /** - * Returns true if this map contains no key-value mappings. - * - * @return True if this map contains no key-value mappings. - */ - public boolean isEmpty() { - return getDelegate().isEmpty(); - } - - /** - * Returns a set view of the keys contained in this map. - * - * @return A set view of the keys contained in this map. - */ - public Set keySet() { - return getDelegate().keySet(); - } - - /** - * Associates the specified value with the specified key in this map (optional - * operation). - * - * @param key Key with which the specified value is to be associated. - * @param value Value to be associated with the specified key. - * @return The previous value associated with specified key, or null if there - * was no mapping for key. A null return can also indicate that the map - * previously associated null with the specified key, if the - * implementation supports null values. - */ - public V put(K key, V value) { - return getDelegate().put(key, value); - } - - /** - * Copies all of the mappings from the specified map to this map (optional - * operation). - * - * @param t Mappings to be stored in this map. - */ - public void putAll(Map t) { - getDelegate().putAll(t); - } - - /** - * Removes the mapping for this key from this map if it is present (optional - * operation). - * - * @param key Key whose mapping is to be removed from the map. - * @return The previous value associated with specified key, or null if there - * was no mapping for key. - */ - public V remove(Object key) { - return getDelegate().remove(key); - } - - /** - * Returns the number of key-value mappings in this map. - * - * @return The number of key-value mappings in this map. - */ - public int size() { - return getDelegate().size(); - } - - /** - * Returns a collection view of the values contained in this map. - * - * @return A collection view of the values contained in this map. - */ - public Collection values() { - return getDelegate().values(); - } - + /** The delegate map. */ + private final Map delegate; + + /** Constructor. */ + public WrapperMap() { + this.delegate = new ConcurrentHashMap<>(); + } + + /** + * Constructor. + * + * @param delegate The delegate list. + */ + public WrapperMap(Map delegate) { + this.delegate = delegate; + } + + /** Removes all mappings from this Map. */ + public void clear() { + getDelegate().clear(); + } + + /** + * Returns true if this map contains a mapping for the specified key. + * + * @param key The key to look up. + * @return True if this map contains a mapping for the specified key. + */ + public boolean containsKey(Object key) { + return getDelegate().containsKey(key); + } + + /** + * Returns true if this map maps one or more keys to the specified value. + * + * @param value The value to look up + * @return True if this map maps one or more keys to the specified value. + */ + public boolean containsValue(Object value) { + return getDelegate().containsValue(value); + } + + /** + * Returns a set view of the mappings contained in this map. + * + * @return A set view of the mappings contained in this map. + */ + public Set> entrySet() { + return getDelegate().entrySet(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object o) { + return getDelegate().equals(o); + } + + /** + * Returns the value to which this map maps the specified key. + * + * @param key Key whose associated value is to be returned. + * @return The value to which this map maps the specified key, or null if the map contains no + * mapping for this key. + */ + public V get(Object key) { + return getDelegate().get(key); + } + + /** + * Returns the delegate list. + * + * @return The delegate list. + */ + protected Map getDelegate() { + return this.delegate; + } + + /** + * Returns the hash code value for this map. + * + * @return The hash code value for this map. + */ + @Override + public int hashCode() { + return getDelegate().hashCode(); + } + + /** + * Returns true if this map contains no key-value mappings. + * + * @return True if this map contains no key-value mappings. + */ + public boolean isEmpty() { + return getDelegate().isEmpty(); + } + + /** + * Returns a set view of the keys contained in this map. + * + * @return A set view of the keys contained in this map. + */ + public Set keySet() { + return getDelegate().keySet(); + } + + /** + * Associates the specified value with the specified key in this map (optional operation). + * + * @param key Key with which the specified value is to be associated. + * @param value Value to be associated with the specified key. + * @return The previous value associated with a specified key, or null if there was no mapping + * for key. A null return can also indicate that the map previously associated null with the + * specified key, if the implementation supports null values. + */ + public V put(K key, V value) { + return getDelegate().put(key, value); + } + + /** + * Copies all the mappings from the specified map to this map (optional operation). + * + * @param t Mappings to be stored in this map. + */ + public void putAll(Map t) { + getDelegate().putAll(t); + } + + /** + * Removes the mapping for this key from this map if it is present (optional operation). + * + * @param key Key whose mapping is to be removed from the map. + * @return The previous value associated with a specified key, or null if there was no mapping + * for key. + */ + public V remove(Object key) { + return getDelegate().remove(key); + } + + /** + * Returns the number of key-value mappings in this map. + * + * @return The number of key-value mappings in this map. + */ + public int size() { + return getDelegate().size(); + } + + /** + * Returns a collection view of the values contained in this map. + * + * @return A collection view of the values contained in this map. + */ + public Collection values() { + return getDelegate().values(); + } } diff --git a/org.restlet/src/main/java/org/restlet/util/WrapperRepresentation.java b/org.restlet/src/main/java/org/restlet/util/WrapperRepresentation.java index ba704f6be8..f012099e6b 100644 --- a/org.restlet/src/main/java/org/restlet/util/WrapperRepresentation.java +++ b/org.restlet/src/main/java/org/restlet/util/WrapperRepresentation.java @@ -1,235 +1,238 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; -import org.restlet.data.*; -import org.restlet.representation.Representation; - import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.Date; import java.util.List; +import org.restlet.data.CharacterSet; +import org.restlet.data.Disposition; +import org.restlet.data.Encoding; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Reference; +import org.restlet.data.Tag; +import org.restlet.representation.Representation; /** - * Representation wrapper. Useful for application developer who need to enrich - * the representation with application related properties and behavior. - * - * @see The decorator (aka - * wrapper) pattern + * Representation wrapper. Useful for developers who need to enrich the representation with + * application-related properties and behavior. + * + * @see The decorator (aka wrapper) pattern * @author Jerome Louvel */ public class WrapperRepresentation extends Representation { - /** The wrapped representation. */ - private final Representation wrappedRepresentation; - - /** - * Constructor. - * - * @param wrappedRepresentation The wrapped representation. - */ - public WrapperRepresentation(Representation wrappedRepresentation) { - this.wrappedRepresentation = wrappedRepresentation; - } - - @Override - public long exhaust() throws IOException { - return getWrappedRepresentation().exhaust(); - } - - @Override - public long getAvailableSize() { - return getWrappedRepresentation().getAvailableSize(); - } - - @Override - public CharacterSet getCharacterSet() { - return getWrappedRepresentation().getCharacterSet(); - } - - @Override - public org.restlet.data.Digest getDigest() { - return getWrappedRepresentation().getDigest(); - } - - @Override - public Disposition getDisposition() { - return getWrappedRepresentation().getDisposition(); - } - - @Override - public List getEncodings() { - return getWrappedRepresentation().getEncodings(); - } - - @Override - public Date getExpirationDate() { - return getWrappedRepresentation().getExpirationDate(); - } - - @Override - public List getLanguages() { - return getWrappedRepresentation().getLanguages(); - } - - @Override - public Reference getLocationRef() { - return getWrappedRepresentation().getLocationRef(); - } - - @Override - public MediaType getMediaType() { - return getWrappedRepresentation().getMediaType(); - } - - @Override - public Date getModificationDate() { - return getWrappedRepresentation().getModificationDate(); - } - - @Override - public org.restlet.data.Range getRange() { - return getWrappedRepresentation().getRange(); - } - - @Override - public Reader getReader() throws IOException { - return getWrappedRepresentation().getReader(); - } - - @Override - public long getSize() { - return getWrappedRepresentation().getSize(); - } - - @Override - public InputStream getStream() throws IOException { - return getWrappedRepresentation().getStream(); - } - - @Override - public Tag getTag() { - return getWrappedRepresentation().getTag(); - } - - @Override - public String getText() throws IOException { - return getWrappedRepresentation().getText(); - } - - /** - * Returns the wrapped representation. - * - * @return The wrapped representation. - */ - public Representation getWrappedRepresentation() { - return this.wrappedRepresentation; - } - - @Override - public boolean isAvailable() { - return getWrappedRepresentation().isAvailable(); - } - - @Override - public boolean isTransient() { - return getWrappedRepresentation().isTransient(); - } - - @Override - public void release() { - getWrappedRepresentation().release(); - } - - @Override - public void setAvailable(boolean isAvailable) { - getWrappedRepresentation().setAvailable(isAvailable); - } - - @Override - public void setCharacterSet(CharacterSet characterSet) { - getWrappedRepresentation().setCharacterSet(characterSet); - } - - @Override - public void setDigest(org.restlet.data.Digest digest) { - getWrappedRepresentation().setDigest(digest); - } - - @Override - public void setDisposition(Disposition disposition) { - getWrappedRepresentation().setDisposition(disposition); - } - - @Override - public void setEncodings(List encodings) { - getWrappedRepresentation().setEncodings(encodings); - } - - @Override - public void setExpirationDate(Date expirationDate) { - getWrappedRepresentation().setExpirationDate(expirationDate); - } - - @Override - public void setLanguages(List languages) { - getWrappedRepresentation().setLanguages(languages); - } - - @Override - public void setLocationRef(Reference location) { - getWrappedRepresentation().setLocationRef(location); - } - - @Override - public void setLocationRef(String locationUri) { - getWrappedRepresentation().setLocationRef(locationUri); - } - - @Override - public void setMediaType(MediaType mediaType) { - getWrappedRepresentation().setMediaType(mediaType); - } - - @Override - public void setModificationDate(Date modificationDate) { - getWrappedRepresentation().setModificationDate(modificationDate); - } - - @Override - public void setRange(org.restlet.data.Range range) { - getWrappedRepresentation().setRange(range); - } - - @Override - public void setSize(long expectedSize) { - getWrappedRepresentation().setSize(expectedSize); - } - - @Override - public void setTag(Tag tag) { - getWrappedRepresentation().setTag(tag); - } - - @Override - public void setTransient(boolean isTransient) { - getWrappedRepresentation().setTransient(isTransient); - } - - @Override - public void write(java.io.OutputStream outputStream) throws IOException { - getWrappedRepresentation().write(outputStream); - } - - @Override - public void write(java.io.Writer writer) throws IOException { - getWrappedRepresentation().write(writer); - } + /** The wrapped representation. */ + private final Representation wrappedRepresentation; + + /** + * Constructor. + * + * @param wrappedRepresentation The wrapped representation. + */ + public WrapperRepresentation(Representation wrappedRepresentation) { + this.wrappedRepresentation = wrappedRepresentation; + } + + @Override + public long exhaust() throws IOException { + return getWrappedRepresentation().exhaust(); + } + + @Override + public long getAvailableSize() { + return getWrappedRepresentation().getAvailableSize(); + } + + @Override + public CharacterSet getCharacterSet() { + return getWrappedRepresentation().getCharacterSet(); + } + + @Override + public org.restlet.data.Digest getDigest() { + return getWrappedRepresentation().getDigest(); + } + + @Override + public Disposition getDisposition() { + return getWrappedRepresentation().getDisposition(); + } + + @Override + public List getEncodings() { + return getWrappedRepresentation().getEncodings(); + } + + @Override + public Date getExpirationDate() { + return getWrappedRepresentation().getExpirationDate(); + } + + @Override + public List getLanguages() { + return getWrappedRepresentation().getLanguages(); + } + + @Override + public Reference getLocationRef() { + return getWrappedRepresentation().getLocationRef(); + } + + @Override + public MediaType getMediaType() { + return getWrappedRepresentation().getMediaType(); + } + + @Override + public Date getModificationDate() { + return getWrappedRepresentation().getModificationDate(); + } + + @Override + public org.restlet.data.Range getRange() { + return getWrappedRepresentation().getRange(); + } + + @Override + public Reader getReader() throws IOException { + return getWrappedRepresentation().getReader(); + } + + @Override + public long getSize() { + return getWrappedRepresentation().getSize(); + } + + @Override + public InputStream getStream() throws IOException { + return getWrappedRepresentation().getStream(); + } + + @Override + public Tag getTag() { + return getWrappedRepresentation().getTag(); + } + + @Override + public String getText() throws IOException { + return getWrappedRepresentation().getText(); + } + + /** + * Returns the wrapped representation. + * + * @return The wrapped representation. + */ + public Representation getWrappedRepresentation() { + return this.wrappedRepresentation; + } + + @Override + public boolean isAvailable() { + return getWrappedRepresentation().isAvailable(); + } + + @Override + public boolean isTransient() { + return getWrappedRepresentation().isTransient(); + } + + @Override + public void release() { + getWrappedRepresentation().release(); + } + + @Override + public void setAvailable(boolean isAvailable) { + getWrappedRepresentation().setAvailable(isAvailable); + } + + @Override + public void setCharacterSet(CharacterSet characterSet) { + getWrappedRepresentation().setCharacterSet(characterSet); + } + + @Override + public void setDigest(org.restlet.data.Digest digest) { + getWrappedRepresentation().setDigest(digest); + } + + @Override + public void setDisposition(Disposition disposition) { + getWrappedRepresentation().setDisposition(disposition); + } + + @Override + public void setEncodings(List encodings) { + getWrappedRepresentation().setEncodings(encodings); + } + + @Override + public void setExpirationDate(Date expirationDate) { + getWrappedRepresentation().setExpirationDate(expirationDate); + } + + @Override + public void setLanguages(List languages) { + getWrappedRepresentation().setLanguages(languages); + } + + @Override + public void setLocationRef(Reference location) { + getWrappedRepresentation().setLocationRef(location); + } + + @Override + public void setLocationRef(String locationUri) { + getWrappedRepresentation().setLocationRef(locationUri); + } + + @Override + public void setMediaType(MediaType mediaType) { + getWrappedRepresentation().setMediaType(mediaType); + } + + @Override + public void setModificationDate(Date modificationDate) { + getWrappedRepresentation().setModificationDate(modificationDate); + } + + @Override + public void setRange(org.restlet.data.Range range) { + getWrappedRepresentation().setRange(range); + } + + @Override + public void setSize(long expectedSize) { + getWrappedRepresentation().setSize(expectedSize); + } + + @Override + public void setTag(Tag tag) { + getWrappedRepresentation().setTag(tag); + } + + @Override + public void setTransient(boolean isTransient) { + getWrappedRepresentation().setTransient(isTransient); + } + + @Override + public void write(java.io.OutputStream outputStream) throws IOException { + getWrappedRepresentation().write(outputStream); + } + + @Override + public void write(java.io.Writer writer) throws IOException { + getWrappedRepresentation().write(writer); + } } diff --git a/org.restlet/src/main/java/org/restlet/util/WrapperRequest.java b/org.restlet/src/main/java/org/restlet/util/WrapperRequest.java index 7a6b49a640..bef197704b 100644 --- a/org.restlet/src/main/java/org/restlet/util/WrapperRequest.java +++ b/org.restlet/src/main/java/org/restlet/util/WrapperRequest.java @@ -1,495 +1,492 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; import org.restlet.Request; import org.restlet.Response; import org.restlet.Uniform; -import org.restlet.data.*; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ClientInfo; +import org.restlet.data.Conditions; +import org.restlet.data.Cookie; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.data.Range; +import org.restlet.data.Reference; import org.restlet.representation.Representation; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; - /** - * Request wrapper. Useful for application developer who need to enrich the - * request with application related properties and behavior. - * - * @see The decorator (aka - * wrapper) pattern + * Request wrapper. Useful for application developer who need to enrich the request with application + * related properties and behavior. + * + * @see The decorator (aka wrapper) pattern * @author Jerome Louvel */ public class WrapperRequest extends Request { - /** The wrapped request. */ - private final Request wrappedRequest; - - /** - * Constructor. - * - * @param wrappedRequest The wrapped request. - */ - public WrapperRequest(Request wrappedRequest) { - this.wrappedRequest = wrappedRequest; - } - - @Override - public boolean abort() { - return wrappedRequest.abort(); - } - - @Override - public void commit(Response response) { - wrappedRequest.commit(response); - } - - /** - * Returns a modifiable attributes map that can be used by developers to save - * information relative to the message. This is an easier alternative to the - * creation of a wrapper instance around the whole message.
- *
- * - * In addition, this map is a shared space between the developer and the - * connectors. In this case, it is used to exchange information that is not - * uniform across all protocols and couldn't therefore be directly included in - * the API. For this purpose, all attribute names starting with "org.restlet" - * are reserved. Currently the following attributes are used: - * - * - * - * - * - * - * - * - * - * - * - * - *
list of supported attributes
Attribute nameClass nameDescription
org.restlet.http.headersorg.restlet.data.FormServer HTTP connectors must provide all request headers and client HTTP - * connectors must provide all response headers, exactly as they were received. - * In addition, developers can also use this attribute to specify - * non-standard headers that should be added to the request or to the - * response.
- * Adding standard HTTP headers is forbidden because it could conflict with the - * connector's internal behavior, limit portability or prevent future - * optimizations. - * - * @return The modifiable attributes map. - */ - @Override - public ConcurrentMap getAttributes() { - return getWrappedRequest().getAttributes(); - } - - /** - * Returns the authentication response sent by a client to an origin server. - * - * @return The authentication response sent by a client to an origin server. - */ - @Override - public ChallengeResponse getChallengeResponse() { - return getWrappedRequest().getChallengeResponse(); - } - - /** - * Returns the client-specific information. - * - * @return The client-specific information. - */ - @Override - public ClientInfo getClientInfo() { - return getWrappedRequest().getClientInfo(); - } - - /** - * Returns the conditions applying to this call. - * - * @return The conditions applying to this call. - */ - @Override - public Conditions getConditions() { - return getWrappedRequest().getConditions(); - } - - /** - * Returns the cookies provided by the client. - * - * @return The cookies provided by the client. - */ - @Override - public Series getCookies() { - return getWrappedRequest().getCookies(); - } - - /** - * Returns the entity representation. - * - * @return The entity representation. - */ - @Override - public Representation getEntity() { - return getWrappedRequest().getEntity(); - } - - /** - * Returns the host reference. This may be different from the resourceRef's - * host, for example for URNs and other URIs that don't contain host - * information. - * - * @return The host reference. - */ - @Override - public Reference getHostRef() { - return getWrappedRequest().getHostRef(); - } - - @Override - public int getMaxForwards() { - return wrappedRequest.getMaxForwards(); - } - - /** - * Returns the method. - * - * @return The method. - */ - @Override - public Method getMethod() { - return getWrappedRequest().getMethod(); - } - - @Override - public Uniform getOnResponse() { - return wrappedRequest.getOnResponse(); - } - - @Override - public Reference getOriginalRef() { - return wrappedRequest.getOriginalRef(); - } - - /** - * Returns the protocol by first returning the baseRef.schemeProtocol property - * if it is set, or the resourceRef.schemeProtocol property otherwise. - * - * @return The protocol or null if not available. - */ - @Override - public Protocol getProtocol() { - return getWrappedRequest().getProtocol(); - } - - /** - * Returns the authentication response sent by a client to a proxy. - * - * @return The authentication response sent by a client to a proxy. - */ - @Override - public ChallengeResponse getProxyChallengeResponse() { - return getWrappedRequest().getProxyChallengeResponse(); - } - - @Override - public List getRanges() { - return wrappedRequest.getRanges(); - } - - /** - * Returns the referrer reference if available. - * - * @return The referrer reference. - */ - @Override - public Reference getReferrerRef() { - return getWrappedRequest().getReferrerRef(); - } - - /** - * Returns the reference of the target resource. - * - * @return The reference of the target resource. - */ - @Override - public Reference getResourceRef() { - return getWrappedRequest().getResourceRef(); - } - - /** - * Returns the application root reference. - * - * @return The application root reference. - */ - @Override - public Reference getRootRef() { - return getWrappedRequest().getRootRef(); - } - - /** - * Returns the wrapped request. - * - * @return The wrapped request. - */ - protected Request getWrappedRequest() { - return this.wrappedRequest; - } - - /** - * Returns the access control request headers of the target resource. - * - * @return The access control request headers of the target resource. - */ - @Override - public Set getAccessControlRequestHeaders() { - return wrappedRequest.getAccessControlRequestHeaders(); - } - - /** - * Returns the access control request method of the target resource. - * - * @return The access control request method of the target resource. - */ - @Override - public Method getAccessControlRequestMethod() { - return wrappedRequest.getAccessControlRequestMethod(); - } - - @Override - public boolean isAsynchronous() { - return wrappedRequest.isAsynchronous(); - } - - /** - * Indicates if the call came over a confidential channel such as an SSL-secured - * connection. - * - * @return True if the call came over a confidential channel. - */ - @Override - public boolean isConfidential() { - return getWrappedRequest().isConfidential(); - } - - /** - * Indicates if a content is available and can be sent. Several conditions must - * be met: the method must allow the sending of content, the content must exists - * and have some available data. - * - * @return True if a content is available and can be sent. - */ - @Override - public boolean isEntityAvailable() { - return getWrappedRequest().isEntityAvailable(); - } - - @Override - public boolean isExpectingResponse() { - return wrappedRequest.isExpectingResponse(); - } - - @Override - public boolean isSynchronous() { - return wrappedRequest.isSynchronous(); - } - - /** - * Sets the authentication response sent by a client to an origin server. - * - * @param response The authentication response sent by a client to an origin - * server. - */ - @Override - public void setChallengeResponse(ChallengeResponse response) { - getWrappedRequest().setChallengeResponse(response); - } - - @Override - public void setClientInfo(ClientInfo clientInfo) { - wrappedRequest.setClientInfo(clientInfo); - } - - @Override - public void setConditions(Conditions conditions) { - wrappedRequest.setConditions(conditions); - } - - @Override - public void setCookies(Series cookies) { - wrappedRequest.setCookies(cookies); - } - - /** - * Sets the entity representation. - * - * @param entity The entity representation. - */ - @Override - public void setEntity(Representation entity) { - getWrappedRequest().setEntity(entity); - } - - /** - * Sets a textual entity. - * - * @param value The represented string. - * @param mediaType The representation's media type. - */ - @Override - public void setEntity(String value, MediaType mediaType) { - getWrappedRequest().setEntity(value, mediaType); - } - - /** - * Sets the host reference. - * - * @param hostRef The host reference. - */ - @Override - public void setHostRef(Reference hostRef) { - getWrappedRequest().setHostRef(hostRef); - } - - /** - * Sets the host reference using an URI string. - * - * @param hostUri The host URI. - */ - @Override - public void setHostRef(String hostUri) { - getWrappedRequest().setHostRef(hostUri); - } - - @Override - public void setMaxForwards(int maxForwards) { - wrappedRequest.setMaxForwards(maxForwards); - } - - /** - * Sets the method called. - * - * @param method The method called. - */ - @Override - public void setMethod(Method method) { - getWrappedRequest().setMethod(method); - } - - @Override - public void setOnResponse(Uniform onResponseCallback) { - wrappedRequest.setOnResponse(onResponseCallback); - } - - @Override - public void setOriginalRef(Reference originalRef) { - wrappedRequest.setOriginalRef(originalRef); - } - - @Override - public void setProtocol(Protocol protocol) { - wrappedRequest.setProtocol(protocol); - } - - /** - * Sets the authentication response sent by a client to a proxy. - * - * @param response The authentication response sent by a client to a proxy. - */ - @Override - public void setProxyChallengeResponse(ChallengeResponse response) { - getWrappedRequest().setProxyChallengeResponse(response); - } - - @Override - public void setRanges(List ranges) { - wrappedRequest.setRanges(ranges); - } - - /** - * Sets the referrer reference if available. - * - * @param referrerRef The referrer reference. - */ - @Override - public void setReferrerRef(Reference referrerRef) { - getWrappedRequest().setReferrerRef(referrerRef); - } - - /** - * Sets the referrer reference if available using an URI string. - * - * @param referrerUri The referrer URI. - */ - @Override - public void setReferrerRef(String referrerUri) { - getWrappedRequest().setReferrerRef(referrerUri); - } - - /** - * Sets the target resource reference. If the reference is relative, it will be - * resolved as an absolute reference. Also, the context's base reference will be - * reset. Finally, the reference will be normalized to ensure a consistent - * handling of the call. - * - * @param resourceRef The resource reference. - */ - @Override - public void setResourceRef(Reference resourceRef) { - getWrappedRequest().setResourceRef(resourceRef); - } - - /** - * Sets the target resource reference using an URI string. Note that the URI can - * be either absolute or relative to the context's base reference. - * - * @param resourceUri The resource URI. - */ - @Override - public void setResourceRef(String resourceUri) { - getWrappedRequest().setResourceRef(resourceUri); - } - - /** - * Sets the application root reference. - * - * @param rootRef The application root reference. - */ - @Override - public void setRootRef(Reference rootRef) { - getWrappedRequest().setRootRef(rootRef); - } - - /** - * Sets the access control request headers of the target resource. - * - * @param accessControlRequestHeaders The access control request headers of the - * target resource. - */ - @Override - public void setAccessControlRequestHeaders(Set accessControlRequestHeaders) { - super.setAccessControlRequestHeaders(accessControlRequestHeaders); - } - - /** - * Sets the access control request method of the target resource. - * - * @param accessControlRequestMethod The access control request method of the - * target resource. - */ - @Override - public void setAccessControlRequestMethod(Method accessControlRequestMethod) { - super.setAccessControlRequestMethod(accessControlRequestMethod); - } - - @Override - public String toString() { - return wrappedRequest.toString(); - } - + /** The wrapped request. */ + private final Request wrappedRequest; + + /** + * Constructor. + * + * @param wrappedRequest The wrapped request. + */ + public WrapperRequest(Request wrappedRequest) { + this.wrappedRequest = wrappedRequest; + } + + @Override + public boolean abort() { + return wrappedRequest.abort(); + } + + @Override + public void commit(Response response) { + wrappedRequest.commit(response); + } + + /** + * Returns a modifiable attributes map that developers can use to save information relative to + * the message. This is an easier alternative to the creation of a wrapper instance around the + * whole message.
+ *
+ * In addition, this map is a shared space between the developer and the connectors. In this + * case, it is used to exchange information that is not uniform across all protocols and + * couldn't therefore be directly included in the API. For this purpose, all attribute names + * starting with "org.restlet" are reserved. Currently, the following attributes are used: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
list of supported attributes
Attribute nameClass nameDescription
org.restlet.http.headersorg.restlet.data.FormServer HTTP connectors must provide all request headers, and client HTTP + * connectors must provide all response headers, exactly as they were received. + * In addition, developers can also use this attribute to specify + * non-standard headers that should be added to the request or to the + * response.
+ * + * Adding standard HTTP headers is forbidden because it could conflict with the connector's + * internal behavior, limit portability, or prevent future optimizations. + * + * @return The modifiable attributes map. + */ + @Override + public ConcurrentMap getAttributes() { + return getWrappedRequest().getAttributes(); + } + + /** + * Returns the authentication response sent by a client to an origin server. + * + * @return The authentication response sent by a client to an origin server. + */ + @Override + public ChallengeResponse getChallengeResponse() { + return getWrappedRequest().getChallengeResponse(); + } + + /** + * Returns the client-specific information. + * + * @return The client-specific information. + */ + @Override + public ClientInfo getClientInfo() { + return getWrappedRequest().getClientInfo(); + } + + /** + * Returns the conditions applying to this call. + * + * @return The conditions applying to this call. + */ + @Override + public Conditions getConditions() { + return getWrappedRequest().getConditions(); + } + + /** + * Returns the cookies provided by the client. + * + * @return The cookies provided by the client. + */ + @Override + public Series getCookies() { + return getWrappedRequest().getCookies(); + } + + /** + * Returns the entity representation. + * + * @return The entity representation. + */ + @Override + public Representation getEntity() { + return getWrappedRequest().getEntity(); + } + + /** + * Returns the host reference. This may be different from the resourceRef's host, for example, + * for URNs and other URIs that don't contain host information. + * + * @return The host reference. + */ + @Override + public Reference getHostRef() { + return getWrappedRequest().getHostRef(); + } + + @Override + public int getMaxForwards() { + return wrappedRequest.getMaxForwards(); + } + + /** + * Returns the method. + * + * @return The method. + */ + @Override + public Method getMethod() { + return getWrappedRequest().getMethod(); + } + + @Override + public Uniform getOnResponse() { + return wrappedRequest.getOnResponse(); + } + + @Override + public Reference getOriginalRef() { + return wrappedRequest.getOriginalRef(); + } + + /** + * Returns the protocol by first returning the baseRef.schemeProtocol property if it is set, or + * the resourceRef.schemeProtocol property otherwise. + * + * @return The protocol or null if not available. + */ + @Override + public Protocol getProtocol() { + return getWrappedRequest().getProtocol(); + } + + /** + * Returns the authentication response sent by a client to a proxy. + * + * @return The authentication response sent by a client to a proxy. + */ + @Override + public ChallengeResponse getProxyChallengeResponse() { + return getWrappedRequest().getProxyChallengeResponse(); + } + + @Override + public List getRanges() { + return wrappedRequest.getRanges(); + } + + /** + * Returns the referrer reference if available. + * + * @return The referrer reference. + */ + @Override + public Reference getReferrerRef() { + return getWrappedRequest().getReferrerRef(); + } + + /** + * Returns the reference of the target resource. + * + * @return The reference of the target resource. + */ + @Override + public Reference getResourceRef() { + return getWrappedRequest().getResourceRef(); + } + + /** + * Returns the application root reference. + * + * @return The application root reference. + */ + @Override + public Reference getRootRef() { + return getWrappedRequest().getRootRef(); + } + + /** + * Returns the wrapped request. + * + * @return The wrapped request. + */ + protected Request getWrappedRequest() { + return this.wrappedRequest; + } + + /** + * Returns the access control request headers of the target resource. + * + * @return The access control request headers of the target resource. + */ + @Override + public Set getAccessControlRequestHeaders() { + return wrappedRequest.getAccessControlRequestHeaders(); + } + + /** + * Returns the access control request method of the target resource. + * + * @return The access control request method of the target resource. + */ + @Override + public Method getAccessControlRequestMethod() { + return wrappedRequest.getAccessControlRequestMethod(); + } + + @Override + public boolean isAsynchronous() { + return wrappedRequest.isAsynchronous(); + } + + /** + * Indicates if the call came over a confidential channel such as an SSL-secured connection. + * + * @return True if the call came over a confidential channel. + */ + @Override + public boolean isConfidential() { + return getWrappedRequest().isConfidential(); + } + + /** + * Indicates if a content is available and can be sent. Several conditions must be met: the + * method must allow the sending of content, the content must exist and have some available + * data. + * + * @return True if a content is available and can be sent. + */ + @Override + public boolean isEntityAvailable() { + return getWrappedRequest().isEntityAvailable(); + } + + @Override + public boolean isExpectingResponse() { + return wrappedRequest.isExpectingResponse(); + } + + @Override + public boolean isSynchronous() { + return wrappedRequest.isSynchronous(); + } + + /** + * Sets the authentication response sent by a client to an origin server. + * + * @param response The authentication response sent by a client to an origin server. + */ + @Override + public void setChallengeResponse(ChallengeResponse response) { + getWrappedRequest().setChallengeResponse(response); + } + + @Override + public void setClientInfo(ClientInfo clientInfo) { + wrappedRequest.setClientInfo(clientInfo); + } + + @Override + public void setConditions(Conditions conditions) { + wrappedRequest.setConditions(conditions); + } + + @Override + public void setCookies(Series cookies) { + wrappedRequest.setCookies(cookies); + } + + /** + * Sets the entity representation. + * + * @param entity The entity representation. + */ + @Override + public void setEntity(Representation entity) { + getWrappedRequest().setEntity(entity); + } + + /** + * Sets a textual entity. + * + * @param value The represented string. + * @param mediaType The representation's media-type. + */ + @Override + public void setEntity(String value, MediaType mediaType) { + getWrappedRequest().setEntity(value, mediaType); + } + + /** + * Sets the host reference. + * + * @param hostRef The host reference. + */ + @Override + public void setHostRef(Reference hostRef) { + getWrappedRequest().setHostRef(hostRef); + } + + /** + * Sets the host reference using a URI string. + * + * @param hostUri The host URI. + */ + @Override + public void setHostRef(String hostUri) { + getWrappedRequest().setHostRef(hostUri); + } + + @Override + public void setMaxForwards(int maxForwards) { + wrappedRequest.setMaxForwards(maxForwards); + } + + /** + * Sets the method called. + * + * @param method The method called. + */ + @Override + public void setMethod(Method method) { + getWrappedRequest().setMethod(method); + } + + @Override + public void setOnResponse(Uniform onResponseCallback) { + wrappedRequest.setOnResponse(onResponseCallback); + } + + @Override + public void setOriginalRef(Reference originalRef) { + wrappedRequest.setOriginalRef(originalRef); + } + + @Override + public void setProtocol(Protocol protocol) { + wrappedRequest.setProtocol(protocol); + } + + /** + * Sets the authentication response sent by a client to a proxy. + * + * @param response The authentication response sent by a client to a proxy. + */ + @Override + public void setProxyChallengeResponse(ChallengeResponse response) { + getWrappedRequest().setProxyChallengeResponse(response); + } + + @Override + public void setRanges(List ranges) { + wrappedRequest.setRanges(ranges); + } + + /** + * Sets the referrer reference if available. + * + * @param referrerRef The referrer reference. + */ + @Override + public void setReferrerRef(Reference referrerRef) { + getWrappedRequest().setReferrerRef(referrerRef); + } + + /** + * Sets the referrer reference if available using a URI string. + * + * @param referrerUri The referrer URI. + */ + @Override + public void setReferrerRef(String referrerUri) { + getWrappedRequest().setReferrerRef(referrerUri); + } + + /** + * Sets the target resource reference. If the reference is relative, it will be resolved as an + * absolute reference. Also, the context's base reference will be reset. Finally, the reference + * will be normalized to ensure a consistent handling of the call. + * + * @param resourceRef The resource reference. + */ + @Override + public void setResourceRef(Reference resourceRef) { + getWrappedRequest().setResourceRef(resourceRef); + } + + /** + * Sets the target resource reference using a URI string. Note that the URI can be either + * absolute or relative to the context's base reference. + * + * @param resourceUri The resource URI. + */ + @Override + public void setResourceRef(String resourceUri) { + getWrappedRequest().setResourceRef(resourceUri); + } + + /** + * Sets the application root reference. + * + * @param rootRef The application root reference. + */ + @Override + public void setRootRef(Reference rootRef) { + getWrappedRequest().setRootRef(rootRef); + } + + /** + * Sets the access control request headers of the target resource. + * + * @param accessControlRequestHeaders The access control request headers of the target resource. + */ + @Override + public void setAccessControlRequestHeaders(Set accessControlRequestHeaders) { + super.setAccessControlRequestHeaders(accessControlRequestHeaders); + } + + /** + * Sets the access control request method of the target resource. + * + * @param accessControlRequestMethod The access control request method of the target resource. + */ + @Override + public void setAccessControlRequestMethod(Method accessControlRequestMethod) { + super.setAccessControlRequestMethod(accessControlRequestMethod); + } + + @Override + public String toString() { + return wrappedRequest.toString(); + } } diff --git a/org.restlet/src/main/java/org/restlet/util/WrapperResponse.java b/org.restlet/src/main/java/org/restlet/util/WrapperResponse.java index e4486124bf..1495fa3b9b 100644 --- a/org.restlet/src/main/java/org/restlet/util/WrapperResponse.java +++ b/org.restlet/src/main/java/org/restlet/util/WrapperResponse.java @@ -1,493 +1,485 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; -import org.restlet.Request; -import org.restlet.Response; -import org.restlet.data.*; -import org.restlet.representation.Representation; - import java.util.Date; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentMap; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.AuthenticationInfo; +import org.restlet.data.ChallengeRequest; +import org.restlet.data.CookieSetting; +import org.restlet.data.Dimension; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Reference; +import org.restlet.data.ServerInfo; +import org.restlet.data.Status; +import org.restlet.representation.Representation; /** - * Request wrapper. Useful for application developer who need to enrich the - * request with application related properties and behavior. - * - * @see The decorator (aka - * wrapper) pattern + * Request wrapper. Useful for developers who need to enrich the request with application-related + * properties and behavior. + * + * @see The decorator (aka wrapper) pattern * @author Jerome Louvel */ public class WrapperResponse extends Response { - /** The wrapped response. */ - private final Response wrappedResponse; - - /** - * Constructor. - * - * @param wrappedResponse The wrapped response. - */ - public WrapperResponse(Response wrappedResponse) { - super(null); - this.wrappedResponse = wrappedResponse; - } - - @Override - public void abort() { - wrappedResponse.abort(); - } - - @Override - public void commit() { - wrappedResponse.commit(); - } - - @Override - public int getAge() { - return wrappedResponse.getAge(); - } - - /** - * Returns the set of methods allowed on the requested resource. This property - * only has to be updated when a status CLIENT_ERROR_METHOD_NOT_ALLOWED is set. - * - * @return The list of allowed methods. - */ - @Override - public Set getAllowedMethods() { - return getWrappedResponse().getAllowedMethods(); - } - - /** - * Returns a modifiable attributes map that can be used by developers to save - * information relative to the message. This is an easier alternative to the - * creation of a wrapper instance around the whole message.
- *
- * - * In addition, this map is a shared space between the developer and the - * connectors. In this case, it is used to exchange information that is not - * uniform across all protocols and couldn't therefore be directly included in - * the API. For this purpose, all attribute names starting with "org.restlet" - * are reserved. Currently the following attributes are used: - * - * - * - * - * - * - * - * - * - * - * - * - *
list of supported attributes
Attribute nameClass nameDescription
org.restlet.http.headersorg.restlet.data.FormServer HTTP connectors must provide all request headers and client HTTP - * connectors must provide all response headers, exactly as they were received. - * In addition, developers can also use this attribute to specify - * non-standard headers that should be added to the request or to the - * response.
- * Adding standard HTTP headers is forbidden because it could conflict with the - * connector's internal behavior, limit portability or prevent future - * optimizations. - * - * @return The modifiable attributes map. - */ - @Override - public ConcurrentMap getAttributes() { - return getWrappedResponse().getAttributes(); - } - - @Override - public AuthenticationInfo getAuthenticationInfo() { - return wrappedResponse.getAuthenticationInfo(); - } - - /** - * Returns the list of authentication requests sent by an origin server to a - * client. - * - * @return The list of authentication requests sent by an origin server to a - * client. - */ - @Override - public List getChallengeRequests() { - return getWrappedResponse().getChallengeRequests(); - } - - /** - * Returns the cookie settings provided by the server. - * - * @return The cookie settings provided by the server. - */ - @Override - public Series getCookieSettings() { - return getWrappedResponse().getCookieSettings(); - } - - /** - * Returns the set of selecting dimensions on which the response entity may - * vary. If some server-side content negotiation is done, this set should be - * properly updated, other it can be left empty. - * - * @return The set of dimensions on which the response entity may vary. - */ - @Override - public Set getDimensions() { - return getWrappedResponse().getDimensions(); - } - - /** - * Returns the entity representation. - * - * @return The entity representation. - */ - @Override - public Representation getEntity() { - return getWrappedResponse().getEntity(); - } - - /** - * Returns the reference that the client should follow for redirections or - * resource creations. - * - * @return The redirection reference. - */ - @Override - public Reference getLocationRef() { - return getWrappedResponse().getLocationRef(); - } - - /** - * Returns the list of authentication requests sent by a proxy to a client. - * - * @return The list of authentication requests sent by a proxy to a client. - */ - @Override - public List getProxyChallengeRequests() { - return getWrappedResponse().getProxyChallengeRequests(); - } - - /** - * Returns the associated request - * - * @return The associated request - */ - @Override - public Request getRequest() { - return getWrappedResponse().getRequest(); - } - - @Override - public Date getRetryAfter() { - return wrappedResponse.getRetryAfter(); - } - - /** - * Returns the server-specific information. - * - * @return The server-specific information. - */ - @Override - public ServerInfo getServerInfo() { - return getWrappedResponse().getServerInfo(); - } - - /** - * Returns the status. - * - * @return The status. - */ - @Override - public Status getStatus() { - return getWrappedResponse().getStatus(); - } - - /** - * Returns the wrapped response. - * - * @return The wrapped response. - */ - protected Response getWrappedResponse() { - return this.wrappedResponse; - } - - @Override - public boolean isAutoCommitting() { - return wrappedResponse.isAutoCommitting(); - } - - @Override - public boolean isCommitted() { - return wrappedResponse.isCommitted(); - } - - /** - * Indicates if the call came over a confidential channel such as an SSL-secured - * connection. - * - * @return True if the call came over a confidential channel. - */ - @Override - public boolean isConfidential() { - return getWrappedResponse().isConfidential(); - } - - /** - * Indicates if a content is available and can be sent. Several conditions must - * be met: the content must exists and have some available data. - * - * @return True if a content is available and can be sent. - */ - @Override - public boolean isEntityAvailable() { - return getWrappedResponse().isEntityAvailable(); - } - - /** - * Permanently redirects the client to a target URI. The client is expected to - * reuse the same method for the new request. - * - * @param targetRef The target URI reference. - */ - @Override - public void redirectPermanent(Reference targetRef) { - getWrappedResponse().redirectPermanent(targetRef); - } - - /** - * Permanently redirects the client to a target URI. The client is expected to - * reuse the same method for the new request. - * - * @param targetUri The target URI. - */ - @Override - public void redirectPermanent(String targetUri) { - getWrappedResponse().redirectPermanent(targetUri); - } - - /** - * Redirects the client to a different URI that SHOULD be retrieved using a GET - * method on that resource. This method exists primarily to allow the output of - * a POST-activated script to redirect the user agent to a selected resource. - * The new URI is not a substitute reference for the originally requested - * resource. - * - * @param targetRef The target reference. - */ - @Override - public void redirectSeeOther(Reference targetRef) { - getWrappedResponse().redirectSeeOther(targetRef); - } - - /** - * Redirects the client to a different URI that SHOULD be retrieved using a GET - * method on that resource. This method exists primarily to allow the output of - * a POST-activated script to redirect the user agent to a selected resource. - * The new URI is not a substitute reference for the originally requested - * resource. - * - * @param targetUri The target URI. - */ - @Override - public void redirectSeeOther(String targetUri) { - getWrappedResponse().redirectSeeOther(targetUri); - } - - /** - * Temporarily redirects the client to a target URI. The client is expected to - * reuse the same method for the new request. - * - * @param targetRef The target reference. - */ - @Override - public void redirectTemporary(Reference targetRef) { - getWrappedResponse().redirectTemporary(targetRef); - } - - /** - * Temporarily redirects the client to a target URI. The client is expected to - * reuse the same method for the new request. - * - * @param targetUri The target URI. - */ - @Override - public void redirectTemporary(String targetUri) { - getWrappedResponse().redirectTemporary(targetUri); - } - - @Override - public void setAge(int age) { - wrappedResponse.setAge(age); - } - - @Override - public void setAllowedMethods(Set allowedMethods) { - wrappedResponse.setAllowedMethods(allowedMethods); - } - - @Override - public void setAuthenticationInfo(AuthenticationInfo authenticationInfo) { - wrappedResponse.setAuthenticationInfo(authenticationInfo); - } - - @Override - public void setAutoCommitting(boolean autoCommitting) { - wrappedResponse.setAutoCommitting(autoCommitting); - } - - /** - * Sets the list of authentication requests sent by an origin server to a - * client. - * - * @param requests The list of authentication requests sent by an origin server - * to a client. - */ - @Override - public void setChallengeRequests(List requests) { - getWrappedResponse().setChallengeRequests(requests); - } - - @Override - public void setCommitted(boolean committed) { - wrappedResponse.setCommitted(committed); - } - - @Override - public void setCookieSettings(Series cookieSettings) { - wrappedResponse.setCookieSettings(cookieSettings); - } - - @Override - public void setDimensions(Set dimensions) { - wrappedResponse.setDimensions(dimensions); - } - - /** - * Sets the entity representation. - * - * @param entity The entity representation. - */ - @Override - public void setEntity(Representation entity) { - getWrappedResponse().setEntity(entity); - } - - /** - * Sets a textual entity. - * - * @param value The represented string. - * @param mediaType The representation's media type. - */ - @Override - public void setEntity(String value, MediaType mediaType) { - getWrappedResponse().setEntity(value, mediaType); - } - - /** - * Sets the reference that the client should follow for redirections or resource - * creations. - * - * @param locationRef The reference to set. - */ - @Override - public void setLocationRef(Reference locationRef) { - getWrappedResponse().setLocationRef(locationRef); - } - - /** - * Sets the reference that the client should follow for redirections or resource - * creations. - * - * @param locationUri The URI to set. - */ - @Override - public void setLocationRef(String locationUri) { - getWrappedResponse().setLocationRef(locationUri); - } - - /** - * Sets the list of authentication requests sent by a proxy to a client. - * - * @param requests The list of authentication requests sent by a proxy to a - * client. - */ - @Override - public void setProxyChallengeRequests(List requests) { - getWrappedResponse().setProxyChallengeRequests(requests); - } - - /** - * Sets the associated request. - * - * @param request The associated request - */ - @Override - public void setRequest(Request request) { - getWrappedResponse().setRequest(request); - } - - /** - * Sets the associated request. - * - * @param request The associated request - */ - public void setRequest(WrapperRequest request) { - getWrappedResponse().setRequest(request); - } - - @Override - public void setRetryAfter(Date retryAfter) { - wrappedResponse.setRetryAfter(retryAfter); - } - - @Override - public void setServerInfo(ServerInfo serverInfo) { - wrappedResponse.setServerInfo(serverInfo); - } - - /** - * Sets the status. - * - * @param status The status to set. - */ - @Override - public void setStatus(Status status) { - getWrappedResponse().setStatus(status); - } - - /** - * Sets the status. - * - * @param status The status to set. - * @param message The status message. - */ - @Override - public void setStatus(Status status, String message) { - getWrappedResponse().setStatus(status, message); - } - - @Override - public void setStatus(Status status, Throwable throwable) { - wrappedResponse.setStatus(status, throwable); - } - - @Override - public void setStatus(Status status, Throwable throwable, String message) { - wrappedResponse.setStatus(status, throwable, message); - } - - @Override - public String toString() { - return wrappedResponse.toString(); - } - + /** The wrapped response. */ + private final Response wrappedResponse; + + /** + * Constructor. + * + * @param wrappedResponse The wrapped response. + */ + public WrapperResponse(Response wrappedResponse) { + super(null); + this.wrappedResponse = wrappedResponse; + } + + @Override + public void abort() { + wrappedResponse.abort(); + } + + @Override + public void commit() { + wrappedResponse.commit(); + } + + @Override + public int getAge() { + return wrappedResponse.getAge(); + } + + /** + * Returns the set of methods allowed on the requested resource. This property only has to be + * updated when a status CLIENT_ERROR_METHOD_NOT_ALLOWED is set. + * + * @return The list of allowed methods. + */ + @Override + public Set getAllowedMethods() { + return getWrappedResponse().getAllowedMethods(); + } + + /** + * Returns a modifiable attributes map that developers can use to save information relative to + * the message. This is an easier alternative to the creation of a wrapper instance around the + * whole message.
+ *
+ * In addition, this map is a shared space between the developer and the connectors. In this + * case, it is used to exchange information that is not uniform across all protocols and + * couldn't therefore be directly included in the API. For this purpose, all attribute names + * starting with "org.restlet" are reserved. Currently, the following attributes are used: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
list of supported attributes
Attribute nameClass nameDescription
org.restlet.http.headersorg.restlet.data.FormServer HTTP connectors must provide all request headers, and client HTTP + * connectors must provide all response headers, exactly as they were received. + * In addition, developers can also use this attribute to specify + * non-standard headers that should be added to the request or to the + * response.
+ * + * Adding standard HTTP headers is forbidden because it could conflict with the connector's + * internal behavior, limit portability, or prevent future optimizations. + * + * @return The modifiable attributes map. + */ + @Override + public ConcurrentMap getAttributes() { + return getWrappedResponse().getAttributes(); + } + + @Override + public AuthenticationInfo getAuthenticationInfo() { + return wrappedResponse.getAuthenticationInfo(); + } + + /** + * Returns the list of authentication requests sent by an origin server to a client. + * + * @return The list of authentication requests sent by an origin server to a client. + */ + @Override + public List getChallengeRequests() { + return getWrappedResponse().getChallengeRequests(); + } + + /** + * Returns the cookie settings provided by the server. + * + * @return The cookie settings provided by the server. + */ + @Override + public Series getCookieSettings() { + return getWrappedResponse().getCookieSettings(); + } + + /** + * Returns the set of selecting dimensions on which the response entity may vary. If some + * server-side content negotiation is done, this set should be properly updated, other it can be + * left empty. + * + * @return The set of dimensions on which the response entity may vary. + */ + @Override + public Set getDimensions() { + return getWrappedResponse().getDimensions(); + } + + /** + * Returns the entity representation. + * + * @return The entity representation. + */ + @Override + public Representation getEntity() { + return getWrappedResponse().getEntity(); + } + + /** + * Returns the reference that the client should follow for redirections or resource creations. + * + * @return The redirection reference. + */ + @Override + public Reference getLocationRef() { + return getWrappedResponse().getLocationRef(); + } + + /** + * Returns the list of authentication requests sent by a proxy to a client. + * + * @return The list of authentication requests sent by a proxy to a client. + */ + @Override + public List getProxyChallengeRequests() { + return getWrappedResponse().getProxyChallengeRequests(); + } + + /** + * Returns the associated request + * + * @return The associated request + */ + @Override + public Request getRequest() { + return getWrappedResponse().getRequest(); + } + + @Override + public Date getRetryAfter() { + return wrappedResponse.getRetryAfter(); + } + + /** + * Returns the server-specific information. + * + * @return The server-specific information. + */ + @Override + public ServerInfo getServerInfo() { + return getWrappedResponse().getServerInfo(); + } + + /** + * Returns the status. + * + * @return The status. + */ + @Override + public Status getStatus() { + return getWrappedResponse().getStatus(); + } + + /** + * Returns the wrapped response. + * + * @return The wrapped response. + */ + protected Response getWrappedResponse() { + return this.wrappedResponse; + } + + @Override + public boolean isAutoCommitting() { + return wrappedResponse.isAutoCommitting(); + } + + @Override + public boolean isCommitted() { + return wrappedResponse.isCommitted(); + } + + /** + * Indicates if the call came over a confidential channel such as an SSL-secured connection. + * + * @return True if the call came over a confidential channel. + */ + @Override + public boolean isConfidential() { + return getWrappedResponse().isConfidential(); + } + + /** + * Indicates if a content is available and can be sent. Several conditions must be met: the + * content must exists and have some available data. + * + * @return True if a content is available and can be sent. + */ + @Override + public boolean isEntityAvailable() { + return getWrappedResponse().isEntityAvailable(); + } + + /** + * Permanently redirects the client to a target URI. The client is expected to reuse the same + * method for the new request. + * + * @param targetRef The target URI reference. + */ + @Override + public void redirectPermanent(Reference targetRef) { + getWrappedResponse().redirectPermanent(targetRef); + } + + /** + * Permanently redirects the client to a target URI. The client is expected to reuse the same + * method for the new request. + * + * @param targetUri The target URI. + */ + @Override + public void redirectPermanent(String targetUri) { + getWrappedResponse().redirectPermanent(targetUri); + } + + /** + * Redirects the client to a different URI that SHOULD be retrieved using a GET method on that + * resource. This method exists primarily to allow the output of a POST-activated script to + * redirect the user agent to a selected resource. The new URI is not a substitute reference for + * the originally requested resource. + * + * @param targetRef The target reference. + */ + @Override + public void redirectSeeOther(Reference targetRef) { + getWrappedResponse().redirectSeeOther(targetRef); + } + + /** + * Redirects the client to a different URI that SHOULD be retrieved using a GET method on that + * resource. This method exists primarily to allow the output of a POST-activated script to + * redirect the user agent to a selected resource. The new URI is not a substitute reference for + * the originally requested resource. + * + * @param targetUri The target URI. + */ + @Override + public void redirectSeeOther(String targetUri) { + getWrappedResponse().redirectSeeOther(targetUri); + } + + /** + * Temporarily redirects the client to a target URI. The client is expected to reuse the same + * method for the new request. + * + * @param targetRef The target reference. + */ + @Override + public void redirectTemporary(Reference targetRef) { + getWrappedResponse().redirectTemporary(targetRef); + } + + /** + * Temporarily redirects the client to a target URI. The client is expected to reuse the same + * method for the new request. + * + * @param targetUri The target URI. + */ + @Override + public void redirectTemporary(String targetUri) { + getWrappedResponse().redirectTemporary(targetUri); + } + + @Override + public void setAge(int age) { + wrappedResponse.setAge(age); + } + + @Override + public void setAllowedMethods(Set allowedMethods) { + wrappedResponse.setAllowedMethods(allowedMethods); + } + + @Override + public void setAuthenticationInfo(AuthenticationInfo authenticationInfo) { + wrappedResponse.setAuthenticationInfo(authenticationInfo); + } + + @Override + public void setAutoCommitting(boolean autoCommitting) { + wrappedResponse.setAutoCommitting(autoCommitting); + } + + /** + * Sets the list of authentication requests sent by an origin server to a client. + * + * @param requests The list of authentication requests sent by an origin server to a client. + */ + @Override + public void setChallengeRequests(List requests) { + getWrappedResponse().setChallengeRequests(requests); + } + + @Override + public void setCommitted(boolean committed) { + wrappedResponse.setCommitted(committed); + } + + @Override + public void setCookieSettings(Series cookieSettings) { + wrappedResponse.setCookieSettings(cookieSettings); + } + + @Override + public void setDimensions(Set dimensions) { + wrappedResponse.setDimensions(dimensions); + } + + /** + * Sets the entity representation. + * + * @param entity The entity representation. + */ + @Override + public void setEntity(Representation entity) { + getWrappedResponse().setEntity(entity); + } + + /** + * Sets a textual entity. + * + * @param value The represented string. + * @param mediaType The representation's media-type. + */ + @Override + public void setEntity(String value, MediaType mediaType) { + getWrappedResponse().setEntity(value, mediaType); + } + + /** + * Sets the reference that the client should follow for redirections or resource creations. + * + * @param locationRef The reference to set. + */ + @Override + public void setLocationRef(Reference locationRef) { + getWrappedResponse().setLocationRef(locationRef); + } + + /** + * Sets the reference that the client should follow for redirections or resource creations. + * + * @param locationUri The URI to set. + */ + @Override + public void setLocationRef(String locationUri) { + getWrappedResponse().setLocationRef(locationUri); + } + + /** + * Sets the list of authentication requests sent by a proxy to a client. + * + * @param requests The list of authentication requests sent by a proxy to a client. + */ + @Override + public void setProxyChallengeRequests(List requests) { + getWrappedResponse().setProxyChallengeRequests(requests); + } + + /** + * Sets the associated request. + * + * @param request The associated request + */ + @Override + public void setRequest(Request request) { + getWrappedResponse().setRequest(request); + } + + /** + * Sets the associated request. + * + * @param request The associated request + */ + public void setRequest(WrapperRequest request) { + getWrappedResponse().setRequest(request); + } + + @Override + public void setRetryAfter(Date retryAfter) { + wrappedResponse.setRetryAfter(retryAfter); + } + + @Override + public void setServerInfo(ServerInfo serverInfo) { + wrappedResponse.setServerInfo(serverInfo); + } + + /** + * Sets the status. + * + * @param status The status to set. + */ + @Override + public void setStatus(Status status) { + getWrappedResponse().setStatus(status); + } + + /** + * Sets the status. + * + * @param status The status to set. + * @param message The status message. + */ + @Override + public void setStatus(Status status, String message) { + getWrappedResponse().setStatus(status, message); + } + + @Override + public void setStatus(Status status, Throwable throwable) { + wrappedResponse.setStatus(status, throwable); + } + + @Override + public void setStatus(Status status, Throwable throwable, String message) { + wrappedResponse.setStatus(status, throwable, message); + } + + @Override + public String toString() { + return wrappedResponse.toString(); + } } diff --git a/org.restlet/src/main/java/org/restlet/util/WrapperRestlet.java b/org.restlet/src/main/java/org/restlet/util/WrapperRestlet.java index bf977c9541..8a7958c0d6 100644 --- a/org.restlet/src/main/java/org/restlet/util/WrapperRestlet.java +++ b/org.restlet/src/main/java/org/restlet/util/WrapperRestlet.java @@ -1,127 +1,122 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.util; +import java.util.logging.Logger; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; -import java.util.logging.Logger; - /** - * Restlet wrapper. Useful for application developer who need to wrap a Restlet - * instance. - * + * Restlet wrapper. Useful for developers who need to wrap a Restlet instance. + * * @author Thierry Boileau - * @see The decorator (aka - * wrapper) pattern + * @see The decorator (aka wrapper) pattern */ public class WrapperRestlet extends Restlet { - /** The wrapped Restlet instance. */ - private Restlet wrappedRestlet; - - /** - * Constructor. - * - * @param wrappedRestlet The wrapped Restlet instance. - */ - public WrapperRestlet(Restlet wrappedRestlet) { - super(); - this.wrappedRestlet = wrappedRestlet; - } - - @Override - public org.restlet.Application getApplication() { - return wrappedRestlet.getApplication(); - } - - @Override - public String getAuthor() { - return wrappedRestlet.getAuthor(); - } - - @Override - public Context getContext() { - return wrappedRestlet.getContext(); - } - - @Override - public String getDescription() { - return wrappedRestlet.getDescription(); - } - - @Override - public Logger getLogger() { - return wrappedRestlet.getLogger(); - } - - @Override - public String getName() { - return wrappedRestlet.getName(); - } - - @Override - public String getOwner() { - return wrappedRestlet.getOwner(); - } - - @Override - public void handle(Request request, Response response) { - wrappedRestlet.handle(request, response); - } - - @Override - public boolean isStarted() { - return wrappedRestlet.isStarted(); - } - - @Override - public boolean isStopped() { - return wrappedRestlet.isStopped(); - } - - @Override - public void setAuthor(String author) { - wrappedRestlet.setAuthor(author); - } - - @Override - public void setContext(Context context) { - wrappedRestlet.setContext(context); - } - - @Override - public void setDescription(String description) { - wrappedRestlet.setDescription(description); - } - - @Override - public void setName(String name) { - wrappedRestlet.setName(name); - } - - @Override - public void setOwner(String owner) { - wrappedRestlet.setOwner(owner); - } - - @Override - public synchronized void start() throws Exception { - wrappedRestlet.start(); - } - - @Override - public synchronized void stop() throws Exception { - wrappedRestlet.stop(); - } - + /** The wrapped Restlet instance. */ + private Restlet wrappedRestlet; + + /** + * Constructor. + * + * @param wrappedRestlet The wrapped Restlet instance. + */ + public WrapperRestlet(Restlet wrappedRestlet) { + super(); + this.wrappedRestlet = wrappedRestlet; + } + + @Override + public org.restlet.Application getApplication() { + return wrappedRestlet.getApplication(); + } + + @Override + public String getAuthor() { + return wrappedRestlet.getAuthor(); + } + + @Override + public Context getContext() { + return wrappedRestlet.getContext(); + } + + @Override + public String getDescription() { + return wrappedRestlet.getDescription(); + } + + @Override + public Logger getLogger() { + return wrappedRestlet.getLogger(); + } + + @Override + public String getName() { + return wrappedRestlet.getName(); + } + + @Override + public String getOwner() { + return wrappedRestlet.getOwner(); + } + + @Override + public void handle(Request request, Response response) { + wrappedRestlet.handle(request, response); + } + + @Override + public boolean isStarted() { + return wrappedRestlet.isStarted(); + } + + @Override + public boolean isStopped() { + return wrappedRestlet.isStopped(); + } + + @Override + public void setAuthor(String author) { + wrappedRestlet.setAuthor(author); + } + + @Override + public void setContext(Context context) { + wrappedRestlet.setContext(context); + } + + @Override + public void setDescription(String description) { + wrappedRestlet.setDescription(description); + } + + @Override + public void setName(String name) { + wrappedRestlet.setName(name); + } + + @Override + public void setOwner(String owner) { + wrappedRestlet.setOwner(owner); + } + + @Override + public synchronized void start() throws Exception { + wrappedRestlet.start(); + } + + @Override + public synchronized void stop() throws Exception { + wrappedRestlet.stop(); + } } diff --git a/org.restlet/src/main/java/org/restlet/util/package-info.java b/org.restlet/src/main/java/org/restlet/util/package-info.java new file mode 100644 index 0000000000..b35cb27be8 --- /dev/null +++ b/org.restlet/src/main/java/org/restlet/util/package-info.java @@ -0,0 +1,6 @@ +/** + * Various utility classes. + * + * @since Restlet 1.0 + */ +package org.restlet.util; diff --git a/org.restlet/src/main/java/org/restlet/util/package.html b/org.restlet/src/main/java/org/restlet/util/package.html deleted file mode 100644 index b8e7177896..0000000000 --- a/org.restlet/src/main/java/org/restlet/util/package.html +++ /dev/null @@ -1,7 +0,0 @@ - - - Various utility classes. -

- @since Restlet 1.0 - - diff --git a/org.restlet/src/test/java/org/restlet/ApplicationContextTestCase.java b/org.restlet/src/test/java/org/restlet/ApplicationContextTestCase.java index 225edaea82..1a3e3f17c8 100644 --- a/org.restlet/src/test/java/org/restlet/ApplicationContextTestCase.java +++ b/org.restlet/src/test/java/org/restlet/ApplicationContextTestCase.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -21,12 +22,11 @@ import org.restlet.resource.ServerResource; import org.restlet.routing.Router; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** - * Tests that when issuing internal calls, the application context is kept intact in the caller server resource. + * Tests that when issuing internal calls, the application context is kept intact in the caller + * server resource. */ -public class ApplicationContextTestCase { +class ApplicationContextTestCase { public static class InternalApplication extends Application { @@ -82,15 +82,16 @@ protected void setUpEach() throws Exception { @AfterEach protected void tearDownEach() throws Exception { Engine.clearThreadLocalVariables(); - this.component.stop(); + this.component.stop(); } @Test - public void testApplicationContext() throws Exception { + void testApplicationContext() throws Exception { ClientResource res = new ClientResource("http://localhost:" + testPort + "/api/test"); Representation rep = res.get(MediaType.TEXT_PLAIN); // following https://github.com/restlet/restlet-framework-java/issues/1317 fix, - // should return "InternalApplication" since the current Application thread variable has not been cleared + // should return "InternalApplication" since the current Application thread variable has not + // been cleared assertEquals("InternalApplication", rep.getText()); } } diff --git a/org.restlet/src/test/java/org/restlet/Bug1145TestCase.java b/org.restlet/src/test/java/org/restlet/Bug1145TestCase.java index 9f68b77711..2dc1adc3cb 100644 --- a/org.restlet/src/test/java/org/restlet/Bug1145TestCase.java +++ b/org.restlet/src/test/java/org/restlet/Bug1145TestCase.java @@ -1,19 +1,17 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashSet; import java.util.List; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,7 +22,7 @@ import org.restlet.engine.Engine; import org.restlet.representation.StringRepresentation; -public class Bug1145TestCase { +class Bug1145TestCase { public static class Bug1145TestCaseRestlet extends Restlet { @Override public void handle(Request request, Response response) { @@ -63,7 +61,7 @@ public void tearDownEach() throws Exception { } @Test - public void test0() throws Exception { + void test0() throws Exception { Request request = new Request(Method.GET, "http://localhost:" + testPort); Response result = client.handle(request); assertEquals(Status.SUCCESS_OK, result.getStatus()); diff --git a/org.restlet/src/test/java/org/restlet/CallTestCase.java b/org.restlet/src/test/java/org/restlet/CallTestCase.java index a0bf453bc0..6f2c4decc4 100644 --- a/org.restlet/src/test/java/org/restlet/CallTestCase.java +++ b/org.restlet/src/test/java/org/restlet/CallTestCase.java @@ -1,58 +1,34 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.Test; import org.restlet.data.ClientInfo; import org.restlet.data.Method; import org.restlet.data.Reference; import org.restlet.data.Status; -import org.restlet.engine.adapter.Call; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; /** * Test {@link org.restlet.engine.adapter.Call}. - * + * * @author Lars Heuer (heuer[at]semagia.com) */ -public class CallTestCase { - /** - * Returns a connector call. - * - * @return A connector call instance. - */ - protected Call getCall() { - return new Call() { - - @Override - protected boolean isClientKeepAlive() { - return false; - } - - @Override - protected boolean isServerKeepAlive() { - return false; - } - - }; - } +class CallTestCase { /** * Returns a reference with the specified URI. - * - * @param uri - * The URI. + * + * @param uri The URI. * @return Reference instance. */ protected Reference getReference(String uri) { @@ -61,7 +37,7 @@ protected Reference getReference(String uri) { /** * Returns a request. - * + * * @return Request instance. */ protected Request getRequest() { @@ -70,32 +46,29 @@ protected Request getRequest() { /** * Returns a response. - * - * @param request - * The associated request. + * + * @param request The associated request. * @return Response instance. */ protected Response getResponse(Request request) { return new Response(request); } - /** - * Tests context's base reference getting/setting. - */ + /** Tests context's base reference getting/setting. */ @Test - public void testBaseRef() { + void testBaseRef() { final Request request = getRequest(); final String resourceRefURI = "http://restlet.org/path/to/resource"; final Reference resourceRef = getReference(resourceRefURI); request.setResourceRef(resourceRefURI); assertEquals(resourceRef, request.getResourceRef()); - + String uri = "http://restlet.org/path"; Reference reference = getReference(uri); request.getResourceRef().setBaseRef(uri); assertEquals(uri, request.getResourceRef().getBaseRef().toString()); assertEquals(reference, request.getResourceRef().getBaseRef()); - + uri = "http://restlet.org/path/to"; reference = getReference(uri); request.getResourceRef().setBaseRef(uri); @@ -103,11 +76,9 @@ public void testBaseRef() { assertEquals(reference, request.getResourceRef().getBaseRef()); } - /** - * Tests client address getting/setting. - */ + /** Tests client address getting/setting. */ @Test - public void testClientAddress() { + void testClientAddress() { final ClientInfo client = getRequest().getClientInfo(); String address = "127.0.0.1"; client.setAddress(address); @@ -115,91 +86,79 @@ public void testClientAddress() { assertEquals(0, client.getForwardedAddresses().size()); } - /** - * Tests client agent getting/setting. - */ + /** Tests client agent getting/setting. */ @Test - public void testClientAgent() { + void testClientAgent() { final ClientInfo client = getRequest().getClientInfo(); String name = "Restlet"; client.setAgent(name); assertEquals(name, client.getAgent()); - + name = "Restlet Client"; client.setAgent(name); assertEquals(name, client.getAgent()); } - /** - * Tests client addresses getting/setting. - */ + /** Tests client addresses getting/setting. */ @Test - public void testClientForwardedAddresses() { + void testClientForwardedAddresses() { final ClientInfo client = getRequest().getClientInfo(); String firstAddress = "127.0.0.1"; final String secondAddress = "192.168.99.10"; List addresses = Arrays.asList(firstAddress, secondAddress); client.getForwardedAddresses().addAll(addresses); assertEquals(addresses, client.getForwardedAddresses()); - + client.getForwardedAddresses().clear(); client.getForwardedAddresses().addAll(addresses); assertEquals(addresses, client.getForwardedAddresses()); } - /** - * Tests method getting/setting. - */ + /** Tests method getting/setting. */ @Test - public void testMethod() { + void testMethod() { final Request request = getRequest(); request.setMethod(Method.GET); assertEquals(Method.GET, request.getMethod()); - + request.setMethod(Method.POST); assertEquals(Method.POST, request.getMethod()); } - /** - * Tests redirection reference getting/setting. - */ + /** Tests redirection reference getting/setting. */ @Test - public void testRedirectionRef() { + void testRedirectionRef() { final Request request = getRequest(); final Response response = getResponse(request); String uri = "http://restlet.org/"; Reference reference = getReference(uri); response.setLocationRef(uri); assertEquals(reference, response.getLocationRef()); - + uri = "http://restlet.org/something"; reference = getReference(uri); response.setLocationRef(reference); assertEquals(reference, response.getLocationRef()); } - /** - * Tests referrer reference getting/setting. - */ + /** Tests referrer reference getting/setting. */ @Test - public void testReferrerRef() { + void testReferrerRef() { final Request request = getRequest(); String uri = "http://restlet.org/"; Reference reference = getReference(uri); request.setReferrerRef(uri); assertEquals(reference, request.getReferrerRef()); - + uri = "http://restlet.org/something"; reference = getReference(uri); request.setReferrerRef(reference); assertEquals(reference, request.getReferrerRef()); } - /** - * Tests resource reference getting/setting. - */ + /** Tests resource reference getting/setting. */ @Test - public void testResourceRef() { + void testResourceRef() { final Request request = getRequest(); String uri = "http://restlet.org/"; Reference reference = getReference(uri); @@ -211,50 +170,43 @@ public void testResourceRef() { assertEquals(reference, request.getResourceRef()); } - /** - * Tests server address getting/setting. - */ + /** Tests server address getting/setting. */ @Test - public void testServerAddress() { + void testServerAddress() { final Request request = getRequest(); final Response response = getResponse(request); String address = "127.0.0.1"; response.getServerInfo().setAddress(address); assertEquals(address, response.getServerInfo().getAddress()); - + address = "192.168.99.10"; response.getServerInfo().setAddress(address); assertEquals(address, response.getServerInfo().getAddress()); } - /** - * Tests server agent getting/setting. - */ + /** Tests server agent getting/setting. */ @Test - public void testServerAgent() { + void testServerAgent() { final Request request = getRequest(); final Response response = getResponse(request); String name = "Restlet"; response.getServerInfo().setAgent(name); assertEquals(name, response.getServerInfo().getAgent()); - + name = "Restlet Server"; response.getServerInfo().setAgent(name); assertEquals(name, response.getServerInfo().getAgent()); } - /** - * Tests status getting/setting. - */ + /** Tests status getting/setting. */ @Test - public void testStatus() { + void testStatus() { final Request request = getRequest(); final Response response = getResponse(request); response.setStatus(Status.SUCCESS_OK); assertEquals(Status.SUCCESS_OK, response.getStatus()); - + response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST); assertEquals(Status.CLIENT_ERROR_BAD_REQUEST, response.getStatus()); } - } diff --git a/org.restlet/src/test/java/org/restlet/RestartTestCase.java b/org.restlet/src/test/java/org/restlet/RestartTestCase.java index a2e47b8964..fa651ae719 100644 --- a/org.restlet/src/test/java/org/restlet/RestartTestCase.java +++ b/org.restlet/src/test/java/org/restlet/RestartTestCase.java @@ -1,28 +1,26 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.time.Duration; import org.junit.jupiter.api.Test; import org.restlet.data.Protocol; -import java.time.Duration; - /** * Test the ability of a connector to be restarted. * * @author Jerome Louvel */ -public class RestartTestCase { +class RestartTestCase { @Test void testRestart() throws Exception { @@ -44,5 +42,4 @@ void testRestart() throws Exception { connector.stop(); assertFalse(connector.isStarted()); } - } diff --git a/org.restlet/src/test/java/org/restlet/data/AuthenticationInfoTestCase.java b/org.restlet/src/test/java/org/restlet/data/AuthenticationInfoTestCase.java index 1a2350716a..59d67ac473 100644 --- a/org.restlet/src/test/java/org/restlet/data/AuthenticationInfoTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/AuthenticationInfoTestCase.java @@ -1,75 +1,67 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.junit.jupiter.api.Test; -import org.restlet.engine.security.AuthenticatorUtils; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import org.junit.jupiter.api.Test; +import org.restlet.engine.security.AuthenticatorUtils; + /** * Test {@link org.restlet.data.Reference}. - * + * * @author Kelly McLaughlin (mclaughlin77[at]gmail.com) */ -public class AuthenticationInfoTestCase { - /** - * Test parsing an Authorization-Info header string. - */ +class AuthenticationInfoTestCase { + /** Test parsing an Authorization-Info header string. */ @Test - public void testAuthenticationInfoHeaderParse() { - AuthenticationInfo authInfo = new AuthenticationInfo("00000002", 1, - "MDAzMTAw1", "auth", null); + void testAuthenticationInfoHeaderParse() { + AuthenticationInfo authInfo = + new AuthenticationInfo("00000002", 1, "MDAzMTAw1", "auth", null); String authInfoHeader = "nc=00000001, qop=auth, cnonce=\"MDAzMTAw1\", nextnonce=00000002"; - AuthenticationInfo parsedAuthInfo = AuthenticatorUtils.parseAuthenticationInfo(authInfoHeader); + AuthenticationInfo parsedAuthInfo = + AuthenticatorUtils.parseAuthenticationInfo(authInfoHeader); assertEquals(authInfo, parsedAuthInfo); assertEquals(parsedAuthInfo, authInfo); } - /** - * Test cnonce getting/setting. - */ + /** Test cnonce getting/setting. */ @Test - public void testCnonce() { - AuthenticationInfo authInfo = new AuthenticationInfo("testnonce", - 1111111, "testcnonce", "auth", "FFFFFF"); - assertEquals(authInfo.getClientNonce(), "testcnonce"); + void testCnonce() { + AuthenticationInfo authInfo = + new AuthenticationInfo("testnonce", 1111111, "testcnonce", "auth", "FFFFFF"); + assertEquals("testcnonce", authInfo.getClientNonce()); String newCnonce = "newcnonce"; authInfo.setClientNonce(newCnonce); - assertEquals(authInfo.getClientNonce(), "newcnonce"); + assertEquals("newcnonce", authInfo.getClientNonce()); } - /** - * Equality tests. - */ + /** Equality tests. */ @Test - public void testEquals() { - final AuthenticationInfo authInfo1 = new AuthenticationInfo( - "testnonce", 1111111, "testcnonce", "auth", "FFFFFF"); - final AuthenticationInfo authInfo2 = new AuthenticationInfo( - "testnonce", 1111111, "testcnonce", "auth", "FFFFFF"); + void testEquals() { + final AuthenticationInfo authInfo1 = + new AuthenticationInfo("testnonce", 1111111, "testcnonce", "auth", "FFFFFF"); + final AuthenticationInfo authInfo2 = + new AuthenticationInfo("testnonce", 1111111, "testcnonce", "auth", "FFFFFF"); assertEquals(authInfo1, authInfo2); assertEquals(authInfo1, authInfo2); } - /** - * Test nextnonce getting/setting. - */ + /** Test nextnonce getting/setting. */ @Test - public void testNextNonce() { - AuthenticationInfo authInfo = new AuthenticationInfo("testnonce", - 1111111, "testcnonce", "auth", "FFFFFF"); + void testNextNonce() { + AuthenticationInfo authInfo = + new AuthenticationInfo("testnonce", 1111111, "testcnonce", "auth", "FFFFFF"); assertEquals(authInfo.getNextServerNonce(), "testnonce"); String newNonce = "newnonce"; @@ -77,13 +69,11 @@ public void testNextNonce() { assertEquals(authInfo.getNextServerNonce(), "newnonce"); } - /** - * Test nonce-count getting/setting. - */ + /** Test nonce-count getting/setting. */ @Test - public void testNonceCount() { - AuthenticationInfo authInfo = new AuthenticationInfo("testnonce", - 1111111, "testcnonce", "auth", "FFFFFF"); + void testNonceCount() { + AuthenticationInfo authInfo = + new AuthenticationInfo("testnonce", 1111111, "testcnonce", "auth", "FFFFFF"); assertEquals(authInfo.getNonceCount(), 1111111); int newNonceCount = 2222222; @@ -91,13 +81,11 @@ public void testNonceCount() { assertEquals(authInfo.getNonceCount(), 2222222); } - /** - * Test message-qop getting/setting. - */ + /** Test message-qop getting/setting. */ @Test - public void testQop() { - AuthenticationInfo authInfo = new AuthenticationInfo("testnonce", - 1111111, "testcnonce", "auth", "FFFFFF"); + void testQop() { + AuthenticationInfo authInfo = + new AuthenticationInfo("testnonce", 1111111, "testcnonce", "auth", "FFFFFF"); assertEquals(authInfo.getQuality(), "auth"); String newQop = "auth-int"; @@ -105,13 +93,11 @@ public void testQop() { assertEquals(authInfo.getQuality(), "auth-int"); } - /** - * Test response-auth getting/setting. - */ + /** Test response-auth getting/setting. */ @Test - public void testResponseAuth() { - AuthenticationInfo authInfo = new AuthenticationInfo("testnonce", - 1111111, "testcnonce", "auth", "FFFFFF"); + void testResponseAuth() { + AuthenticationInfo authInfo = + new AuthenticationInfo("testnonce", 1111111, "testcnonce", "auth", "FFFFFF"); assertEquals(authInfo.getResponseDigest(), "FFFFFF"); String newResponseAuth = "000000"; @@ -120,11 +106,11 @@ public void testResponseAuth() { } @Test - public void testUnEquals() { - final AuthenticationInfo authInfo1 = new AuthenticationInfo( - "testnonce1", 1111111, "testcnonce1", "auth", "FFFFFF"); - final AuthenticationInfo authInfo2 = new AuthenticationInfo( - "testnonce2", 1111111, "testcnonce2", "auth", "FFFFFF"); + void testUnEquals() { + final AuthenticationInfo authInfo1 = + new AuthenticationInfo("testnonce1", 1111111, "testcnonce1", "auth", "FFFFFF"); + final AuthenticationInfo authInfo2 = + new AuthenticationInfo("testnonce2", 1111111, "testcnonce2", "auth", "FFFFFF"); assertNotEquals(authInfo1, authInfo2); assertNotEquals(null, authInfo1); diff --git a/org.restlet/src/test/java/org/restlet/data/ClientInfoTestCase.java b/org.restlet/src/test/java/org/restlet/data/ClientInfoTestCase.java index 8f81d3914f..0b3acbc718 100644 --- a/org.restlet/src/test/java/org/restlet/data/ClientInfoTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/ClientInfoTestCase.java @@ -1,14 +1,23 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.restlet.data.Language.ENGLISH; +import static org.restlet.data.Language.ENGLISH_US; +import static org.restlet.data.Language.FRENCH; +import static org.restlet.data.Language.FRENCH_FRANCE; +import static org.restlet.data.MediaType.APPLICATION_XML; +import static org.restlet.data.MediaType.TEXT_PLAIN; +import static org.restlet.data.MediaType.TEXT_XML; + +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -17,18 +26,12 @@ import org.restlet.service.ConnegService; import org.restlet.service.MetadataService; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.restlet.data.Language.*; -import static org.restlet.data.MediaType.*; - /** * Test {@link org.restlet.data.ClientInfo} for content negotiation. * * @author Jerome Louvel */ -public class ClientInfoTestCase { +class ClientInfoTestCase { @Nested class MixLanguageMediaTypeContentNegotiationTextCase { @@ -46,69 +49,68 @@ void setup() { } @Test - public void shouldReturnEnUsAndTextXml() { - List variants = List.of( - new Variant(TEXT_XML, ENGLISH_US), - new Variant(TEXT_XML, FRENCH_FRANCE)); + void shouldReturnEnUsAndTextXml() { + List variants = + List.of( + new Variant(TEXT_XML, ENGLISH_US), + new Variant(TEXT_XML, FRENCH_FRANCE)); Variant pv = connegService.getPreferredVariant(variants, request, ms); assertEquals(TEXT_XML, pv.getMediaType()); - assertEquals(ENGLISH_US, pv.getLanguages().get(0)); + assertEquals(ENGLISH_US, pv.getLanguages().getFirst()); } @Test - public void shouldReturnEnAndTextXml() { - List variants = List.of( - new Variant(TEXT_XML, ENGLISH), - new Variant(TEXT_XML, FRENCH)); + void shouldReturnEnAndTextXml() { + List variants = + List.of(new Variant(TEXT_XML, ENGLISH), new Variant(TEXT_XML, FRENCH)); Variant pv = connegService.getPreferredVariant(variants, request, ms); assertEquals(TEXT_XML, pv.getMediaType()); - assertEquals(ENGLISH, pv.getLanguages().get(0)); + assertEquals(ENGLISH, pv.getLanguages().getFirst()); } // Testing quality priority over parent metadata @Test - public void shouldReturnFrFrAndText() { - List variants = List.of( - new Variant(TEXT_PLAIN, ENGLISH), - new Variant(TEXT_XML, FRENCH_FRANCE)); + void shouldReturnFrFrAndText() { + List variants = + List.of(new Variant(TEXT_PLAIN, ENGLISH), new Variant(TEXT_XML, FRENCH_FRANCE)); Variant pv = connegService.getPreferredVariant(variants, request, ms); assertEquals(TEXT_XML, pv.getMediaType()); - assertEquals(FRENCH_FRANCE, pv.getLanguages().get(0)); + assertEquals(FRENCH_FRANCE, pv.getLanguages().getFirst()); } // Testing quality priority over parent metadata @Test - public void shouldReturnFrFrAndXml() { - List variants = List.of( - new Variant(APPLICATION_XML, ENGLISH_US), - new Variant(TEXT_XML, FRENCH_FRANCE)); + void shouldReturnFrFrAndXml() { + List variants = + List.of( + new Variant(APPLICATION_XML, ENGLISH_US), + new Variant(TEXT_XML, FRENCH_FRANCE)); Variant pv = connegService.getPreferredVariant(variants, request, ms); assertEquals(TEXT_XML, pv.getMediaType()); - assertEquals(FRENCH_FRANCE, pv.getLanguages().get(0)); + assertEquals(FRENCH_FRANCE, pv.getLanguages().getFirst()); } // Leveraging parent media types @Test - public void shouldPreferEnUsAndApplicationXml() { - List variants = List.of( - new Variant(APPLICATION_XML, ENGLISH_US), - new Variant(APPLICATION_XML, FRENCH_FRANCE)); + void shouldPreferEnUsAndApplicationXml() { + List variants = + List.of( + new Variant(APPLICATION_XML, ENGLISH_US), + new Variant(APPLICATION_XML, FRENCH_FRANCE)); Variant pv = connegService.getPreferredVariant(variants, request, ms); assertEquals(APPLICATION_XML, pv.getMediaType()); - assertEquals(ENGLISH_US, pv.getLanguages().get(0)); + assertEquals(ENGLISH_US, pv.getLanguages().getFirst()); } } - /** - * Conneg tests for IE which accepts all media types. - */ + /** Conneg tests for IE which accepts all media types. */ @Test - public void testConnegIe() { + void testConnegIe() { ClientInfo ci = new ClientInfo(); Preference allMediaTypesPreference = new Preference<>(MediaType.ALL, 1.0F); ci.getAcceptedMediaTypes().add(allMediaTypesPreference); @@ -118,5 +120,4 @@ public void testConnegIe() { assertEquals(TEXT_XML, pmt); } - } diff --git a/org.restlet/src/test/java/org/restlet/data/CookieTestCase.java b/org.restlet/src/test/java/org/restlet/data/CookieTestCase.java index 27eeb8ed3e..8ad9128295 100644 --- a/org.restlet/src/test/java/org/restlet/data/CookieTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/CookieTestCase.java @@ -1,31 +1,28 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import org.junit.jupiter.api.Test; + /** * Test {@link org.restlet.data.Cookie}. - * + * * @author Jerome Louvel */ -public class CookieTestCase { +class CookieTestCase { - /** - * Equality tests. - */ + /** Equality tests. */ @Test - public void testEquals() { + void testEquals() { Cookie c1 = new Cookie(1, "name1", "value1", "path1", "domain1"); Cookie c2 = new Cookie(1, "name1", "value1", "path1", "domain1"); @@ -34,11 +31,9 @@ public void testEquals() { assertEquals(c1, c2); } - /** - * Inequality tests. - */ + /** Inequality tests. */ @Test - public void testUnEquals() { + void testUnEquals() { Cookie c1 = new Cookie(1, "name1", "value1", "path1", "domain1"); Cookie c2 = new Cookie(2, "name2", "value2", "path2", "domain2"); assertNotEquals(c1, c2); @@ -51,5 +46,4 @@ public void testUnEquals() { assertNotEquals(c1, c2); assertNotEquals(c1.hashCode(), c2.hashCode()); } - } diff --git a/org.restlet/src/test/java/org/restlet/data/FileClientTestCase.java b/org.restlet/src/test/java/org/restlet/data/FileClientTestCase.java index 273d81cf9d..71c120ef86 100644 --- a/org.restlet/src/test/java/org/restlet/data/FileClientTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/FileClientTestCase.java @@ -1,34 +1,32 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; import org.junit.jupiter.api.Test; import org.restlet.engine.Engine; import org.restlet.representation.StringRepresentation; import org.restlet.resource.ClientResource; -import java.io.File; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - /** * Unit test case for the File client connector. - * + * * @author Jerome Louvel */ -public class FileClientTestCase { +class FileClientTestCase { @Test - public void testFileClient() throws IOException { + void testFileClient() throws IOException { Engine.register(); Engine.clearThreadLocalVariables(); diff --git a/org.restlet/src/test/java/org/restlet/data/FileReferenceTestCase.java b/org.restlet/src/test/java/org/restlet/data/FileReferenceTestCase.java index 4cf3e9348f..04c17e3602 100644 --- a/org.restlet/src/test/java/org/restlet/data/FileReferenceTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/FileReferenceTestCase.java @@ -1,29 +1,27 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; /** * Unit test case for the File Reference parsing. - * + * * @author Jerome Louvel */ -public class FileReferenceTestCase { +class FileReferenceTestCase { @Test - public void testCreation() { + void testCreation() { String path = "D:\\Restlet\\build.xml"; LocalReference fr = LocalReference.createFileReference(path); fr.getFile(); diff --git a/org.restlet/src/test/java/org/restlet/data/FormTestCase.java b/org.restlet/src/test/java/org/restlet/data/FormTestCase.java index 24dc0a88d0..6e46ed17ac 100644 --- a/org.restlet/src/test/java/org/restlet/data/FormTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/FormTestCase.java @@ -1,31 +1,29 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.junit.jupiter.api.Test; -import org.restlet.engine.util.FormReader; - -import java.io.IOException; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import java.io.IOException; +import org.junit.jupiter.api.Test; +import org.restlet.engine.util.FormReader; + /** * Unit tests for the {@link Form} class. - * + * * @author Jerome Louvel */ -public class FormTestCase { +class FormTestCase { @Test - public void testParsing() throws IOException { + void testParsing() throws IOException { Form form = new Form(); form.add("name", "John D. Mitchell"); form.add("email", "john@bob.net"); @@ -39,7 +37,7 @@ public void testParsing() throws IOException { } @Test - public void testEmptyParameter() { + void testEmptyParameter() { // Manual construction of form Form form = new Form(); form.add("normalParam", "abcd"); @@ -60,5 +58,4 @@ public void testEmptyParameter() { assertNull(form.getFirstValue("nullParam")); assertNull(form.getFirstValue("unknownParam")); } - } diff --git a/org.restlet/src/test/java/org/restlet/data/LanguageTestCase.java b/org.restlet/src/test/java/org/restlet/data/LanguageTestCase.java index 1eff0bc45f..058e071ba5 100644 --- a/org.restlet/src/test/java/org/restlet/data/LanguageTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/LanguageTestCase.java @@ -1,37 +1,36 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.List; +import org.junit.jupiter.api.Test; + /** * Test {@link org.restlet.data.Language}. * * @author Jerome Louvel */ -public class LanguageTestCase { +class LanguageTestCase { - /** - * Testing {@link Language#valueOf(String)} - */ + /** Testing {@link Language#valueOf(String)} */ @Test - public void testValueOf() { + void testValueOf() { assertSame(Language.FRENCH_FRANCE, Language.valueOf("fr-fr")); assertSame(Language.ALL, Language.valueOf("*")); } @Test - public void testUnmodifiable() { - assertThrows(UnsupportedOperationException.class, () -> Language.FRENCH_FRANCE.getSubTags().add("foo")); + void testUnmodifiable() { + final List subTags = Language.FRENCH_FRANCE.getSubTags(); + assertThrows(UnsupportedOperationException.class, () -> subTags.add("foo")); } } diff --git a/org.restlet/src/test/java/org/restlet/data/MediaTypeTestCase.java b/org.restlet/src/test/java/org/restlet/data/MediaTypeTestCase.java index a9ef47bd52..e5a4ebb192 100644 --- a/org.restlet/src/test/java/org/restlet/data/MediaTypeTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/MediaTypeTestCase.java @@ -1,83 +1,98 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.restlet.data.MediaType.ALL; +import static org.restlet.data.MediaType.APPLICATION_ALL; +import static org.restlet.data.MediaType.APPLICATION_ALL_XML; +import static org.restlet.data.MediaType.APPLICATION_ATOM; +import static org.restlet.data.MediaType.APPLICATION_ATOMPUB_SERVICE; +import static org.restlet.data.MediaType.APPLICATION_OCTET_STREAM; +import static org.restlet.data.MediaType.APPLICATION_XML; +import static org.restlet.data.MediaType.IMAGE_ALL; +import static org.restlet.data.MediaType.TEXT_ALL; +import static org.restlet.data.MediaType.TEXT_PLAIN; +import static org.restlet.data.MediaType.getMostSpecific; +import static org.restlet.data.MediaType.register; +import static org.restlet.data.MediaType.valueOf; + +import java.util.stream.Stream; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; import org.restlet.util.Series; -import static org.junit.jupiter.api.Assertions.*; - /** * Test {@link org.restlet.data.MediaType}. - * + * * @author Jerome Louvel */ -public class MediaTypeTestCase { +class MediaTypeTestCase { /** - * Makes sure that a {@link MediaType} instance initialized on the specified - * name has the expected values. - * - * @param name - * type to analyze. - * @param main - * expected main type. - * @param sub - * expected subtype. - * @param concrete - * expected 'concrete' flag. + * Makes sure that a {@link MediaType} instance initialized on the specified name has the + * expected values. + * + * @param name type to analyze. + * @param expectedMain expected main type. + * @param expectedSub expected subtype. + * @param expectedConcrete expected 'concrete' flag. */ - public void assertMediaType(String name, String main, String sub, - boolean concrete) { + public void assertMediaType( + String name, String expectedMain, String expectedSub, boolean expectedConcrete) { MediaType type; type = new MediaType(name); - assertEquals(main, type.getMainType()); - assertEquals(sub, type.getSubType()); - assertEquals(concrete, type.isConcrete()); + assertEquals(expectedMain, type.getMainType()); + assertEquals(expectedSub, type.getSubType()); + assertEquals(expectedConcrete, type.isConcrete()); } - /** - * Makes sure concrete types are properly initialized. - */ - @Test - public void testConcrete() { - assertMediaType("application/xml", "application", "xml", true); - assertMediaType("application/ xml ", "application", "xml", true); - assertMediaType(" application /xml", "application", "xml", true); - assertMediaType(" application / xml ", "application", "xml", true); - assertMediaType("application/atom+xml;type=entry", "application", - "atom+xml", true); + /** Makes sure concrete types are properly initialized. */ + @ParameterizedTest + @CsvSource({ + "application/xml,application,xml,true", + "application/ xml,application,xml,true,", + "application /xml,application,xml,true", + "application / xml ,application,xml,true", + "application/atom+xml;type=entry,application,atom+xml,true", + }) + void testConcrete( + String name, String expectedMain, String expectedSub, boolean expectedConcrete) { + assertMediaType(name, expectedMain, expectedSub, expectedConcrete); } - /** - * Makes sure concrete types are properly initialized. - */ + /** Makes sure concrete types are properly initialized. */ @Test - public void testParameters() { - MediaType mt = MediaType.valueOf("application/atom+xml;type=entry"); + void testParameters() { + MediaType mt = valueOf("application/atom+xml;type=entry"); assertEquals("entry", mt.getParameters().getFirstValue("type")); - mt = MediaType - .valueOf("multipart/x-mixed-replace; boundary=\"My boundary\""); - assertEquals("\"My boundary\"", - mt.getParameters().getFirstValue("boundary")); + mt = valueOf("multipart/x-mixed-replace; boundary=\"My boundary\""); + assertEquals("\"My boundary\"", mt.getParameters().getFirstValue("boundary")); } - /** - * Equality tests. - */ + /** Equality tests. */ @Test - public void testEquals() { + void testEquals() { MediaType mt1 = new MediaType("application/xml"); - MediaType mt2 = MediaType.APPLICATION_XML; + MediaType mt2 = APPLICATION_XML; assertEquals(mt1, mt2); assertEquals(mt1, mt2); @@ -100,202 +115,181 @@ public void testEquals() { assertTrue(mt1Bis.equals(mt3, true)); mt1 = new MediaType("application/*"); - mt2 = MediaType.APPLICATION_ALL; + mt2 = APPLICATION_ALL; assertEquals(mt1, mt2); assertEquals(mt1, mt2); } - /** - * Test inclusion. - */ - @Test - public void testIncludes() { - MediaType mt1 = MediaType.APPLICATION_ALL; - MediaType mt2 = MediaType.APPLICATION_XML; - assertTrue(mt1.includes(mt1)); - assertTrue(mt2.includes(mt2)); - assertTrue(mt1.includes(mt2)); - assertFalse(mt2.includes(mt1)); - - mt1 = MediaType.APPLICATION_ALL_XML; - mt2 = MediaType.APPLICATION_XML; - assertTrue(mt1.includes(mt1)); - assertTrue(mt2.includes(mt2)); - assertTrue(mt1.includes(mt2)); - assertFalse(mt2.includes(mt1)); - - mt1 = MediaType.APPLICATION_ALL_XML; - mt2 = MediaType.APPLICATION_ATOMPUB_SERVICE; - assertTrue(mt1.includes(mt1)); - assertTrue(mt2.includes(mt2)); - assertTrue(mt1.includes(mt2)); - assertFalse(mt2.includes(mt1)); - - mt1 = MediaType.IMAGE_ALL; - mt2 = MediaType.APPLICATION_OCTET_STREAM; - assertFalse(mt1.includes(mt2)); - assertFalse(mt2.includes(mt1)); - - assertFalse(mt1.includes(null)); - - /* - * test inclusion for media types with parameters. The rule is: media - * type A includes media type B iff for each parameter name/value pair - * in A, B contains the same parameter name/value pair + /** Test inclusion. */ + @Nested + class Inclusion { + + @ParameterizedTest + @MethodSource("inclusionTestCases") + void shouldInclude(MediaType mt, MediaType included) { + assertTrue(mt.includes(included)); + } + + static Stream inclusionTestCases() { + return Stream.of( + Arguments.of(APPLICATION_ALL, APPLICATION_ALL), + Arguments.of(APPLICATION_XML, APPLICATION_XML), + Arguments.of(APPLICATION_ALL, APPLICATION_XML), + Arguments.of(APPLICATION_ALL_XML, APPLICATION_ALL_XML), + Arguments.of(APPLICATION_XML, APPLICATION_XML), + Arguments.of(APPLICATION_ALL_XML, APPLICATION_XML), + Arguments.of(APPLICATION_ATOMPUB_SERVICE, APPLICATION_ATOMPUB_SERVICE), + Arguments.of(APPLICATION_ALL_XML, APPLICATION_ATOMPUB_SERVICE)); + } + + @ParameterizedTest + @MethodSource("noInclusionTestCases") + void shouldNotInclude(MediaType mt, MediaType notIncluded) { + assertFalse(mt.includes(notIncluded)); + } + + static Stream noInclusionTestCases() { + return Stream.of( + Arguments.of(APPLICATION_XML, APPLICATION_ALL), + Arguments.of(APPLICATION_XML, APPLICATION_ALL_XML), + Arguments.of(APPLICATION_ATOMPUB_SERVICE, APPLICATION_ALL_XML), + Arguments.of(IMAGE_ALL, APPLICATION_OCTET_STREAM), + Arguments.of(APPLICATION_OCTET_STREAM, IMAGE_ALL), + Arguments.of(IMAGE_ALL, null)); + } + + /** + * test inclusion for media types with parameters. The rule is: media type A includes media + * type B iff for each parameter name/value pair in A, B contains the same parameter + * name/value pair */ - - // set up test data - - MediaType typeWithNoParams = new MediaType("application/sometype"); - - Series singleParam = new Series<>(Parameter.class); - singleParam.add(new Parameter("name1", "value1")); - MediaType typeWithSingleParam = new MediaType("application/sometype", - singleParam); - - Series singleMatchingParam = new Series<>( - Parameter.class); - singleMatchingParam.add(new Parameter("name1", "value1")); - MediaType typeWithSingleMatchingParam = new MediaType( - "application/sometype", singleMatchingParam); - - Series singleNonMatchingParamValue = new Series<>( - Parameter.class); - singleNonMatchingParamValue.add(new Parameter("name1", "value2")); - MediaType typeWithSingleNonMatchingParamValue = new MediaType( - "application/sometype", singleNonMatchingParamValue); - - Series singleNonMatchingParamName = new Series<>( - Parameter.class); - singleNonMatchingParamName.add(new Parameter("name2", "value2")); - MediaType typeWithSingleNonMatchingParamName = new MediaType( - "application/sometype", singleNonMatchingParamName); - - Series twoParamsOneMatches = new Series<>( - Parameter.class); - twoParamsOneMatches.add(new Parameter("name1", "value1")); - twoParamsOneMatches.add(new Parameter("name2", "value2")); - MediaType typeWithTwoParamsOneMatches = new MediaType( - "application/sometype", twoParamsOneMatches); - - // SCENARIO 1: test whether type with no params includes type with one - // param - - assertTrue(typeWithNoParams.includes(typeWithSingleParam, true)); - assertTrue(typeWithNoParams.includes(typeWithSingleParam, false)); - - // SCENARIO 2: test whether type with one param includes type with no - // params - - assertTrue(typeWithSingleParam.includes(typeWithNoParams, true)); - assertFalse(typeWithSingleParam.includes(typeWithNoParams, false)); - - // SCENARIO 3: test whether type with single param includes type with - // matching single param. - // Note that this is distinct from testing whether a type includes - // itself, as there is a special check for that. - assertTrue(typeWithSingleParam.includes(typeWithSingleMatchingParam, - true)); - assertTrue(typeWithSingleParam.includes(typeWithSingleMatchingParam, - false)); - - // SCENARIO 4: test whether type with single param includes type with - // single param having different name - assertTrue(typeWithSingleParam.includes( - typeWithSingleNonMatchingParamName, true)); - assertFalse(typeWithSingleParam.includes( - typeWithSingleNonMatchingParamName, false)); - - // SCENARIO 5: test whether type with single param includes type with - // single param having same name but different value - assertTrue(typeWithSingleParam.includes( - typeWithSingleNonMatchingParamValue, true)); - assertFalse(typeWithSingleParam.includes( - typeWithSingleNonMatchingParamValue, false)); - - // SCENARIO 6: test whether type with single param includes type with - // two params, one matching - assertTrue(typeWithSingleParam.includes(typeWithTwoParamsOneMatches, - true)); - assertTrue(typeWithSingleParam.includes(typeWithTwoParamsOneMatches, - false)); - - // SCENARIO 7: test whether type with two params includes type with - // single matching param - assertTrue(typeWithTwoParamsOneMatches.includes(typeWithSingleParam, - true)); - assertFalse(typeWithTwoParamsOneMatches.includes(typeWithSingleParam, - false)); + @Test + void testIncludes() { + // set up test data + + MediaType typeWithNoParams = new MediaType("application/sometype"); + + Series singleParam = new Series<>(Parameter.class); + singleParam.add(new Parameter("name1", "value1")); + MediaType typeWithSingleParam = new MediaType("application/sometype", singleParam); + + Series singleMatchingParam = new Series<>(Parameter.class); + singleMatchingParam.add(new Parameter("name1", "value1")); + MediaType typeWithSingleMatchingParam = + new MediaType("application/sometype", singleMatchingParam); + + Series singleNonMatchingParamValue = new Series<>(Parameter.class); + singleNonMatchingParamValue.add(new Parameter("name1", "value2")); + MediaType typeWithSingleNonMatchingParamValue = + new MediaType("application/sometype", singleNonMatchingParamValue); + + Series singleNonMatchingParamName = new Series<>(Parameter.class); + singleNonMatchingParamName.add(new Parameter("name2", "value2")); + MediaType typeWithSingleNonMatchingParamName = + new MediaType("application/sometype", singleNonMatchingParamName); + + Series twoParamsOneMatches = new Series<>(Parameter.class); + twoParamsOneMatches.add(new Parameter("name1", "value1")); + twoParamsOneMatches.add(new Parameter("name2", "value2")); + MediaType typeWithTwoParamsOneMatches = + new MediaType("application/sometype", twoParamsOneMatches); + + // SCENARIO 1: test whether type with no params includes type with one + // param + + assertTrue(typeWithNoParams.includes(typeWithSingleParam, true)); + assertTrue(typeWithNoParams.includes(typeWithSingleParam, false)); + + // SCENARIO 2: test whether type with one param includes type with no + // params + + assertTrue(typeWithSingleParam.includes(typeWithNoParams, true)); + assertFalse(typeWithSingleParam.includes(typeWithNoParams, false)); + + // SCENARIO 3: test whether type with single param includes type with + // matching single param. + // Note that this is distinct from testing whether a type includes + // itself, as there is a special check for that. + assertTrue(typeWithSingleParam.includes(typeWithSingleMatchingParam, true)); + assertTrue(typeWithSingleParam.includes(typeWithSingleMatchingParam, false)); + + // SCENARIO 4: test whether type with single param includes type with + // single param having different name + assertTrue(typeWithSingleParam.includes(typeWithSingleNonMatchingParamName, true)); + assertFalse(typeWithSingleParam.includes(typeWithSingleNonMatchingParamName, false)); + + // SCENARIO 5: test whether type with single param includes type with + // single param having the same name but different value + assertTrue(typeWithSingleParam.includes(typeWithSingleNonMatchingParamValue, true)); + assertFalse(typeWithSingleParam.includes(typeWithSingleNonMatchingParamValue, false)); + + // SCENARIO 6: test whether type with single param includes type with + // two params, one matching + assertTrue(typeWithSingleParam.includes(typeWithTwoParamsOneMatches, true)); + assertTrue(typeWithSingleParam.includes(typeWithTwoParamsOneMatches, false)); + + // SCENARIO 7: test whether type with two params includes type with + // single matching param + assertTrue(typeWithTwoParamsOneMatches.includes(typeWithSingleParam, true)); + assertFalse(typeWithTwoParamsOneMatches.includes(typeWithSingleParam, false)); + } } @Test - public void testMostSpecificMediaType() { - assertEquals(MediaType.TEXT_ALL, - MediaType.getMostSpecific(MediaType.ALL, MediaType.TEXT_ALL)); - assertEquals(MediaType.TEXT_ALL, - MediaType.getMostSpecific(MediaType.TEXT_ALL, MediaType.ALL)); - - assertEquals(MediaType.TEXT_PLAIN, MediaType.getMostSpecific( - MediaType.ALL, MediaType.TEXT_ALL, MediaType.TEXT_PLAIN)); - assertEquals(MediaType.TEXT_PLAIN, MediaType.getMostSpecific( - MediaType.ALL, MediaType.TEXT_PLAIN, MediaType.TEXT_ALL)); - assertEquals(MediaType.TEXT_PLAIN, MediaType.getMostSpecific( - MediaType.TEXT_ALL, MediaType.ALL, MediaType.TEXT_PLAIN)); - assertEquals(MediaType.TEXT_PLAIN, MediaType.getMostSpecific( - MediaType.TEXT_ALL, MediaType.TEXT_PLAIN, MediaType.ALL)); - assertEquals(MediaType.TEXT_PLAIN, MediaType.getMostSpecific( - MediaType.TEXT_PLAIN, MediaType.ALL, MediaType.TEXT_ALL)); - assertEquals(MediaType.TEXT_PLAIN, MediaType.getMostSpecific( - MediaType.TEXT_PLAIN, MediaType.TEXT_ALL, MediaType.ALL)); + void testMostSpecificMediaType() { + assertEquals(TEXT_ALL, getMostSpecific(ALL, TEXT_ALL)); + assertEquals(TEXT_ALL, getMostSpecific(TEXT_ALL, ALL)); + + assertEquals(TEXT_PLAIN, getMostSpecific(ALL, TEXT_ALL, TEXT_PLAIN)); + assertEquals(TEXT_PLAIN, getMostSpecific(ALL, TEXT_PLAIN, TEXT_ALL)); + assertEquals(TEXT_PLAIN, getMostSpecific(TEXT_ALL, ALL, TEXT_PLAIN)); + assertEquals(TEXT_PLAIN, getMostSpecific(TEXT_ALL, TEXT_PLAIN, ALL)); + assertEquals(TEXT_PLAIN, getMostSpecific(TEXT_PLAIN, ALL, TEXT_ALL)); + assertEquals(TEXT_PLAIN, getMostSpecific(TEXT_PLAIN, TEXT_ALL, ALL)); } - /** - * Makes sure that 'abstract' types are properly initialised. - */ - @Test - public void testNotConcrete() { - // */* - assertMediaType("", "*", "*", false); - assertMediaType(" ", "*", "*", false); - assertMediaType("*/", "*", "*", false); - assertMediaType("*/ ", "*", "*", false); - assertMediaType(" * /", "*", "*", false); - assertMediaType("/*", "*", "*", false); - assertMediaType(" /*", "*", "*", false); - assertMediaType("/ * ", "*", "*", false); - assertMediaType(" / * ", "*", "*", false); - assertMediaType("*/*", "*", "*", false); - assertMediaType(" * /*", "*", "*", false); - assertMediaType("*/ * ", "*", "*", false); - assertMediaType(" * / * ", "*", "*", false); - - // */xml - assertMediaType("/xml", "*", "xml", false); - assertMediaType("/ xml ", "*", "xml", false); - assertMediaType(" /xml", "*", "xml", false); - assertMediaType(" / xml ", "*", "xml", false); - assertMediaType("*/xml", "*", "xml", false); - assertMediaType(" * /xml", "*", "xml", false); - assertMediaType("*/ xml ", "*", "xml", false); - assertMediaType(" * / xml ", "*", "xml", false); - - // application/* - assertMediaType("application", "application", "*", false); - assertMediaType(" application ", "application", "*", false); - assertMediaType("application/", "application", "*", false); - assertMediaType(" application /", "application", "*", false); - assertMediaType(" application / ", "application", "*", false); - assertMediaType("application/*", "application", "*", false); - assertMediaType(" application /*", "application", "*", false); - assertMediaType("application/ * ", "application", "*", false); - assertMediaType(" application /*", "application", "*", false); + /** Makes sure that 'abstract' types are properly initialised. */ + @ParameterizedTest + @CsvSource({ + "'',*,*,false", + "' ',*,*,false", + "*/,*,*,false", + "*/ ,*,*,false", + " * /,*,*,false", + "/*,*,*,false", + " /*,*,*,false", + "/ * ,*,*,false", + " / * ,*,*,false", + "*/*,*,*,false", + " * /*,*,*,false", + "*/ * ,*,*,false", + " * / * ,*,*,false", + "/xml,*,xml,false", + "/ xml ,*,xml,false", + " /xml,*,xml,false", + " / xml ,*,xml,false", + "*/xml,*,xml,false", + " * /xml,*,xml,false", + "*/ xml ,*,xml,false", + " * / xml ,*,xml,false", + "application,application,*,false", + " application ,application,*,false", + "application/,application,*,false", + " application /,application,*,false", + " application / ,application,*,false", + "application/*,application,*,false", + " application /*,application,*,false", + "application/ * ,application,*,false", + " application /*,application,*,false" + }) + void testNotConcrete( + String name, String expectedMain, String expectedSub, boolean expectedConcrete) { + assertMediaType(name, expectedMain, expectedSub, expectedConcrete); } - /** - * Test references that are unequal. - */ + /** Test references that are unequal. */ @Test - public void testUnEquals() { + void testUnEquals() { MediaType mt1 = new MediaType("application/xml"); MediaType mt2 = new MediaType("application/xml2"); assertNotEquals(mt1, mt2); @@ -312,55 +306,48 @@ public void testUnEquals() { assertNotEquals(mt1Bis, mt3); mt1 = new MediaType("application/1"); - mt2 = MediaType.APPLICATION_ALL; + mt2 = APPLICATION_ALL; assertNotEquals(mt1, mt2); } - /** - * Testing {@link MediaType#valueOf(String)} and - * {@link MediaType#register(String, String)} - */ + /** Testing {@link MediaType#valueOf(String)} and {@link MediaType#register(String, String)} */ @Test - public void testValueOf() { - assertSame(MediaType.APPLICATION_XML, - MediaType.valueOf("application/xml")); - assertSame(MediaType.ALL, MediaType.valueOf("*/*")); - final MediaType newType = MediaType - .valueOf("application/x-restlet-test"); + void testValueOf() { + assertSame(APPLICATION_XML, valueOf("application/xml")); + assertSame(ALL, valueOf("*/*")); + final MediaType newType = valueOf("application/x-restlet-test"); assertEquals("application", newType.getMainType()); assertEquals("x-restlet-test", newType.getSubType()); assertEquals("application/x-restlet-test", newType.getName()); // Should not have got registered by call to valueOf() alone - assertNotSame(newType, MediaType.valueOf("application/x-restlet-test")); + assertNotSame(newType, valueOf("application/x-restlet-test")); - final MediaType registeredType = MediaType.register( - "application/x-restlet-test", "Restlet testcase"); + final MediaType registeredType = register("application/x-restlet-test", "Restlet testcase"); assertNotSame(newType, registeredType); // didn't touch old value assertEquals("application/x-restlet-test", registeredType.getName()); assertEquals("Restlet testcase", registeredType.getDescription()); // Later valueOf calls always returns the registered type - assertSame(registeredType, - MediaType.valueOf("application/x-restlet-test")); - assertSame(registeredType, - MediaType.valueOf("application/x-restlet-test")); + assertSame(registeredType, valueOf("application/x-restlet-test")); + assertSame(registeredType, valueOf("application/x-restlet-test")); // Test toString() equivalence - MediaType mediaType = MediaType - .valueOf("application/atom+xml; name=value"); + MediaType mediaType = valueOf("application/atom+xml; name=value"); assertEquals("application/atom+xml; name=value", mediaType.toString()); - assertEquals(MediaType.APPLICATION_ATOM, mediaType.getParent()); + assertEquals(APPLICATION_ATOM, mediaType.getParent()); } @Test @SuppressWarnings("unchecked") - public void testUnmodifiable() { + void testUnmodifiable() { final Form form = new Form(); form.add("name1", "value1"); - final Series unmodifiableForm = (Series) Series.unmodifiableSeries(form); + final Series unmodifiableForm = + (Series) Series.unmodifiableSeries(form); - assertThrows(UnsupportedOperationException.class, () -> unmodifiableForm.add("name2", "value2")); + assertThrows( + UnsupportedOperationException.class, () -> unmodifiableForm.add("name2", "value2")); } } diff --git a/org.restlet/src/test/java/org/restlet/data/MethodTestCase.java b/org.restlet/src/test/java/org/restlet/data/MethodTestCase.java index 913822cef0..b6cdc42849 100644 --- a/org.restlet/src/test/java/org/restlet/data/MethodTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/MethodTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; import org.junit.jupiter.api.Assertions; @@ -14,24 +13,25 @@ /** * Test {@link org.restlet.data.Method}. - *

- * Note: this test purposefully does *not* extend RestletTestCase. - * The regression previously present in Restlet - * (described in https://github.com/restlet/restlet-framework-java/issues/1130) - * depends on class initialization order and - * vanishes when the Restlet/Engine class is initialized before the class Method. - * + * + *

Note: this test purposefully does *not* extend RestletTestCase. The regression previously + * present in Restlet (described in https://github.com/restlet/restlet-framework-java/issues/1130) + * depends on class initialization order and vanishes when the Restlet/Engine class is initialized + * before the class Method. + * * @author Andreas Wundsam */ -public class MethodTestCase { +class MethodTestCase { /** - * validate that Method caching works, i.e., the value returned by - * Method.valueOf("GET") is the cached constant Method.GET. + * validate that Method caching works, i.e., the value returned by Method.valueOf("GET") is the + * cached constant Method.GET. */ @Test - public void testCaching() { - Assertions.assertEquals(Method.GET, Method.valueOf("GET"), "Method.valueOf('GET') should return cached constant Method.GET "); + void testCaching() { + Assertions.assertEquals( + Method.GET, + Method.valueOf("GET"), + "Method.valueOf('GET') should return cached constant Method.GET "); } - } diff --git a/org.restlet/src/test/java/org/restlet/data/ProductTokenTestCase.java b/org.restlet/src/test/java/org/restlet/data/ProductTokenTestCase.java index f452f89908..ff9c28f76f 100644 --- a/org.restlet/src/test/java/org/restlet/data/ProductTokenTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/ProductTokenTestCase.java @@ -1,14 +1,20 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -16,23 +22,20 @@ import org.restlet.engine.header.ProductReader; import org.restlet.engine.header.ProductWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - /** * Test {@link org.restlet.data.Product}. * * @author Thierry Boileau */ -public class ProductTokenTestCase { +class ProductTokenTestCase { @ParameterizedTest(name = "{1} {2}") @MethodSource("mainProductTestCases") - public void testMainProduct(final String userAgent, final String productName, final String productVersion, final String productComment) { + void testMainProduct( + final String userAgent, + final String productName, + final String productVersion, + final String productComment) { ClientInfo clientInfo = new ClientInfo(); clientInfo.setAgent(userAgent); Product product = clientInfo.getMainAgentProduct(); @@ -44,114 +47,164 @@ public void testMainProduct(final String userAgent, final String productName, fi private static Stream mainProductTestCases() { return Stream.of( - Arguments.of("Mozilla/4.0 (compatible; MSIE 6.0; America Online Browser 1.1; rev1.1; Windows NT 5.1;)", - "MSIE", "6.0", null), - Arguments.of("Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US; rv:1.8) Gecko/20051107 Camino/1.0b1", - "Camino", "1.0b1", null), - Arguments.of("Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:0.9.2) Gecko/20020508 Netscape6/6.1", - "Netscape6", "6.1", null), - Arguments.of("Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Iceweasel/2.0 (Debian-2.0+dfsg-1)", - "Iceweasel", "2.0", "Debian-2.0+dfsg-1"), - Arguments.of("Mozilla/5.0 (compatible; Konqueror/3.5; Linux 2.6.15-1.2054_FC5; X11; i686; en_US) KHTML/3.5.4 (like Gecko)", - "Konqueror", "3.5.4", "like Gecko"), - Arguments.of("Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)", - "MSIE", "5.5", null), - Arguments.of("Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)", - "MSIE", "6.0", null), - Arguments.of("Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/521.25 (KHTML, like Gecko) Safari/521.24", - "Safari", "521.24", null), - Arguments.of("Opera/9.00 (Macintosh; PPC Mac OS X; U; en)", - "Opera", "9.00", null), - Arguments.of("Wget/1.9", - "Wget", "1.9", null), - Arguments.of("Restlet-Framework/2.2-SNAPSHOT", - "Restlet-Framework", "2.2-SNAPSHOT", null) - ); + Arguments.of( + "Mozilla/4.0 (compatible; MSIE 6.0; America Online Browser 1.1; rev1.1; Windows NT 5.1;)", + "MSIE", + "6.0", + null), + Arguments.of( + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US; rv:1.8) Gecko/20051107 Camino/1.0b1", + "Camino", + "1.0b1", + null), + Arguments.of( + "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:0.9.2) Gecko/20020508 Netscape6/6.1", + "Netscape6", + "6.1", + null), + Arguments.of( + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Iceweasel/2.0 (Debian-2.0+dfsg-1)", + "Iceweasel", + "2.0", + "Debian-2.0+dfsg-1"), + Arguments.of( + "Mozilla/5.0 (compatible; Konqueror/3.5; Linux 2.6.15-1.2054_FC5; X11; i686; en_US) KHTML/3.5.4 (like Gecko)", + "Konqueror", + "3.5.4", + "like Gecko"), + Arguments.of( + "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)", "MSIE", "5.5", null), + Arguments.of( + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)", + "MSIE", + "6.0", + null), + Arguments.of( + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/521.25 (KHTML, like Gecko) Safari/521.24", + "Safari", + "521.24", + null), + Arguments.of("Opera/9.00 (Macintosh; PPC Mac OS X; U; en)", "Opera", "9.00", null), + Arguments.of("Wget/1.9", "Wget", "1.9", null), + Arguments.of( + "Restlet-Framework/2.2-SNAPSHOT", + "Restlet-Framework", + "2.2-SNAPSHOT", + null)); } - @Test - public void testProductTokens() { - final String userAgent1 = "Mozilla/4.0 (compatible; MSIE 6.0; America Online Browser 1.1; rev1.1; Windows NT 5.1;)"; - final String userAgent2 = "Advanced Browser (http://www.avantbrowser.com)"; - final String userAgent3 = "Mozilla/5.0"; - final String userAgent4 = "Mozilla"; - final String userAgent5 = "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US; rv:1.8) Gecko/20051107 Camino/1.0b1"; - final String userAgent6 = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Iceweasel/2.0 (Debian-2.0+dfsg-1)"; - final String userAgent7 = "Restlet-Framework/2.2-SNAPSHOT"; - - List list = ProductReader.read(userAgent1); - assertEquals(1, list.size()); - assertEquals("Mozilla", list.get(0).getName()); - assertEquals("4.0", list.get(0).getVersion()); - assertEquals( - "compatible; MSIE 6.0; America Online Browser 1.1; rev1.1; Windows NT 5.1;", - list.get(0).getComment()); - - list = ProductReader.read(userAgent2); - assertEquals(1, list.size()); - assertEquals(list.get(0).getName(), "Advanced Browser"); - assertNull(list.get(0).getVersion()); - assertEquals(list.get(0).getComment(), "http://www.avantbrowser.com"); - - list = ProductReader.read(userAgent3); - assertEquals(1, list.size()); - assertEquals("Mozilla", list.get(0).getName()); - assertEquals("5.0", list.get(0).getVersion()); - assertNull(list.get(0).getComment()); - - list = ProductReader.read(userAgent4); - assertEquals(1, list.size()); - assertEquals("Mozilla", list.get(0).getName()); - assertNull(list.get(0).getVersion()); - assertNull(list.get(0).getComment()); - - list = ProductReader.read(userAgent5); - assertEquals(3, list.size()); - assertEquals("Mozilla", list.get(0).getName()); - assertEquals("5.0", list.get(0).getVersion()); - assertEquals("Macintosh; U; PPC Mac OS X; en-US; rv:1.8", list.get(0) - .getComment()); - assertEquals("Gecko", list.get(1).getName()); - assertEquals("20051107", list.get(1).getVersion()); - assertNull(list.get(1).getComment()); - assertEquals("Camino", list.get(2).getName()); - assertEquals("1.0b1", list.get(2).getVersion()); - assertNull(list.get(2).getComment()); - - list = ProductReader.read(userAgent6); - assertEquals(3, list.size()); - assertEquals("Mozilla", list.get(0).getName()); - assertEquals("5.0", list.get(0).getVersion()); - assertEquals("X11; U; Linux i686; en-US; rv:1.8.1", list.get(0) - .getComment()); - assertEquals("Gecko", list.get(1).getName()); - assertEquals("20061024", list.get(1).getVersion()); - assertNull(list.get(1).getComment()); - assertEquals("Iceweasel", list.get(2).getName()); - assertEquals("2.0", list.get(2).getVersion()); - assertEquals("Debian-2.0+dfsg-1", list.get(2).getComment()); - - list = ProductReader.read(userAgent7); - assertEquals(1, list.size()); - assertEquals("Restlet-Framework", list.get(0).getName()); - assertEquals("2.2-SNAPSHOT", list.get(0).getVersion()); - assertNull(list.get(0).getComment()); + @Nested + class ProductTokens { + + @Test + void testProductTokensAol() { + final String userAgent = + "Mozilla/4.0 (compatible; MSIE 6.0; America Online Browser 1.1; rev1.1; Windows NT 5.1;)"; + + List list = ProductReader.read(userAgent); + assertEquals(1, list.size()); + assertEquals("Mozilla", list.getFirst().getName()); + assertEquals("4.0", list.getFirst().getVersion()); + assertEquals( + "compatible; MSIE 6.0; America Online Browser 1.1; rev1.1; Windows NT 5.1;", + list.getFirst().getComment()); + } + + @Test + void testProductTokensAvantBrowser() { + final String userAgent = "Advanced Browser (http://www.avantbrowser.com)"; + + List list = ProductReader.read(userAgent); + assertEquals(1, list.size()); + assertEquals("Advanced Browser", list.getFirst().getName()); + assertNull(list.getFirst().getVersion()); + assertEquals("http://www.avantbrowser.com", list.getFirst().getComment()); + } + + @Test + void testProductTokensMozilla5() { + final String userAgent = "Mozilla/5.0"; + List list = ProductReader.read(userAgent); + + assertEquals(1, list.size()); + assertEquals("Mozilla", list.getFirst().getName()); + assertEquals("5.0", list.getFirst().getVersion()); + assertNull(list.getFirst().getComment()); + } + + @Test + void testProductTokensMozilla() { + final String userAgent = "Mozilla"; + + List list = ProductReader.read(userAgent); + + assertEquals(1, list.size()); + assertEquals("Mozilla", list.getFirst().getName()); + assertNull(list.getFirst().getVersion()); + assertNull(list.getFirst().getComment()); + } + + @Test + void testProductTokensMozillaMacintosh() { + final String userAgent = + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US; rv:1.8) Gecko/20051107 Camino/1.0b1"; + + List list = ProductReader.read(userAgent); + assertEquals(3, list.size()); + assertEquals("Mozilla", list.getFirst().getName()); + assertEquals("5.0", list.get(0).getVersion()); + assertEquals("Macintosh; U; PPC Mac OS X; en-US; rv:1.8", list.get(0).getComment()); + assertEquals("Gecko", list.get(1).getName()); + assertEquals("20051107", list.get(1).getVersion()); + assertNull(list.get(1).getComment()); + assertEquals("Camino", list.get(2).getName()); + assertEquals("1.0b1", list.get(2).getVersion()); + assertNull(list.get(2).getComment()); + } + + @Test + void testProductTokensMozillaIceWeasel() { + final String userAgent = + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Iceweasel/2.0 (Debian-2.0+dfsg-1)"; + List list = ProductReader.read(userAgent); + + assertEquals(3, list.size()); + assertEquals("Mozilla", list.getFirst().getName()); + assertEquals("5.0", list.get(0).getVersion()); + assertEquals("X11; U; Linux i686; en-US; rv:1.8.1", list.get(0).getComment()); + assertEquals("Gecko", list.get(1).getName()); + assertEquals("20061024", list.get(1).getVersion()); + assertNull(list.get(1).getComment()); + assertEquals("Iceweasel", list.get(2).getName()); + assertEquals("2.0", list.get(2).getVersion()); + assertEquals("Debian-2.0+dfsg-1", list.get(2).getComment()); + } + + @Test + void testProductTokensRestlet() { + final String userAgent = "Restlet-Framework/2.2-SNAPSHOT"; + + List list = ProductReader.read(userAgent); + assertEquals(1, list.size()); + assertEquals("Restlet-Framework", list.getFirst().getName()); + assertEquals("2.2-SNAPSHOT", list.getFirst().getVersion()); + assertNull(list.getFirst().getComment()); + } } @Test - public void testWriteThenRead() { + void testWriteThenRead() { final List products = new ArrayList<>(); products.add(new Product("Product", "1.2", null)); products.add(new Product("Nre", "1.1m4", "This is a comment")); List list = ProductReader.read(ProductWriter.write(products)); assertEquals(2, list.size()); - assertEquals("Product", list.get(0).getName()); + assertEquals("Product", list.getFirst().getName()); assertEquals("1.2", list.get(0).getVersion()); assertNull(list.get(0).getComment()); assertEquals("Nre", list.get(1).getName()); assertEquals("1.1m4", list.get(1).getVersion()); assertEquals("This is a comment", list.get(1).getComment()); } - } diff --git a/org.restlet/src/test/java/org/restlet/data/RangeTestCase.java b/org.restlet/src/test/java/org/restlet/data/RangeTestCase.java index 5f1b8c39d3..3d284fba7c 100644 --- a/org.restlet/src/test/java/org/restlet/data/RangeTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/RangeTestCase.java @@ -1,44 +1,50 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.junit.jupiter.api.*; -import org.restlet.*; -import org.restlet.engine.Engine; -import org.restlet.engine.io.IoUtils; -import org.restlet.engine.local.FileClientHelper; -import org.restlet.representation.StringRepresentation; -import org.restlet.resource.Directory; -import org.restlet.routing.Router; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.engine.Engine; +import org.restlet.engine.io.IoUtils; +import org.restlet.engine.local.FileClientHelper; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.Directory; +import org.restlet.routing.Router; /** * Test {@link org.restlet.data.Range}. - * + * * @author Jerome Louvel */ -public class RangeTestCase { +class RangeTestCase { private static final Tag ENTITY_TAG = new Tag("TestRangeGetRestlet"); - /** - * Internal class used for test purpose. - * - */ + /** Internal class used for test purpose. */ private static class TestRangeApplication extends Application { public TestRangeApplication() { @@ -53,18 +59,16 @@ public Restlet createInboundRoot() { Router router = new Router(); router.attach("/testGet", new TestRangeGetRestlet()); - Directory directory = new Directory(getContext(), LocalReference.createFileReference(testDirPath.toFile())); + Directory directory = + new Directory( + getContext(), LocalReference.createFileReference(testDirPath.toFile())); directory.setModifiable(true); router.attach("/testPut/", directory); return router; } } - /** - * Internal class used for test purpose. It simply returns a string 10 - * characters long. - * - */ + /** Internal class used for test purpose. It simply returns a string 10 characters long. */ private static class TestRangeGetRestlet extends Restlet { @Override public void handle(Request request, Response response) { @@ -107,8 +111,9 @@ void noRange() throws IOException { assertEquals(10, response.getEntity().getAvailableSize()); assertNull(response.getEntity().getRange()); } + @Test - public void fullRange() throws IOException { + void fullRange() throws IOException { request.setRanges(List.of(new Range(0, 10))); Response response = testRangeApplication.handle(request); assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus()); @@ -118,8 +123,9 @@ public void fullRange() throws IOException { assertEquals(0, response.getEntity().getRange().getIndex()); assertEquals(10, response.getEntity().getRange().getSize()); } + @Test - public void rangeFirst2Bytes() throws Exception { + void rangeFirst2Bytes() throws Exception { request.setRanges(List.of(new Range(Range.INDEX_FIRST, 2))); Response response = testRangeApplication.handle(request); assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus()); @@ -131,7 +137,7 @@ public void rangeFirst2Bytes() throws Exception { } @Test - public void range2To4Bytes() throws Exception { + void range2To4Bytes() throws Exception { request.setRanges(List.of(new Range(2, 2))); Response response = testRangeApplication.handle(request); assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus()); @@ -143,7 +149,7 @@ public void range2To4Bytes() throws Exception { } @Test - public void range2To9Bytes() throws Exception { + void range2To9Bytes() throws Exception { request.setRanges(List.of(new Range(2, 7))); Response response = testRangeApplication.handle(request); assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus()); @@ -153,8 +159,9 @@ public void range2To9Bytes() throws Exception { assertEquals(2, response.getEntity().getRange().getIndex()); assertEquals(7, response.getEntity().getRange().getSize()); } + @Test - public void rangeLast7Bytes() throws Exception { + void rangeLast7Bytes() throws Exception { request.setRanges(List.of(new Range(Range.INDEX_LAST, 7))); Response response = testRangeApplication.handle(request); assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus()); @@ -166,7 +173,7 @@ public void rangeLast7Bytes() throws Exception { } @Test - public void range2toMaxBytes() throws Exception { + void range2toMaxBytes() throws Exception { request.setRanges(List.of(new Range(2, Range.SIZE_MAX))); Response response = testRangeApplication.handle(request); assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus()); @@ -177,7 +184,7 @@ public void range2toMaxBytes() throws Exception { } @Test - public void range2To1000Bytes() throws Exception { + void range2To1000Bytes() throws Exception { request.setRanges(List.of(new Range(2, 1000))); Response response = testRangeApplication.handle(request); assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus()); @@ -199,7 +206,7 @@ void setUp() { } @Test - public void rangeShouldApply() throws Exception { + void rangeShouldApply() throws Exception { request.getConditions().setRangeTag(ENTITY_TAG); Response response = testRangeApplication.handle(request); @@ -212,7 +219,7 @@ public void rangeShouldApply() throws Exception { } @Test - public void rangeShouldNotApply() throws Exception { + void rangeShouldNotApply() throws Exception { Tag entityTag = new Tag(UUID.randomUUID().toString()); request.getConditions().setRangeTag(entityTag); Response response = testRangeApplication.handle(request); @@ -226,7 +233,7 @@ public void rangeShouldNotApply() throws Exception { class TestPut { @Test - public void testPut() throws IOException { + void testPut() throws IOException { Path testFilePath = testDirPath.resolve("essai.txt"); testFilePath.toFile().delete(); @@ -283,7 +290,6 @@ public void testPut() throws IOException { assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus()); assertEquals("20000998", response.getEntity().getText()); - // Partial PUT on a file, with a non-bytes range, not taken into account request = new Request(Method.PUT, "/testPut/essai.txt"); request.setEntity(new StringRepresentation("1234567890")); @@ -321,14 +327,12 @@ void putNewFileWithRangeWithoutSize() throws IOException { } } - @Test - public void testMultipleRanges() { + void testMultipleRanges() { Request request = new Request(Method.GET, "/testGet"); request.setRanges(List.of(new Range(1), new Range(2))); Response response = testRangeApplication.handle(request); assertEquals(Status.SERVER_ERROR_NOT_IMPLEMENTED, response.getStatus()); } - } diff --git a/org.restlet/src/test/java/org/restlet/data/RecipientInfoTestCase.java b/org.restlet/src/test/java/org/restlet/data/RecipientInfoTestCase.java index 11e8acf2e5..02bd56d9f2 100644 --- a/org.restlet/src/test/java/org/restlet/data/RecipientInfoTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/RecipientInfoTestCase.java @@ -1,42 +1,46 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.Test; import org.restlet.engine.header.HeaderConstants; import org.restlet.engine.header.RecipientInfoReader; import org.restlet.engine.header.RecipientInfoWriter; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - /** * Test {@link org.restlet.data.RecipientInfo}. - * + * * @author Jerome Louvel */ -public class RecipientInfoTestCase { +class RecipientInfoTestCase { @Test - public void testVia() { - Header via1a = new Header(HeaderConstants.HEADER_VIA, - "1.0 fred, 1.1 nowhere.com (Apache/1.1)"); - Header via1b = new Header(HeaderConstants.HEADER_VIA, - "HTTP/1.0 fred, HTTP/1.1 nowhere.com (Apache/1.1)"); - Header via1c = new Header(HeaderConstants.HEADER_VIA, - "HTTP/1.0 fred (Apache/1.1), HTTP/1.1 nowhere.com"); - Header via1d = new Header(HeaderConstants.HEADER_VIA, - "HTTP/1.0 fred (Apache/1.1), HTTP/1.1 nowhere.com:8111"); + void testVia() { + Header via1a = + new Header(HeaderConstants.HEADER_VIA, "1.0 fred, 1.1 nowhere.com (Apache/1.1)"); + Header via1b = + new Header( + HeaderConstants.HEADER_VIA, + "HTTP/1.0 fred, HTTP/1.1 nowhere.com (Apache/1.1)"); + Header via1c = + new Header( + HeaderConstants.HEADER_VIA, + "HTTP/1.0 fred (Apache/1.1), HTTP/1.1 nowhere.com"); + Header via1d = + new Header( + HeaderConstants.HEADER_VIA, + "HTTP/1.0 fred (Apache/1.1), HTTP/1.1 nowhere.com:8111"); List recipients = new ArrayList<>(); RecipientInfoReader.addValues(via1a, recipients); diff --git a/org.restlet/src/test/java/org/restlet/data/ReferenceTestCase.java b/org.restlet/src/test/java/org/restlet/data/ReferenceTestCase.java index d2020cd094..133a383131 100644 --- a/org.restlet/src/test/java/org/restlet/data/ReferenceTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/ReferenceTestCase.java @@ -1,39 +1,39 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.restlet.engine.header.HeaderConstants; import org.restlet.engine.util.ReferenceUtils; import org.restlet.util.Series; -import java.util.ArrayList; -import java.util.List; +class ReferenceTestCase { -import static org.junit.jupiter.api.Assertions.*; + protected static final String DEFAULT_SCHEME = "http"; -/** - * Test {@link org.restlet.data.Reference}. - * - * @author Jerome Louvel - * @author Lars Heuer (heuer[at]semagia.com) Semagia - */ -public class ReferenceTestCase { - protected final static String DEFAULT_SCHEME = "http"; - - protected final static String DEFAULT_SCHEMEPART = "//"; + protected static final String DEFAULT_SCHEME_PART = "//"; /** - * Returns a reference that is initialized with http://restlet.org. - * + * Returns a reference initialized with http://restlet.org. + * * @return Reference instance. */ protected Reference getDefaultReference() { @@ -44,21 +44,19 @@ protected Reference getDefaultReference() { /** * Returns a reference with uri == http:// - * + * * @return Reference instance. */ protected Reference getReference() { final Reference ref = new Reference(); ref.setScheme(DEFAULT_SCHEME); - ref.setSchemeSpecificPart(DEFAULT_SCHEMEPART); + ref.setSchemeSpecificPart(DEFAULT_SCHEME_PART); return ref; } - /** - * Test addition methods. - */ + /** Test addition methods. */ @Test - public void testAdditions() throws Exception { + void testAdditions() { final Reference ref = new Reference("http://restlet.org"); ref.addQueryParameter("abc", "123"); assertEquals("http://restlet.org?abc=123", ref.toString()); @@ -71,7 +69,7 @@ public void testAdditions() throws Exception { } @Test - public void testEmptyRef() { + void testEmptyRef() { Reference reference = new Reference(); reference.setAuthority("testAuthority"); // must not produce NPE @@ -84,7 +82,7 @@ public void testEmptyRef() { reference = new Reference(); reference.setHostDomain("localhost"); // must not produce NPE assertEquals("localhost", reference.getAuthority()); - reference.setHostPort(Integer.valueOf(4711)); // must not produce NPE + reference.setHostPort(4711); // must not produce NPE assertEquals("localhost:4711", reference.getAuthority()); reference.setUserInfo("sdgj:skdfj"); // must not produce NPE assertEquals("sdgj:skdfj@localhost:4711", reference.getAuthority()); @@ -118,43 +116,54 @@ public void testEmptyRef() { reference.setSegments(segments); // must not produce NPE } - /** - * Equality tests. - */ + @ParameterizedTest + @CsvSource({ + "http://localhost/abc,/def", + "http://localhost/abc/,/def", + "http://localhost/abc?query,/def", + "http://localhost/abc#fragment,/def", + "http://localhost/abc?query#fragment,/def", + "http://localhost/abc#fragment?query,/def", + "http://localhost#fragment/abc?query,/def" + }) + void testSetPath(String reference, String path) { + final Reference ref = new Reference(reference); + ref.setPath(path); + assertEquals(path, ref.getPath()); + } + + /** Equality tests. */ @Test - public void testEquals() { + void testEquals() { final Reference ref1 = getDefaultReference(); final Reference ref2 = getDefaultReference(); assertEquals(ref1, ref2); - assertEquals(ref1, ref2); } - @Test - public void testGetLastSegment() { + @Test + void testGetLastSegment() { Reference reference = new Reference("http://hostname"); assertNull(reference.getLastSegment()); - + reference = new Reference("http://hostname/"); assertNull(reference.getLastSegment()); - + reference = new Reference("http://hostname/abc"); assertEquals("abc", reference.getLastSegment()); - + reference = new Reference("http://hostname/abc/"); assertEquals("abc", reference.getLastSegment()); - + reference = new Reference("http://hostname/123/abc/"); assertEquals("abc", reference.getLastSegment()); - + reference = new Reference("http://hostname/123/abc"); assertEquals("abc", reference.getLastSegment()); } - /** - * Test hostname getting/setting. - */ + /** Test hostname getting/setting. */ @Test - public void testHostName() { + void testHostName() { final Reference ref = getReference(); String host = "restlet.org"; ref.setHostDomain(host); @@ -167,13 +176,11 @@ public void testHostName() { } @Test - public void testMatrix() { - final Reference ref1 = new Reference( - "http://domain.tld/whatever/a=1;b=2;c=4?x=a&y=b"); - final Reference ref2 = new Reference( - "http://domain.tld/whatever/a=1/foo;b=2;c=4;d?x=a&y=b"); - final Reference ref3 = new Reference( - "http://domain.tld/whatever/a=1;b=2;c=4/foo?x=a&y=b"); + void testMatrix() { + final Reference ref1 = new Reference("http://domain.tld/whatever/a=1;b=2;c=4?x=a&y=b"); + final Reference ref2 = + new Reference("http://domain.tld/whatever/a=1/foo;b=2;c=4;d?x=a&y=b"); + final Reference ref3 = new Reference("http://domain.tld/whatever/a=1;b=2;c=4/foo?x=a&y=b"); assertTrue(ref1.hasMatrix()); assertTrue(ref2.hasMatrix()); @@ -199,23 +206,20 @@ public void testMatrix() { } @Test - public void testOriginalRef() { + void testOriginalRef() { Reference ref = new Reference("http://localhost/test"); Series

headers = new Series<>(Header.class); headers.add(HeaderConstants.HEADER_X_FORWARDED_PROTO, "HTTPS"); headers.add(HeaderConstants.HEADER_X_FORWARDED_PORT, "123"); Reference originalRef = ReferenceUtils.getOriginalRef(ref, headers); - assertEquals(originalRef.getSchemeProtocol(), Protocol.HTTPS); - assertEquals(originalRef.getHostPort(), 123); + assertEquals(Protocol.HTTPS, originalRef.getSchemeProtocol()); + assertEquals(123, originalRef.getHostPort()); } - /** - * Test the computation of parent references, for absolute and relative - * URIs. - */ + /** Test the computation of parent references, for absolute and relative URIs. */ @Test - public void testParentRef() { + void testParentRef() { Reference baseRef = new Reference("http://test.com/foo/bar"); Reference parentRef = baseRef.getParentRef(); assertEquals("http://test.com/foo/", parentRef.toString()); @@ -225,267 +229,27 @@ public void testParentRef() { assertEquals("/foo/", parentRef.toString()); } - /** - * Tests the URI parsing. - */ - @Test - public void testParsing() { - final String base = "http://a/b/c/d;p?q"; - - final String uri01 = "g:h"; - final String uri02 = "g"; - final String uri03 = "./g"; - final String uri04 = "g/"; - final String uri05 = "/g"; - final String uri06 = "//g"; - final String uri07 = "?y"; - final String uri08 = "g?y"; - final String uri09 = "#s"; - final String uri10 = "g#s"; - final String uri11 = "g?y#s"; - final String uri12 = ";x"; - final String uri13 = "g;x"; - final String uri14 = "g;x?y#s"; - final String uri15 = ""; - final String uri16 = "."; - final String uri17 = "./"; - final String uri18 = ".."; - final String uri19 = "../"; - final String uri20 = "../g"; - final String uri21 = "../.."; - final String uri22 = "../../"; - final String uri23 = "../../g"; - final String uri24 = "../../../g"; - final String uri25 = "../../../../g"; - final String uri26 = "/./g"; - final String uri27 = "/../g"; - final String uri28 = "g."; - final String uri29 = ".g"; - final String uri30 = "g.."; - final String uri31 = "..g"; - final String uri32 = "./../g"; - final String uri33 = "./g/."; - final String uri34 = "g/./h"; - final String uri35 = "g/../h"; - final String uri36 = "g;x=1/./y"; - final String uri37 = "g;x=1/../y"; - - final String uri101 = "g:h"; - final String uri102 = "http://a/b/c/g"; - final String uri103 = "http://a/b/c/g"; - final String uri104 = "http://a/b/c/g/"; - final String uri105 = "http://a/g"; - final String uri106 = "http://g"; - final String uri107 = "http://a/b/c/d;p?y"; - final String uri108 = "http://a/b/c/g?y"; - final String uri109 = "http://a/b/c/d;p?q#s"; - final String uri110 = "http://a/b/c/g#s"; - final String uri111 = "http://a/b/c/g?y#s"; - final String uri112 = "http://a/b/c/;x"; - final String uri113 = "http://a/b/c/g;x"; - final String uri114 = "http://a/b/c/g;x?y#s"; - final String uri115 = "http://a/b/c/d;p?q"; - final String uri116 = "http://a/b/c/"; - final String uri117 = "http://a/b/c/"; - final String uri118 = "http://a/b/"; - final String uri119 = "http://a/b/"; - final String uri120 = "http://a/b/g"; - final String uri121 = "http://a/"; - final String uri122 = "http://a/"; - final String uri123 = "http://a/g"; - final String uri124 = "http://a/g"; - final String uri125 = "http://a/g"; - final String uri126 = "http://a/g"; - final String uri127 = "http://a/g"; - final String uri128 = "http://a/b/c/g."; - final String uri129 = "http://a/b/c/.g"; - final String uri130 = "http://a/b/c/g.."; - final String uri131 = "http://a/b/c/..g"; - final String uri132 = "http://a/b/g"; - final String uri133 = "http://a/b/c/g/"; - final String uri134 = "http://a/b/c/g/h"; - final String uri135 = "http://a/b/c/h"; - final String uri136 = "http://a/b/c/g;x=1/y"; - final String uri137 = "http://a/b/c/y"; - - final Reference host = new Reference("http://host.com"); - final Reference slashdir = new Reference(host, "/dir"); - final Reference dir = new Reference(host, "dir"); - final Reference dirslash = new Reference(host, "dir/"); - final Reference fulldir = new Reference("http://host.com/dir"); - final Reference fulldirsub = new Reference(fulldir, "sub"); - final Reference fulldirslashsub = new Reference(fulldir, "/sub"); - final Reference slashdirsub = new Reference(slashdir, "sub"); - final Reference slashdirslashsub = new Reference(slashdir, "/sub"); - final Reference dirslashsub = new Reference(dirslash, "sub"); - final Reference fullsub = new Reference("http://host.com/dir/sub"); - - // Test the parsing of references into its components - testRef0("foo://example.com:8042/over/there?name=ferret#nose", "foo", - "example.com:8042", "/over/there", "name=ferret", "nose"); - testRef0("urn:example:animal:ferret:nose", "urn", null, - "example:animal:ferret:nose", null, null); - testRef0("mailto:fred@example.com", "mailto", null, "fred@example.com", - null, null); - testRef0("foo://info.example.com?fred", "foo", "info.example.com", - null, "fred", null); - testRef0("*", null, null, "*", null, null); - testRef0("http://localhost?query", "http", "localhost", null, "query", - null); - testRef0("http://localhost#?query", "http", "localhost", null, null, - "?query"); - testRef0("http://localhost/?query", "http", "localhost", "/", "query", - null); - testRef0("http://localhost/#?query", "http", "localhost", "/", null, - "?query"); - testRef0("http://localhost/path#frag/ment", "http", "localhost", - "/path", null, "frag/ment"); - testRef0("http://localhost/path?qu/ery", "http", "localhost", "/path", - "qu/ery", null); - - // Test the resolution of relative references - testRef1(base, uri01, uri101); - testRef1(base, uri02, uri102); - testRef1(base, uri03, uri103); - testRef1(base, uri04, uri104); - testRef1(base, uri05, uri105); - testRef1(base, uri06, uri106); - testRef1(base, uri07, uri107); - testRef1(base, uri08, uri108); - testRef1(base, uri09, uri109); - testRef1(base, uri10, uri110); - testRef1(base, uri11, uri111); - testRef1(base, uri12, uri112); - testRef1(base, uri13, uri113); - testRef1(base, uri14, uri114); - testRef1(base, uri15, uri115); - testRef1(base, uri16, uri116); - testRef1(base, uri17, uri117); - testRef1(base, uri18, uri118); - testRef1(base, uri19, uri119); - testRef1(base, uri20, uri120); - testRef1(base, uri21, uri121); - testRef1(base, uri22, uri122); - testRef1(base, uri23, uri123); - testRef1(base, uri24, uri124); - testRef1(base, uri25, uri125); - testRef1(base, uri26, uri126); - testRef1(base, uri27, uri127); - testRef1(base, uri28, uri128); - testRef1(base, uri29, uri129); - testRef1(base, uri30, uri130); - testRef1(base, uri31, uri131); - testRef1(base, uri32, uri132); - testRef1(base, uri33, uri133); - testRef1(base, uri34, uri134); - testRef1(base, uri35, uri135); - testRef1(base, uri36, uri136); - testRef1(base, uri37, uri137); - - // Test the relativization of absolute references - testRef2(base, uri102, uri02); - testRef2(base, uri104, uri04); - testRef2(base, uri107, uri07); - testRef2(base, uri108, uri08); - testRef2(base, uri109, uri09); - testRef2(base, uri110, uri10); - testRef2(base, uri111, uri11); - testRef2(base, uri112, uri12); - testRef2(base, uri113, uri13); - testRef2(base, uri114, uri14); - testRef2(base, uri116, uri16); - testRef2(base, uri118, uri18); - testRef2(base, uri120, uri20); - testRef2(base, uri121, uri21); - testRef2(base, uri123, uri23); - testRef2(uri104, uri116, uri18); - testRef2(uri104, uri118, uri21); - - // Test the toString method with or without query/fragment - testRef3("http://localhost/path#fragment", true, true, - "http://localhost/path#fragment"); - testRef3("http://localhost/path#fragment", true, false, - "http://localhost/path"); - testRef3("http://localhost/path#fragment", false, true, - "http://localhost/path#fragment"); - testRef3("http://localhost/path#fragment", false, false, - "http://localhost/path"); - - testRef3("http://localhost/path?query", true, true, - "http://localhost/path?query"); - testRef3("http://localhost/path?query", true, false, - "http://localhost/path?query"); - testRef3("http://localhost/path?query", false, true, - "http://localhost/path"); - testRef3("http://localhost/path?query", false, false, - "http://localhost/path"); - - testRef3("http://localhost/path?query#fragment", true, true, - "http://localhost/path?query#fragment"); - testRef3("http://localhost/path?query#fragment", true, false, - "http://localhost/path?query"); - testRef3("http://localhost/path?query#fragment", false, true, - "http://localhost/path#fragment"); - testRef3("http://localhost/path?query#fragment", false, false, - "http://localhost/path"); - - testRef3("http://localhost/path#fragment?query", true, true, - "http://localhost/path#fragment?query"); - testRef3("http://localhost/path#fragment?query", true, false, - "http://localhost/path"); - testRef3("http://localhost/path#fragment?query", false, true, - "http://localhost/path#fragment?query"); - testRef3("http://localhost/path#fragment?query", false, false, - "http://localhost/path"); - - testRef4(host, "http", "host.com", null, "http://host.com", - "http://host.com", "http://host.com", null, null); - testRef4(slashdir, null, null, "/dir", null, "/dir", - "http://host.com/dir", null, "/dir"); - testRef4(dir, null, null, "dir", null, "dir", "http://host.com/dir", - null, "dir"); - testRef4(dirslash, null, null, "dir/", null, "dir/", - "http://host.com/dir/", null, "dir/"); - testRef4(fulldir, "http", "host.com", "/dir", "http://host.com/dir", - "http://host.com/dir", "http://host.com/dir", null, null); - - testRef4(fulldirsub, null, null, "sub", null, "sub", - "http://host.com/sub", null, "sub"); - testRef4(fulldirslashsub, null, null, "/sub", null, "/sub", - "http://host.com/sub", null, "/sub"); - testRef4(slashdirsub, null, null, "sub", null, "sub", - "http://host.com/sub", null, "sub"); - testRef4(slashdirslashsub, null, null, "/sub", null, "/sub", - "http://host.com/sub", null, "/sub"); - testRef4(dirslashsub, null, null, "sub", null, "sub", - "http://host.com/dir/sub", null, "sub"); - testRef4(fullsub, "http", "host.com", "/dir/sub", - "http://host.com/dir/sub", "http://host.com/dir/sub", - "http://host.com/dir/sub", null, null); - } - - /** - * Test port getting/setting. - */ - @Test - public void testPort() { + /** Test port getting/setting. */ + @ParameterizedTest + @ValueSource(ints = {8080, 9090}) + void testPort(int port) { Reference ref = getDefaultReference(); - int port = 8080; ref.setHostPort(port); assertEquals(port, ref.getHostPort()); - port = 9090; - ref.setHostPort(port); - assertEquals(port, ref.getHostPort()); - ref = new Reference("http://[::1]:8182"); + } + + @Test + void testPortIPv6() { + Reference ref = new Reference("http://[::1]:8182"); assertEquals(8182, ref.getHostPort()); } @Test - public void testProtocolConstructors() { - assertEquals("http://restlet.org", new Reference(Protocol.HTTP, - "restlet.org").toString()); - assertEquals("https://restlet.org:8443", new Reference(Protocol.HTTPS, - "restlet.org", 8443).toString()); + void testProtocolConstructors() { + assertEquals("http://restlet.org", new Reference(Protocol.HTTP, "restlet.org").toString()); + assertEquals( + "https://restlet.org:8443", + new Reference(Protocol.HTTPS, "restlet.org", 8443).toString()); final Reference ref = new Reference(Protocol.HTTP, "restlet.org"); ref.addQueryParameter("abc", "123"); @@ -493,128 +257,43 @@ public void testProtocolConstructors() { } @Test - public void testQuery() { + void testQuery() { - Reference ref1 = new Reference( - "http://localhost/search?q=anythingelse%"); + Reference ref1 = new Reference("http://localhost/search?q=anythingelse%"); String query = ref1.getQuery(); assertEquals("q=anythingelse%25", query); Form queryForm = ref1.getQueryAsForm(); assertEquals("anythingelse%", queryForm.getFirstValue("q")); - Form extJsQuery = new Form( - "&_dc=1244741620627&callback=stcCallback1001"); + Form extJsQuery = new Form("&_dc=1244741620627&callback=stcCallback1001"); assertEquals("1244741620627", extJsQuery.getFirstValue("_dc")); assertEquals("stcCallback1001", extJsQuery.getFirstValue("callback")); Reference ref = new Reference("http://localhost/v1/projects/13404"); ref.addQueryParameter("dyn", "true"); - assertEquals("http://localhost/v1/projects/13404?dyn=true", - ref.toString()); + assertEquals("http://localhost/v1/projects/13404?dyn=true", ref.toString()); } @Test - public void testQueryWithUri() { - Reference ref = new Reference(new Reference("http://localhost:8111/"), - "http://localhost:8111/contrats/123?srvgwt=localhost:9997"); - assertEquals("contrats/123?srvgwt=localhost:9997", ref.getRelativeRef() - .toString()); - } - - /** - * Tests the parsing of a reference into its components - * - * @param reference - * @param scheme - * @param authority - * @param path - * @param query - * @param fragment - */ - private void testRef0(String reference, String scheme, String authority, - String path, String query, String fragment) { - final Reference ref = new Reference(reference); - assertEquals(scheme, ref.getScheme()); - assertEquals(authority, ref.getAuthority()); - assertEquals(path, ref.getPath()); - assertEquals(query, ref.getQuery()); - assertEquals(fragment, ref.getFragment()); - } - - /** - * Test the resolution of relative references. - * - * @param baseUri - * @param relativeUri - * @param expectedAbsoluteUri - */ - private void testRef1(String baseUri, String relativeUri, - String expectedAbsoluteUri) { - final Reference baseRef = new Reference(baseUri); - final Reference relativeRef = new Reference(baseRef, relativeUri); - final Reference absoluteRef = relativeRef.getTargetRef(); - assertEquals(expectedAbsoluteUri, absoluteRef.toString()); - } - - /** - * Test the relativization of absolute references - * - * @param baseUri - * @param absoluteUri - * @param expectedRelativeUri - */ - private void testRef2(String baseUri, String absoluteUri, - String expectedRelativeUri) { - final Reference baseRef = new Reference(baseUri); - final Reference absoluteRef = new Reference(absoluteUri); - final Reference relativeRef = absoluteRef.getRelativeRef(baseRef); - assertEquals(expectedRelativeUri, relativeRef.toString()); - } - - /** - * Test the toString method with or without query/fragment - * - * @param reference - * @param query - * @param fragment - * @param toString - */ - private void testRef3(String reference, boolean query, boolean fragment, - String toString) { - final Reference ref = new Reference(reference); - assertEquals(ref.toString(query, fragment), toString); - } - - /** - * Test the behaviour of several getters upon a Reference object. - */ - private void testRef4(Reference reference, String scheme, String authority, - String path, String remainingPart, String toString, - String targetRef, String query, String relativePart) { - assertEquals(reference.getScheme(), scheme); - assertEquals(reference.getAuthority(), authority); - assertEquals(reference.getPath(), path); - assertEquals(reference.getRemainingPart(), remainingPart); - assertEquals(reference.toString(), toString); - assertEquals(reference.getTargetRef().toString(), targetRef); - assertEquals(reference.getQuery(), query); - assertEquals(reference.getRelativePart(), relativePart); + void testQueryWithUri() { + Reference ref = + new Reference( + new Reference("http://localhost:8111/"), + "http://localhost:8111/contrats/123?srvgwt=localhost:9997"); + assertEquals("contrats/123?srvgwt=localhost:9997", ref.getRelativeRef().toString()); } @Test - public void testRiap() { + void testRiap() { Reference baseRef = new Reference("riap://component/exist/db/"); Reference ref = new Reference(baseRef, "something.xq"); - assertEquals("riap://component/exist/db/something.xq", ref - .getTargetRef().toString()); + assertEquals("riap://component/exist/db/something.xq", ref.getTargetRef().toString()); } - /** - * Test scheme getting/setting. - */ + /** Test scheme getting/setting. */ @Test - public void testScheme() { + void testScheme() { final Reference ref = getDefaultReference(); assertEquals(DEFAULT_SCHEME, ref.getScheme()); final String scheme = "https"; @@ -624,11 +303,9 @@ public void testScheme() { assertEquals(DEFAULT_SCHEME, ref.getScheme()); } - /** - * Test scheme specific part getting/setting. - */ + /** Test scheme specific part getting/setting. */ @Test - public void testSchemeSpecificPart() { + void testSchemeSpecificPart() { final Reference ref = getDefaultReference(); String part = "//restlet.org"; assertEquals(part, ref.getSchemeSpecificPart()); @@ -637,11 +314,9 @@ public void testSchemeSpecificPart() { assertEquals(part, ref.getSchemeSpecificPart()); } - /** - * Test setting of the last segment. - */ + /** Test setting of the last segment. */ @Test - public void testSetLastSegment() { + void testSetLastSegment() { Reference ref = new Reference("http://localhost:1234"); ref.addSegment("test"); assertEquals("http://localhost:1234/test", ref.toString()); @@ -660,36 +335,67 @@ public void testSetLastSegment() { assertEquals("http://localhost:1234/test/last", ref.toString()); } - @Test - public void testTargetRef() { - Reference ref = new Reference( - "http://twitter.com?status=RT @gamasutra: Devil May Cry : Born Again http://www.gamasutra.com/view/feature/177267/"); - Reference targetRef = new Reference( - new Reference( - "http://www.gamasutra.com/view/feature/177267/devil_may_cry_born_again.php"), - ref).getTargetRef(); - assertEquals( - "http://twitter.com?status=RT%20@gamasutra:%20%20Devil%20May%20Cry%20:%20Born%20Again%20http:?status=RT%20@gamasutra:%20%20Devil%20May%20Cry%20:%20Born%20Again%20http://www.gamasutra.com/view/feature/177267/", - targetRef.toString()); + @ParameterizedTest + @CsvSource({ + "http://localhost:81,//localhost:81", + "http://localhost:81?query,//localhost:81?query", + "http://localhost:81?query#fragment,//localhost:81?query", + "http://localhost:81#fragment,//localhost:81", + "http://localhost:81/#fragment,//localhost:81/", + "http://localhost:81/?query,//localhost:81/?query", + "http://localhost:81/?query=https://perdu.com,//localhost:81/?query=https://perdu.com", + }) + void testSchemeSpecificPart(final String uri, final String expected) { + Reference ref = new Reference(uri); + assertEquals(expected, ref.getSchemeSpecificPart()); } - /** - * Test references that are unequal. - */ + @ParameterizedTest + @CsvSource({ + "http://localhost:81,localhost:81", + "http://localhost:81?query,localhost:81", + "http://localhost:81?query#fragment,localhost:81", + "http://localhost:81#fragment,localhost:81", + "http://localhost:81/#fragment,localhost:81", + "http://localhost:81/?query,localhost:81", + "http://localhost:81/?query=https://perdu.com,localhost:81" + }) + void testAuthority(final String uri, final String expected) { + Reference ref = new Reference(uri); + assertEquals(expected, ref.getAuthority()); + } + + @ParameterizedTest + @CsvSource({ + "http://localhost:81,", + "http://localhost:81/a,/a", + "http://localhost:81?query,", + "http://localhost:81/a?query,/a", + "http://localhost:81?query#fragment,", + "http://localhost:81#fragment/1234,", + "http://localhost:81/a#fragment/1234,/a", + "http://localhost:81/?query,/", + "http://localhost:81/?query=https://perdu.com/1234,/" + }) + void testPath(final String uri, final String expected) { + Reference ref = new Reference(uri); + assertEquals(expected, ref.getPath()); + } + + /** Test references that are unequal. */ @Test - public void testUnEquals() throws Exception { + void testUnEquals() { final String uri1 = "http://restlet.org/"; final String uri2 = "http://restlet.net/"; final Reference ref1 = new Reference(uri1); final Reference ref2 = new Reference(uri2); assertNotEquals(ref1, ref2); - assertNotEquals(null, ref1); } @Test - public void testUserinfo() { + void testUserinfo() { final Reference reference = new Reference("http://localhost:81"); - // This format is depre. however, we may prevent failures. + // This format is deprecated; however, we may prevent failures. reference.setUserInfo("login:password"); assertEquals("login:password@localhost:81", reference.getAuthority()); assertEquals("localhost", reference.getHostDomain()); @@ -703,15 +409,13 @@ public void testUserinfo() { assertEquals("login:password", reference.getUserInfo()); reference.setHostDomain("www.example.com"); - assertEquals("login:password@www.example.com:81", - reference.getAuthority()); + assertEquals("login:password@www.example.com:81", reference.getAuthority()); assertEquals("www.example.com", reference.getHostDomain()); assertEquals(81, reference.getHostPort()); assertEquals("login:password", reference.getUserInfo()); reference.setHostPort(82); - assertEquals("login:password@www.example.com:82", - reference.getAuthority()); + assertEquals("login:password@www.example.com:82", reference.getAuthority()); assertEquals("www.example.com", reference.getHostDomain()); assertEquals(82, reference.getHostPort()); assertEquals("login:password", reference.getUserInfo()); @@ -724,7 +428,7 @@ public void testUserinfo() { } @Test - public void testValidity() { + void testValidity() { String uri = "http ://domain.tld/whatever/"; Reference ref = new Reference(uri); assertEquals("http%20://domain.tld/whatever/", ref.toString()); @@ -733,4 +437,277 @@ public void testValidity() { ref = new Reference(uri); assertEquals("file:///C%7C/wherever%5Cwhatever.swf", ref.toString()); } + + @Nested + class TestParsing { + + @ParameterizedTest + @CsvSource({ + "urn:example:animal:ferret:nose,urn,,example:animal:ferret:nose,,", + "foo://example.com:8042/over/there?name=ferret#nose,foo,example.com:8042,/over/there,name=ferret,nose", + "mailto:fred@example.com,mailto,,fred@example.com,,", + "foo://info.example.com?fred,foo,info.example.com,,fred,", + "http://localhost?query,http,localhost,,query,", + "http://localhost#?query,http,localhost,,,?query", + "http://localhost/?query,http,localhost,/,query,", + "http://localhost/#?query,http,localhost,/,,?query", + "http://localhost/path#frag/ment,http,localhost,/path,,frag/ment", + "http://localhost/path?qu/ery,http,localhost,/path,qu/ery," + }) + void testComponentsParsing( + String reference, + String scheme, + String authority, + String path, + String query, + String fragment) { + final Reference ref = new Reference(reference); + assertEquals(scheme, ref.getScheme()); + assertEquals(authority, ref.getAuthority()); + assertEquals(path, ref.getPath()); + assertEquals(query, ref.getQuery()); + assertEquals(fragment, ref.getFragment()); + } + + @ParameterizedTest + @CsvSource({ + "http://localhost/path#fragment,true,true,http://localhost/path#fragment", + "http://localhost/path#fragment,true,false,http://localhost/path", + "http://localhost/path#fragment,false,true,http://localhost/path#fragment", + "http://localhost/path#fragment,false,false,http://localhost/path", + "http://localhost/path?query,true,true,http://localhost/path?query", + "http://localhost/path?query,true,false,http://localhost/path?query", + "http://localhost/path?query,false,true,http://localhost/path", + "http://localhost/path?query,false,false,http://localhost/path", + "http://localhost/path?query#fragment,true,true,http://localhost/path?query#fragment", + "http://localhost/path?query#fragment,true,false,http://localhost/path?query", + "http://localhost/path?query#fragment,false,true,http://localhost/path#fragment", + "http://localhost/path?query#fragment,false,false,http://localhost/path", + "http://localhost/path#fragment?query,true,true,http://localhost/path#fragment?query", + "http://localhost/path#fragment?query,true,false,http://localhost/path", + "http://localhost/path#fragment?query,false,true,http://localhost/path#fragment?query", + "http://localhost/path#fragment?query,false,false,http://localhost/path" + }) + void testParsingOfQueryAndFragment( + String reference, boolean query, boolean fragment, String toString) { + final Reference ref = new Reference(reference); + assertEquals(ref.toString(query, fragment), toString); + } + + @Test + void testGetters() { + final Reference host = new Reference("http://host.com"); + final Reference slashdir = new Reference(host, "/dir"); + final Reference dir = new Reference(host, "dir"); + final Reference dirslash = new Reference(host, "dir/"); + final Reference fulldir = new Reference("http://host.com/dir"); + final Reference fulldirsub = new Reference(fulldir, "sub"); + final Reference fulldirslashsub = new Reference(fulldir, "/sub"); + final Reference slashdirsub = new Reference(slashdir, "sub"); + final Reference slashdirslashsub = new Reference(slashdir, "/sub"); + final Reference dirslashsub = new Reference(dirslash, "sub"); + final Reference fullsub = new Reference("http://host.com/dir/sub"); + final Reference fullsubQuery = new Reference("http://host.com/dir/sub?query"); + + testGetters( + host, + "http", + "host.com", + null, + "http://host.com", + "http://host.com", + "http://host.com", + null, + null); + testGetters( + slashdir, + null, + null, + "/dir", + null, + "/dir", + "http://host.com/dir", + null, + "/dir"); + testGetters(dir, null, null, "dir", null, "dir", "http://host.com/dir", null, "dir"); + testGetters( + dirslash, + null, + null, + "dir/", + null, + "dir/", + "http://host.com/dir/", + null, + "dir/"); + testGetters( + fulldir, + "http", + "host.com", + "/dir", + "http://host.com/dir", + "http://host.com/dir", + "http://host.com/dir", + null, + null); + testGetters( + fulldirsub, null, null, "sub", null, "sub", "http://host.com/sub", null, "sub"); + testGetters( + fulldirslashsub, + null, + null, + "/sub", + null, + "/sub", + "http://host.com/sub", + null, + "/sub"); + testGetters( + slashdirsub, + null, + null, + "sub", + null, + "sub", + "http://host.com/sub", + null, + "sub"); + testGetters( + slashdirslashsub, + null, + null, + "/sub", + null, + "/sub", + "http://host.com/sub", + null, + "/sub"); + testGetters( + dirslashsub, + null, + null, + "sub", + null, + "sub", + "http://host.com/dir/sub", + null, + "sub"); + testGetters( + fullsub, + "http", + "host.com", + "/dir/sub", + "http://host.com/dir/sub", + "http://host.com/dir/sub", + "http://host.com/dir/sub", + null, + null); + testGetters( + fullsubQuery, + "http", + "host.com", + "/dir/sub", + "http://host.com/dir/sub?query", + "http://host.com/dir/sub?query", + "http://host.com/dir/sub?query", + "query", + null); + } + + private void testGetters( + Reference reference, + String scheme, + String authority, + String path, + String remainingPart, + String toString, + String targetRef, + String query, + String relativePart) { + assertEquals(reference.getScheme(), scheme); + assertEquals(reference.getAuthority(), authority); + assertEquals(reference.getPath(), path); + assertEquals(reference.getRemainingPart(), remainingPart); + assertEquals(reference.toString(), toString); + assertEquals(reference.getTargetRef().toString(), targetRef); + assertEquals(reference.getQuery(), query); + assertEquals(reference.getRelativePart(), relativePart); + } + + @ParameterizedTest + @CsvSource({ + "http://a/b/c/d;p?q,g:h,g:h", + "http://a/b/c/d;p?q,g,http://a/b/c/g", + "http://a/b/c/d;p?q,./g,http://a/b/c/g", + "http://a/b/c/d;p?q,g/,http://a/b/c/g/", + "http://a/b/c/d;p?q,/g,http://a/g", + "http://a/b/c/d;p?q,//g,http://g", + "http://a/b/c/d;p?q,?y,http://a/b/c/d;p?y", + "http://a/b/c/d;p?q,g?y,http://a/b/c/g?y", + "http://a/b/c/d;p?q,#s,http://a/b/c/d;p?q#s", + "http://a/b/c/d;p?q,g#s,http://a/b/c/g#s", + "http://a/b/c/d;p?q,g?y#s,http://a/b/c/g?y#s", + "http://a/b/c/d;p?q,;x,http://a/b/c/;x", + "http://a/b/c/d;p?q,g;x,http://a/b/c/g;x", + "http://a/b/c/d;p?q,g;x?y#s,http://a/b/c/g;x?y#s", + "http://a/b/c/d;p?q,,http://a/b/c/d;p?q", + "http://a/b/c/d;p?q,.,http://a/b/c/", + "http://a/b/c/d;p?q,./,http://a/b/c/", + "http://a/b/c/d;p?q,..,http://a/b/", + "http://a/b/c/d;p?q,../,http://a/b/", + "http://a/b/c/d;p?q,../g,http://a/b/g", + "http://a/b/c/d;p?q,../..,http://a/", + "http://a/b/c/d;p?q,../../,http://a/", + "http://a/b/c/d;p?q,../../g,http://a/g", + "http://a/b/c/d;p?q,../../../g,http://a/g", + "http://a/b/c/d;p?q,../../../../g,http://a/g", + "http://a/b/c/d;p?q,/./g,http://a/g", + "http://a/b/c/d;p?q,/../g,http://a/g", + "http://a/b/c/d;p?q,g.,http://a/b/c/g.", + "http://a/b/c/d;p?q,.g,http://a/b/c/.g", + "http://a/b/c/d;p?q,g..,http://a/b/c/g..", + "http://a/b/c/d;p?q,..g,http://a/b/c/..g", + "http://a/b/c/d;p?q,./../g,http://a/b/g", + "http://a/b/c/d;p?q,./g/.,http://a/b/c/g/", + "http://a/b/c/d;p?q,g/./h,http://a/b/c/g/h", + "http://a/b/c/d;p?q,g/../h,http://a/b/c/h", + "http://a/b/c/d;p?q,g;x=1/./y,http://a/b/c/g;x=1/y", + "http://a/b/c/d;p?q,g;x=1/../y,http://a/b/c/y" + }) + void testResolutionRelativeReference( + String baseUri, String relativeUri, String expectedAbsoluteUri) { + final Reference baseRef = new Reference(baseUri); + final Reference relativeRef = new Reference(baseRef, relativeUri); + final Reference absoluteRef = relativeRef.getTargetRef(); + assertEquals(expectedAbsoluteUri, absoluteRef.toString()); + } + + @ParameterizedTest + @CsvSource({ + "http://a/b/c/d;p?q,http://a/b/c/g,g", + "http://a/b/c/d;p?q,http://a/b/c/g/,g/", + "http://a/b/c/d;p?q,http://a/b/c/d;p?y,?y", + "http://a/b/c/d;p?q,http://a/b/c/g?y,g?y", + "http://a/b/c/d;p?q,http://a/b/c/d;p?q#s,#s", + "http://a/b/c/d;p?q,http://a/b/c/g#s,g#s", + "http://a/b/c/d;p?q,http://a/b/c/g?y#s,g?y#s", + "http://a/b/c/d;p?q,http://a/b/c/;x,;x", + "http://a/b/c/d;p?q,http://a/b/c/g;x,g;x", + "http://a/b/c/d;p?q,http://a/b/c/g;x?y#s,g;x?y#s", + "http://a/b/c/d;p?q,http://a/b/c/,.", + "http://a/b/c/d;p?q,http://a/b/,..", + "http://a/b/c/d;p?q,http://a/b/g,../g", + "http://a/b/c/d;p?q,http://a/,../..", + "http://a/b/c/d;p?q,http://a/g,../../g", + "http://a/b/c/g/,http://a/b/c/,..", + "http://a/b/c/g/,http://a/b/,../.." + }) + void testRelativizeAbsoluteReference( + String baseUri, String absoluteUri, String expectedRelativeUri) { + final Reference baseRef = new Reference(baseUri); + final Reference absoluteRef = new Reference(absoluteUri); + final Reference relativeRef = absoluteRef.getRelativeRef(baseRef); + assertEquals(expectedRelativeUri, relativeRef.toString()); + } + } } diff --git a/org.restlet/src/test/java/org/restlet/data/RiapConnectorsTestCase.java b/org.restlet/src/test/java/org/restlet/data/RiapConnectorsTestCase.java index 4e71bb98dd..25e9b962c2 100644 --- a/org.restlet/src/test/java/org/restlet/data/RiapConnectorsTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/RiapConnectorsTestCase.java @@ -1,38 +1,40 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.restlet.*; +import org.restlet.Application; +import org.restlet.Component; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.engine.Engine; import org.restlet.representation.Representation; import org.restlet.resource.ClientResource; import org.restlet.routing.Router; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Unit test case for the RIAP Internal routing protocol. - */ -public class RiapConnectorsTestCase { +/** Unit test case for the RIAP Internal routing protocol. */ +class RiapConnectorsTestCase { - /** - * Test the RIAP client and server connectors. - */ + /** Test the RIAP client and server connectors. */ @ParameterizedTest - @ValueSource(strings = { "riap://component/app/test", "riap://component/app/redirectToInternalResource" }) + @ValueSource( + strings = { + "riap://component/app/test", + "riap://component/app/redirectToInternalResource" + }) public void testRiapConnectors(final String url) throws IOException { ClientResource res = new ClientResource(url); Representation rep = res.get(); @@ -51,27 +53,32 @@ static void setUp() throws Exception { component.getServers().add(Protocol.RIAP); component.getClients().add(Protocol.RIAP); - Application app = new Application() { - @Override - public Restlet createInboundRoot() { - Router router = new Router(getContext()); - router.attach("/test", new Restlet(getContext()) { - + Application app = + new Application() { @Override - public void handle(Request request, Response response) { - response.setEntity("hello, world", MediaType.TEXT_PLAIN); + public Restlet createInboundRoot() { + Router router = new Router(getContext()); + router.attach( + "/test", + new Restlet(getContext()) { + + @Override + public void handle(Request request, Response response) { + response.setEntity("hello, world", MediaType.TEXT_PLAIN); + } + }); + router.attach( + "/redirectToInternalResource", + new Restlet(getContext()) { + public void handle(Request request, Response response) { + ClientResource resource = + new ClientResource("riap://component/app/test"); + response.setEntity(resource.get()); + } + }); + return router; } - - }); - router.attach("/redirectToInternalResource", new Restlet(getContext()) { - public void handle(Request request, Response response) { - ClientResource resource = new ClientResource("riap://component/app/test"); - response.setEntity(resource.get()); - } - }); - return router; - } - }; + }; // Attach the private application component.getInternalRouter().attach("/app", app); @@ -85,5 +92,4 @@ static void tearDown() throws Exception { component.stop(); component = null; } - } diff --git a/org.restlet/src/test/java/org/restlet/data/RiapTestCase.java b/org.restlet/src/test/java/org/restlet/data/RiapTestCase.java index 70c9a7bdb7..20fbd4bd44 100644 --- a/org.restlet/src/test/java/org/restlet/data/RiapTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/RiapTestCase.java @@ -1,34 +1,39 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.fail; +import static org.restlet.data.LocalReference.RIAP_APPLICATION; +import static org.restlet.data.LocalReference.createRiapReference; + +import java.io.Serializable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.restlet.*; +import org.restlet.Application; +import org.restlet.Component; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.representation.ObjectRepresentation; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.resource.ClientResource; -import java.io.Serializable; - -import static org.junit.jupiter.api.Assertions.*; -import static org.restlet.data.LocalReference.RIAP_APPLICATION; -import static org.restlet.data.LocalReference.createRiapReference; - /** * Unit test case for the RIAP Internal routing protocol. * * @author Marc Portier */ -public class RiapTestCase { +class RiapTestCase { private static final String DEFAULT_MSG = "no-default"; @@ -51,39 +56,46 @@ private static String buildAggregate(String echoMessage, String echoCopy) { @BeforeAll public static void setUp() { final Component comp = new Component(); - final Application localOnly = new Application() { - @Override - public Restlet createInboundRoot() { - return new Restlet(getContext()) { + final Application localOnly = + new Application() { @Override - public void handle(Request request, Response response) { - final Reference ref = request.getResourceRef(); - final String remainder = ref.getRemainingPart(); - - Representation result = new StringRepresentation(DEFAULT_MSG); - - if (remainder.startsWith("/echo/")) { - result = new StringRepresentation(remainder.substring(6)); - } else if (remainder.equals("/object")) { - result = new ObjectRepresentation<>(JUST_SOME_OBJ); - } else if (remainder.equals("/null")) { - result = new ObjectRepresentation<>((Serializable) null); - } else if (remainder.equals("/self-aggregated")) { - final String echoMessage = ECHO_TEST_MSG; - final Reference echoRef = createRiapReference(RIAP_APPLICATION, "/echo/" + echoMessage); - try { - String echoCopy = new ClientResource(echoRef).get().getText(); - assertEquals(echoMessage, echoCopy, "expected echoMessage back"); - result = new StringRepresentation(buildAggregate( echoMessage, echoCopy)); - } catch (Exception e) { - fail("Error getting internal reference to " + echoRef, e); + public Restlet createInboundRoot() { + return new Restlet(getContext()) { + @Override + public void handle(Request request, Response response) { + final Reference ref = request.getResourceRef(); + final String remainder = ref.getRemainingPart(); + + Representation result = new StringRepresentation(DEFAULT_MSG); + + if (remainder.startsWith("/echo/")) { + result = new StringRepresentation(remainder.substring(6)); + } else if (remainder.equals("/object")) { + result = new ObjectRepresentation<>(JUST_SOME_OBJ); + } else if (remainder.equals("/null")) { + result = new ObjectRepresentation<>((Serializable) null); + } else if (remainder.equals("/self-aggregated")) { + final String echoMessage = ECHO_TEST_MSG; + final Reference echoRef = + createRiapReference( + RIAP_APPLICATION, "/echo/" + echoMessage); + try { + String echoCopy = + new ClientResource(echoRef).get().getText(); + assertEquals( + echoMessage, echoCopy, "expected echoMessage back"); + result = + new StringRepresentation( + buildAggregate(echoMessage, echoCopy)); + } catch (Exception e) { + fail("Error getting internal reference to " + echoRef, e); + } + } + response.setEntity(result); } - } - response.setEntity(result); + }; } }; - } - }; comp.getInternalRouter().attach("/local", localOnly); @@ -92,40 +104,41 @@ public void handle(Request request, Response response) { } @Test - public void testEcho() throws Exception { + void testEcho() throws Exception { String msg = "this%20message"; Representation echoRep = sendLocalRequestAndGetRepresentation("/echo/" + msg); assertEquals(msg, echoRep.getText(), "expected echo of uri-remainder"); } @Test - public void testObject() throws Exception { + void testObject() throws Exception { final Representation objRep = sendLocalRequestAndGetRepresentation("/object"); - assertSame(JUST_SOME_OBJ, ((ObjectRepresentation) objRep).getObject(), - "expected specific test-object" - ); + assertSame( + JUST_SOME_OBJ, + ((ObjectRepresentation) objRep).getObject(), + "expected specific test-object"); } @Test - public void testNull() throws Exception { + void testNull() throws Exception { final Representation nullRep = sendLocalRequestAndGetRepresentation("/null"); assertNull(((ObjectRepresentation) nullRep).getObject(), "expected null"); } @Test - public void testWhatever() throws Exception { + void testWhatever() throws Exception { final Representation anyRep = sendLocalRequestAndGetRepresentation("/whatever"); assertEquals(DEFAULT_MSG, anyRep.getText(), "expected echo of uri-remainder"); } @Test - public void testSelfAggregated() throws Exception { + void testSelfAggregated() throws Exception { final Representation aggRep = sendLocalRequestAndGetRepresentation("/self-aggregated"); final String expectedResult = buildAggregate(ECHO_TEST_MSG, ECHO_TEST_MSG); assertEquals(expectedResult, aggRep.getText(), "expected specific aggregated message"); } private static Representation sendLocalRequestAndGetRepresentation(final String url) { - return dispatcher.handle(new Request(Method.GET, localBase+ url)).getEntity(); + return dispatcher.handle(new Request(Method.GET, localBase + url)).getEntity(); } } diff --git a/org.restlet/src/test/java/org/restlet/data/StatusTestCase.java b/org.restlet/src/test/java/org/restlet/data/StatusTestCase.java index 6ba5009254..c28a975fc5 100644 --- a/org.restlet/src/test/java/org/restlet/data/StatusTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/StatusTestCase.java @@ -1,37 +1,36 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; /** * Test {@link org.restlet.data.Status}. - * + * * @author Jerome Louvel */ -public class StatusTestCase { +class StatusTestCase { @Test - public void testCustomDescription() { + void testCustomDescription() { final String customDescription = "My custom description"; final Status s = new Status(Status.CLIENT_ERROR_NOT_FOUND, customDescription); assertEquals(customDescription, s.getDescription()); } - /** - * Equality tests. - */ + /** Equality tests. */ @Test - public void testEquals() { + void testEquals() { final Status s1 = new Status(201); final Status s2 = Status.SUCCESS_CREATED; @@ -40,11 +39,9 @@ public void testEquals() { assertEquals(s1, s2); } - /** - * Tests for status classes. - */ + /** Tests for status classes. */ @Test - public void testStatusClasses() { + void testStatusClasses() { final Status s1 = new Status(287); assertTrue(s1.isSuccess()); @@ -53,11 +50,9 @@ public void testStatusClasses() { assertTrue(s2.isError()); } - /** - * Unequality tests. - */ + /** Unequality tests. */ @Test - public void testUnEquals() { + void testUnEquals() { final Status s1 = new Status(200); final Status s2 = Status.SUCCESS_CREATED; @@ -66,5 +61,4 @@ public void testUnEquals() { assertNotEquals(null, s1); assertNotEquals(null, s2); } - } diff --git a/org.restlet/src/test/java/org/restlet/data/TagTestCase.java b/org.restlet/src/test/java/org/restlet/data/TagTestCase.java index 05c4a189b5..c264061ffb 100644 --- a/org.restlet/src/test/java/org/restlet/data/TagTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/TagTestCase.java @@ -1,48 +1,49 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; -import org.junit.jupiter.api.Test; -import org.restlet.engine.header.TagReader; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import org.restlet.engine.header.TagReader; /** * Test {@link org.restlet.data.Tag}. * * @author Jerome Louvel */ -public class TagTestCase { +class TagTestCase { @Test - public void testSimpleTag() { + void testSimpleTag() { assertEquals("my-tag", Tag.parse("\"my-tag\"").getName()); assertFalse(Tag.parse("\"my-tag\"").isWeak()); } @Test - public void testInvalidTag() { + void testInvalidTag() { assertNull(Tag.parse("my-tag")); assertNull(Tag.parse("\"my-tag")); } @Test - public void testAllTag() { + void testAllTag() { assertEquals(Tag.ALL.getName(), Tag.parse("*").getName()); } @Test - public void testWeakTag() { + void testWeakTag() { assertEquals(Tag.ALL.getName(), Tag.parse("W/*").getName()); assertTrue(Tag.parse("W/*").isWeak()); @@ -51,7 +52,7 @@ public void testWeakTag() { } @Test - public void testListOfValidTags() { + void testListOfValidTags() { List tags = new ArrayList<>(); new TagReader("\"xyz\", \"r2d2\", \"c3pio\", *").addValues(tags); assertEquals("xyz", tags.get(0).getName()); @@ -62,7 +63,7 @@ public void testListOfValidTags() { } @Test - public void testListOfTagsWithInvalidTag() { + void testListOfTagsWithInvalidTag() { List tags = new ArrayList<>(); new TagReader("\"xyz\", \"r2d2\", c3pio, *").addValues(tags); assertEquals("xyz", tags.get(0).getName()); @@ -70,5 +71,4 @@ public void testListOfTagsWithInvalidTag() { assertEquals(Tag.ALL.getName(), tags.get(2).getName()); assertEquals(3, tags.size()); } - } diff --git a/org.restlet/src/test/java/org/restlet/data/ZipClientTestCase.java b/org.restlet/src/test/java/org/restlet/data/ZipClientTestCase.java index aaf09f59db..4e51401362 100644 --- a/org.restlet/src/test/java/org/restlet/data/ZipClientTestCase.java +++ b/org.restlet/src/test/java/org/restlet/data/ZipClientTestCase.java @@ -1,14 +1,20 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.data; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -18,32 +24,24 @@ import org.restlet.resource.ClientResource; import org.restlet.resource.ResourceException; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - /** * Unit test case for the Zip client connector. * * @author Remi Dewitte */ @Disabled("flaky on github") -public class ZipClientTestCase { +class ZipClientTestCase { private File zipFile; @BeforeEach - protected void setUpEach() throws Exception { + void setUpEach() throws Exception { Path testCaseDirectoryPath = Files.createTempDirectory("ZipClientTestCase"); zipFile = testCaseDirectoryPath.resolve("test.zip").toFile(); } @AfterEach - protected void tearDownEach() throws Exception { + void tearDownEach() { zipFile.delete(); } @@ -58,52 +56,57 @@ void testFileClient() throws IOException { String dirEntryReference = zr + "!/dir/"; String test3FileInDirEntryReference = dirEntryReference + "test3.txt"; - // Write test.txt as first entry + // Write test.txt as the first entry ClientResource testFileEntryClientResource = new ClientResource(testFileEntryReference); testFileEntryClientResource.put(new StringRepresentation(text)); - assertEquals(testFileEntryClientResource.getStatus(), Status.SUCCESS_CREATED); + assertEquals(Status.SUCCESS_CREATED, testFileEntryClientResource.getStatus()); // Get the text and compare to the original testFileEntryClientResource.get(); - assertEquals(testFileEntryClientResource.getStatus(), Status.SUCCESS_OK); - assertEquals(testFileEntryClientResource.getResponseEntity().getText(), text); + assertEquals(Status.SUCCESS_OK, testFileEntryClientResource.getStatus()); + assertEquals(text, testFileEntryClientResource.getResponseEntity().getText()); testFileEntryClientResource.release(); - // Write test2.txt as second entry + // Write test2.txt as the second entry ClientResource test2FileEntryClientResource = new ClientResource(test2FileEntryReference); test2FileEntryClientResource.put(new StringRepresentation(text2)); - assertEquals(test2FileEntryClientResource.getStatus(), Status.SUCCESS_OK); + assertEquals(Status.SUCCESS_OK, test2FileEntryClientResource.getStatus()); // Check that the first entry has not been overwritten testFileEntryClientResource.get(); - assertEquals(testFileEntryClientResource.getStatus(), Status.SUCCESS_OK); - assertEquals(testFileEntryClientResource.getResponseEntity().getText(), text); + assertEquals(Status.SUCCESS_OK, testFileEntryClientResource.getStatus()); + assertEquals(text, testFileEntryClientResource.getResponseEntity().getText()); testFileEntryClientResource.release(); // Put a directory ClientResource dirEntryClientResource = new ClientResource(dirEntryReference); dirEntryClientResource.put(new EmptyRepresentation()); - assertEquals(dirEntryClientResource.getStatus(), Status.SUCCESS_OK); + assertEquals(Status.SUCCESS_OK, dirEntryClientResource.getStatus()); dirEntryClientResource.get(); - assertEquals(dirEntryClientResource.getStatus(), Status.SUCCESS_OK); + assertEquals(Status.SUCCESS_OK, dirEntryClientResource.getStatus()); // Add a file inside the directory - ClientResource testFileInDirEntryCLientResource = new ClientResource(test3FileInDirEntryReference); + ClientResource testFileInDirEntryCLientResource = + new ClientResource(test3FileInDirEntryReference); testFileInDirEntryCLientResource.put(new StringRepresentation(text)); - assertEquals(testFileInDirEntryCLientResource.getStatus(), Status.SUCCESS_OK); + assertEquals(Status.SUCCESS_OK, testFileInDirEntryCLientResource.getStatus()); // Check that the second entry is still there test2FileEntryClientResource.get(); - assertEquals(test2FileEntryClientResource.getStatus(), Status.SUCCESS_OK, "Could not get " + test2FileEntryReference); - assertEquals(test2FileEntryClientResource.getResponseEntity().getText(), text2); + assertEquals( + Status.SUCCESS_OK, + test2FileEntryClientResource.getStatus(), + "Could not get " + test2FileEntryReference); + assertEquals(text2, test2FileEntryClientResource.getResponseEntity().getText()); // Check that content negotiation does not work ClientResource rTest2 = new ClientResource(zr + "!test2"); assertThrows(ResourceException.class, rTest2::get); - // Try to replace file by directory + // Try to replace a file by directory ClientResource r2d = new ClientResource(test2FileEntryReference + "/"); - assertThrows(ResourceException.class, () -> r2d.put(new EmptyRepresentation())); + final EmptyRepresentation entity = new EmptyRepresentation(); + assertThrows(ResourceException.class, () -> r2d.put(entity)); } } diff --git a/org.restlet/src/test/java/org/restlet/engine/EngineTest.java b/org.restlet/src/test/java/org/restlet/engine/EngineTest.java index fe0e1f4038..91ae860e1d 100644 --- a/org.restlet/src/test/java/org/restlet/engine/EngineTest.java +++ b/org.restlet/src/test/java/org/restlet/engine/EngineTest.java @@ -1,36 +1,36 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.io.InputStream; import java.util.Properties; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class EngineTest { +class EngineTest { @Test - public void engineVersionShouldBeEqualToMavenProjectVersion() { + void engineVersionShouldBeEqualToMavenProjectVersion() { // When I retrieve the Maven project's version as stated in the pom file. Properties properties = new Properties(); - try (InputStream resourceAsStream = EngineTest.class.getClassLoader().getResourceAsStream("maven-version.properties")) { + try (InputStream resourceAsStream = + EngineTest.class.getClassLoader().getResourceAsStream("maven-version.properties")) { properties.load(resourceAsStream); } catch (IOException e) { - Assertions.fail("Can't load the properties file that contain the Maven's project version"); + Assertions.fail( + "Can't load the properties file that contain the Maven's project version"); } // Then the Maven project's version should be equal to the Engine's version. assertEquals(Engine.VERSION, properties.getProperty("maven.version")); } -} \ No newline at end of file +} diff --git a/org.restlet/src/test/java/org/restlet/engine/application/CorsResponseFilterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/CorsResponseFilterTestCase.java index 25e3512a58..d0bdede48e 100644 --- a/org.restlet/src/test/java/org/restlet/engine/application/CorsResponseFilterTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/application/CorsResponseFilterTestCase.java @@ -1,14 +1,19 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collection; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; @@ -20,22 +25,19 @@ import org.restlet.resource.Options; import org.restlet.resource.ServerResource; -import java.util.Collection; - -import static org.junit.jupiter.api.Assertions.*; - - /** +/** * @author Manuel Boillod */ -public class CorsResponseFilterTestCase { +class CorsResponseFilterTestCase { private CorsFilter corsFilter; public static class DummyServerResource extends ServerResource { @Options - public void doOption(){} + public void doOption() {} + @Get - public void doGet(){} + public void doGet() {} } @BeforeEach @@ -47,7 +49,7 @@ public void setUpEach() throws Exception { // INVALID CORS REQUESTS @Test - public void testGet_withoutOrigin() { + void testGet_withoutOrigin() { Request request = new Request(); request.setMethod(Method.GET); Response response = corsFilter.handle(request); @@ -55,7 +57,7 @@ public void testGet_withoutOrigin() { } @Test - public void testOption_withoutOrigin() { + void testOption_withoutOrigin() { Request request = new Request(); request.setMethod(Method.OPTIONS); Response response = corsFilter.handle(request); @@ -63,7 +65,7 @@ public void testOption_withoutOrigin() { } @Test - public void testOption_withoutRequestMethod() { + void testOption_withoutRequestMethod() { Request request = new Request(); request.setMethod(Method.OPTIONS); request.getHeaders().set("Origin", "localhost"); @@ -74,7 +76,7 @@ public void testOption_withoutRequestMethod() { // VALID CORS REQUESTS @Test - public void testGet() { + void testGet() { Request request = new Request(); request.setMethod(Method.GET); request.getHeaders().set("Origin", "localhost"); @@ -87,7 +89,7 @@ public void testGet() { } @Test - public void testGet_withAuthenticationAllowed() { + void testGet_withAuthenticationAllowed() { corsFilter.setAllowedCredentials(true); Request request = new Request(); @@ -102,7 +104,7 @@ public void testGet_withAuthenticationAllowed() { } @Test - public void testOption_requestGet() { + void testOption_requestGet() { Request request = new Request(); request.setMethod(Method.OPTIONS); request.getHeaders().set("Origin", "localhost"); @@ -111,12 +113,14 @@ public void testOption_requestGet() { assertEquals("*", response.getAccessControlAllowOrigin()); assertNull(response.getAccessControlAllowCredentials()); assertIsEmpty(response.getAccessControlAllowHeaders()); - MatcherAssert.assertThat(response.getAccessControlAllowMethods(), Matchers.contains(Method.GET, Method.OPTIONS)); + MatcherAssert.assertThat( + response.getAccessControlAllowMethods(), + Matchers.contains(Method.GET, Method.OPTIONS)); assertIsEmpty(response.getAccessControlExposeHeaders()); } @Test - public void testOption_requestGet_skippingResource() { + void testOption_requestGet_skippingResource() { corsFilter.setSkippingResourceForCorsOptions(true); Request request = new Request(); @@ -127,12 +131,15 @@ public void testOption_requestGet_skippingResource() { assertEquals("*", response.getAccessControlAllowOrigin()); assertNull(response.getAccessControlAllowCredentials()); assertIsEmpty(response.getAccessControlAllowHeaders()); - MatcherAssert.assertThat(response.getAccessControlAllowMethods(), Matchers.containsInAnyOrder(Method.GET, Method.POST, Method.PUT, Method.DELETE, Method.PATCH)); + MatcherAssert.assertThat( + response.getAccessControlAllowMethods(), + Matchers.containsInAnyOrder( + Method.GET, Method.POST, Method.PUT, Method.DELETE, Method.PATCH)); assertIsEmpty(response.getAccessControlExposeHeaders()); } @Test - public void testOption_requestPost_skippingResource() { + void testOption_requestPost_skippingResource() { corsFilter.setSkippingResourceForCorsOptions(true); Request request = new Request(); @@ -143,12 +150,15 @@ public void testOption_requestPost_skippingResource() { assertEquals("*", response.getAccessControlAllowOrigin()); assertNull(response.getAccessControlAllowCredentials()); assertIsEmpty(response.getAccessControlAllowHeaders()); - MatcherAssert.assertThat(response.getAccessControlAllowMethods(), Matchers.containsInAnyOrder(Method.GET, Method.POST, Method.PUT, Method.DELETE, Method.PATCH)); + MatcherAssert.assertThat( + response.getAccessControlAllowMethods(), + Matchers.containsInAnyOrder( + Method.GET, Method.POST, Method.PUT, Method.DELETE, Method.PATCH)); assertIsEmpty(response.getAccessControlExposeHeaders()); } @Test - public void testOption_requestGet_withAuthenticationAllowed() { + void testOption_requestGet_withAuthenticationAllowed() { corsFilter.setAllowedCredentials(true); Request request = new Request(); @@ -159,7 +169,9 @@ public void testOption_requestGet_withAuthenticationAllowed() { assertEquals("localhost", response.getAccessControlAllowOrigin()); assertEquals(Boolean.TRUE, response.getAccessControlAllowCredentials()); assertIsEmpty(response.getAccessControlAllowHeaders()); - MatcherAssert.assertThat(response.getAccessControlAllowMethods(), Matchers.contains(Method.GET, Method.OPTIONS)); + MatcherAssert.assertThat( + response.getAccessControlAllowMethods(), + Matchers.contains(Method.GET, Method.OPTIONS)); assertIsEmpty(response.getAccessControlExposeHeaders()); } @@ -175,5 +187,4 @@ public void assertNoCorsHeaders(Response response) { assertIsEmpty(response.getAccessControlAllowMethods()); assertIsEmpty(response.getAccessControlExposeHeaders()); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/application/RangeRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/RangeRepresentationTestCase.java index 20552ee461..42e38f63fc 100644 --- a/org.restlet/src/test/java/org/restlet/engine/application/RangeRepresentationTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/application/RangeRepresentationTestCase.java @@ -1,30 +1,29 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + import org.junit.jupiter.api.Test; import org.restlet.data.Range; import org.restlet.representation.StringRepresentation; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - /** * Unit test case for the {@link RangeRepresentation} class. - * + * * @author Jerome Louvel */ -public class RangeRepresentationTestCase { +class RangeRepresentationTestCase { @Test - public void testAppendable() throws Exception { + void testAppendable() throws Exception { StringRepresentation sr = new StringRepresentation("1234567890"); RangeRepresentation rr = new RangeRepresentation(sr); rr.setRange(new Range(2, 5)); @@ -36,7 +35,7 @@ public void testAppendable() throws Exception { } @Test - public void testSize() throws Exception { + void testSize() throws Exception { StringRepresentation sr = new StringRepresentation("1234567890"); RangeRepresentation rr = new RangeRepresentation(sr); rr.setRange(new Range(5, 10000)); @@ -46,5 +45,4 @@ public void testSize() throws Exception { assertEquals("1234567890", sr.getText()); assertEquals("67890", rr.getText()); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/application/TunnelFilterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/application/TunnelFilterTestCase.java index bde324499b..c5ffe713ce 100644 --- a/org.restlet/src/test/java/org/restlet/engine/application/TunnelFilterTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/application/TunnelFilterTestCase.java @@ -1,36 +1,45 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.application; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.restlet.Application; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; -import org.restlet.data.*; +import org.restlet.data.CharacterSet; +import org.restlet.data.Encoding; +import org.restlet.data.Header; +import org.restlet.data.Language; +import org.restlet.data.MediaType; +import org.restlet.data.Metadata; +import org.restlet.data.Method; +import org.restlet.data.Preference; +import org.restlet.data.Reference; import org.restlet.engine.header.HeaderConstants; import org.restlet.util.Series; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests cases for the tunnel filter. - */ -public class TunnelFilterTestCase { +/** Tests cases for the tunnel filter. */ +class TunnelFilterTestCase { /** . */ private static final String EFFECTED = "http://example.org/adf.asdf/af.html"; @@ -39,7 +48,8 @@ public class TunnelFilterTestCase { private static final String QUERY = "http://example.org/?start=2013-11-26T03%3A45%2B1300"; /** . */ - private static final String QUERY_PREF = "http://example.org/?start=2013-11-26T03%3A45%2B1300&media=txt"; + private static final String QUERY_PREF = + "http://example.org/?start=2013-11-26T03%3A45%2B1300&media=txt"; /** . */ private static final String START_REF_FOR_PATH_TEST = "http://www.example.com/abc/def/"; @@ -74,7 +84,8 @@ void assertEncodings(Encoding... encodings) { } @SafeVarargs - final void assertEqualSet(List> actual, A... expected) { + final void assertEqualSet( + List> actual, A... expected) { if (actual.size() != expected.length) { System.out.println("Is: " + actual); System.out.println("Should: " + Arrays.asList(expected)); @@ -89,8 +100,7 @@ final void assertEqualSet(List> act } } if (!contained) { - final String message = exp - + " should be in, but is missing in " + actual; + final String message = exp + " should be in, but is missing in " + actual; fail(message); } } @@ -112,10 +122,6 @@ void assertNotSameMethod(Method method) { assertNotSame(this.request.getMethod(), method); } - /** - * @param expectedCut - * @param expectedExtensions - */ private void check(String expectedCut, String expectedExtensions) { final Reference resourceRef = this.request.getResourceRef(); assertEquals(expectedCut, resourceRef.toString()); @@ -127,19 +133,14 @@ private void check(String expectedCut, String expectedExtensions) { } /** - * - * @param expectedSubPathCut - * if null, the same as subPathOrig - * @param expectedExtension - * if null, then same as "" for this test + * @param expectedSubPathCut if null, the same as subPathOrig + * @param expectedExtension if null, then the same as "" for this test */ - private void checkFromPath(String expectedSubPathCut, - String expectedExtension) { + private void checkFromPath(String expectedSubPathCut, String expectedExtension) { if (expectedSubPathCut == null) { check(this.lastCreatedReference, expectedExtension); } else { - check(START_REF_FOR_PATH_TEST + expectedSubPathCut, - expectedExtension); + check(START_REF_FOR_PATH_TEST + expectedSubPathCut, expectedExtension); } } @@ -152,8 +153,6 @@ void createGet(String reference) { } /** - * - * @param subPathToCheck * @see #createGet(String) * @see #createRequest(Method, String) */ @@ -161,25 +160,21 @@ private void createGetFromPath(String subPathToCheck) { createGet(START_REF_FOR_PATH_TEST + subPathToCheck); } - /** - * - */ + /** */ void createPost(String reference) { createRequest(Method.POST, reference); } /** * Creates a {@link Request} and put it into {@link #request}.
- * To use the methods provided by the test case class use ever the provided - * create methods to create a request. + * To use the methods provided by the test case class, use ever the provided create methods to + * create a request. * - * @param method - * @param reference * @see #createPost(String) * @see #createGet(String) * @see #createGetFromPath(String) */ - void createRequest(Method method, String reference) { + private void createRequest(Method method, String reference) { this.request = new Request(method, reference); this.request.setOriginalRef(new Reference(reference)); this.response = new Response(this.request); @@ -193,49 +188,43 @@ private void extensionTunnelOff() { application.getTunnelService().setExtensionsTunnel(false); } - /** - * Call this method to filter the current request - */ + /** Call this method to filter the current request */ private void filter() { this.tunnelFilter.beforeHandle(this.request, this.response); setPrefs(); } private void setPrefs() { - this.accMediaTypes = this.request.getClientInfo() - .getAcceptedMediaTypes(); + this.accMediaTypes = this.request.getClientInfo().getAcceptedMediaTypes(); this.accLanguages = this.request.getClientInfo().getAcceptedLanguages(); - this.accCharsets = this.request.getClientInfo() - .getAcceptedCharacterSets(); + this.accCharsets = this.request.getClientInfo().getAcceptedCharacterSets(); this.accEncodings = this.request.getClientInfo().getAcceptedEncodings(); } @BeforeEach - public void setUpEach() throws Exception { + void setUpEach() { Application app = new Application(new Context()); Application.setCurrent(app); this.tunnelFilter = new TunnelFilter(app.getContext()); - this.tunnelFilter.getApplication().getTunnelService() - .setExtensionsTunnel(true); + this.tunnelFilter.getApplication().getTunnelService().setExtensionsTunnel(true); } @AfterEach - protected void tearDownEach() throws Exception { + void tearDownEach() { this.tunnelFilter = null; this.request = null; this.response = null; } - @Test - public void testExtMappingOff1() { + @ParameterizedTest + @ValueSource(strings = {EFFECTED, UNEFFECTED}) + void testExtensionsMappingOff(String reference) { extensionTunnelOff(); - createGet(UNEFFECTED); - this.accLanguages - .add(new Preference<>(Language.valueOf("ajh"))); - this.accMediaTypes.add(new Preference<>( - MediaType.APPLICATION_STUFFIT)); + createGet(reference); + this.accLanguages.add(new Preference<>(Language.valueOf("ajh"))); + this.accMediaTypes.add(new Preference<>(MediaType.APPLICATION_STUFFIT)); filter(); - assertEquals(UNEFFECTED, this.request.getResourceRef().toString()); + assertEquals(reference, this.request.getResourceRef().toString()); assertLanguages(Language.valueOf("ajh")); assertMediaTypes(MediaType.APPLICATION_STUFFIT); assertCharSets(); @@ -243,39 +232,27 @@ public void testExtMappingOff1() { } @Test - public void testExtMappingOff2() { - extensionTunnelOff(); - createGet(EFFECTED); - this.accLanguages - .add(new Preference<>(Language.valueOf("ajh"))); - this.accMediaTypes.add(new Preference<>( - MediaType.APPLICATION_STUFFIT)); - filter(); - assertEquals(EFFECTED, this.request.getResourceRef().toString()); - assertLanguages(Language.valueOf("ajh")); - assertMediaTypes(MediaType.APPLICATION_STUFFIT); - assertCharSets(); - assertEncodings(); - } - - @Test - public void testExtMappingOn() { + void shouldDetectExtensionWithoutMediaType() { createGet(UNEFFECTED); filter(); check(UNEFFECTED, "ab"); assertLanguages(); assertCharSets(); - assertCharSets(); assertMediaTypes(); + } + @Test + void shouldDetectMediaType() { createGet(EFFECTED); filter(); check("http://example.org/adf.asdf/af", null); assertMediaTypes(MediaType.TEXT_HTML); assertLanguages(); assertCharSets(); - assertCharSets(); + } + @Test + void shouldCutPathAndExtension() { createGetFromPath("afhhh"); filter(); checkFromPath(null, null); @@ -283,7 +260,10 @@ public void testExtMappingOn() { assertLanguages(); assertEncodings(); assertCharSets(); + } + @Test + void shouldCutPathAndDetectExtension() { createGetFromPath("hksf.afsdf"); filter(); checkFromPath(null, "afsdf"); @@ -291,7 +271,10 @@ public void testExtMappingOn() { assertLanguages(); assertEncodings(); assertCharSets(); + } + @Test + void shouldKeepPathAndDetectExtensionAndMediaType() { createGetFromPath("hksf.afsdf.html"); filter(); checkFromPath("hksf.afsdf", "afsdf"); @@ -299,29 +282,31 @@ public void testExtMappingOn() { assertLanguages(); assertEncodings(); assertCharSets(); + } - createGetFromPath("hksf.afsdf.html.txt"); - filter(); - checkFromPath("hksf.afsdf.html", "afsdf.html"); - assertMediaTypes(MediaType.TEXT_PLAIN); - assertLanguages(); - assertEncodings(); - assertCharSets(); - - createGetFromPath("hksf.html.afsdf.txt"); + @ParameterizedTest + @CsvSource({ + "hksf.afsdf.html.txt,hksf.afsdf.html,afsdf.html", + "hksf.html.afsdf.txt,hksf.html.afsdf,html.afsdf" + }) + void shouldKeepPathAndDetectExtensionAndKeepLastMediaType( + String path, String expectedSubPathCut, String expectedExtension) { + createGetFromPath(path); filter(); - checkFromPath("hksf.html.afsdf", "html.afsdf"); + checkFromPath(expectedSubPathCut, expectedExtension); assertMediaTypes(MediaType.TEXT_PLAIN); assertLanguages(); assertEncodings(); assertCharSets(); + } + @Test + void shouldKeepPathAndDetectExtensionAndKeepLastMediaTypeAndLastLanguage() { createGetFromPath("hksf.html.afsdf.txt.en.fr"); filter(); checkFromPath("hksf.html.afsdf.txt.en", "html.afsdf.txt.en"); // Take care about the fact that only one extension per metadata "type" // is allowed: ie only one Language, one encoding, one media type, etc. - // assertMediaTypes(MediaType.TEXT_PLAIN); assertMediaTypes(); assertLanguages(Language.FRENCH); assertEncodings(); @@ -334,7 +319,10 @@ public void testExtMappingOn() { assertLanguages(Language.ENGLISH); assertEncodings(); assertCharSets(); + } + @Test + void shouldDetectNoExtension() { createGet(START_REF_FOR_PATH_TEST); filter(); checkFromPath(null, null); @@ -345,12 +333,11 @@ public void testExtMappingOn() { } @Test - public void testMethodTunnelingViaHeader() { + void testMethodTunnelingViaHeader() { tunnelFilter.getTunnelService().setMethodTunnel(true); Map attributesHeader = new HashMap<>(); Series

headers = new Series<>(Header.class); - headers.add(HeaderConstants.HEADER_X_HTTP_METHOD_OVERRIDE, - Method.GET.getName()); + headers.add(HeaderConstants.HEADER_X_HTTP_METHOD_OVERRIDE, Method.GET.getName()); headers.add(HeaderConstants.HEADER_X_FORWARDED_FOR, "TEST"); attributesHeader.put(HeaderConstants.ATTRIBUTE_HEADERS, headers); @@ -364,24 +351,21 @@ public void testMethodTunnelingViaHeader() { assertMethod(Method.POST); createPost(UNEFFECTED); - tunnelFilter.getTunnelService().setMethodHeader( - HeaderConstants.HEADER_X_FORWARDED_FOR); + tunnelFilter.getTunnelService().setMethodHeader(HeaderConstants.HEADER_X_FORWARDED_FOR); this.request.setAttributes(attributesHeader); filter(); assertNotSameMethod(Method.PUT); createPost(UNEFFECTED); - tunnelFilter.getTunnelService().setMethodHeader( - HeaderConstants.HEADER_X_FORWARDED_FOR); + tunnelFilter.getTunnelService().setMethodHeader(HeaderConstants.HEADER_X_FORWARDED_FOR); tunnelFilter.getTunnelService().setHeadersTunnel(false); this.request.setAttributes(attributesHeader); filter(); assertMethod(Method.POST); - } @Test - public void testWithMatrixParam() { + void testWithMatrixParam() { createGet(EFFECTED + ";abcdef"); filter(); check("http://example.org/adf.asdf/af;abcdef", null); @@ -392,7 +376,7 @@ public void testWithMatrixParam() { } @Test - public void testMethodTunnelingViaUserAgent() { + void testMethodTunnelingViaUserAgent() { tunnelFilter.getTunnelService().setExtensionsTunnel(false); tunnelFilter.getTunnelService().setHeadersTunnel(false); tunnelFilter.getTunnelService().setMethodTunnel(false); @@ -401,26 +385,28 @@ public void testMethodTunnelingViaUserAgent() { tunnelFilter.getTunnelService().setUserAgentTunnel(true); createGet(UNEFFECTED); - this.accMediaTypes.add(new Preference<>( - MediaType.APPLICATION_ZIP)); + this.accMediaTypes.add(new Preference<>(MediaType.APPLICATION_ZIP)); filter(); assertEquals(UNEFFECTED, this.request.getResourceRef().toString()); assertMediaTypes(MediaType.APPLICATION_ZIP); assertCharSets(); assertEncodings(); - this.userAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)"; + this.userAgent = + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)"; createGet(UNEFFECTED); - this.accMediaTypes.add(new Preference<>( - MediaType.APPLICATION_ZIP)); + this.accMediaTypes.add(new Preference<>(MediaType.APPLICATION_ZIP)); filter(); assertEquals(UNEFFECTED, this.request.getResourceRef().toString()); - assertMediaTypes(MediaType.TEXT_HTML, MediaType.APPLICATION_XHTML, - MediaType.APPLICATION_XML, MediaType.ALL); + assertMediaTypes( + MediaType.TEXT_HTML, + MediaType.APPLICATION_XHTML, + MediaType.APPLICATION_XML, + MediaType.ALL); } @Test - public void testMethodTunnelingViaQuery() { + void testMethodTunnelingViaQuery() { tunnelFilter.getTunnelService().setExtensionsTunnel(false); tunnelFilter.getTunnelService().setHeadersTunnel(false); tunnelFilter.getTunnelService().setMethodTunnel(false); diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/AsyncTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/AsyncTestCase.java index 765a2a5222..a80faced4b 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/AsyncTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/AsyncTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -15,7 +14,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.CountDownLatch; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,7 +39,7 @@ * * @author Florian Buecklers */ -public class AsyncTestCase { +class AsyncTestCase { private Context context; @@ -62,25 +60,24 @@ private boolean responseEntityExpected(Method method) { private void testCall(Context context, int count, Method method) throws Exception { final CountDownLatch latch = new CountDownLatch(count); - final Uniform responseHandler = (request, response) -> { - String item = request.getResourceRef().getQueryAsForm() - .getFirstValue("item"); - - try { - assertEquals(item, Integer.toString(response.getAge())); - if (responseEntityExpected(request.getMethod())) { - assertEquals(Status.SUCCESS_OK, response.getStatus()); - assertTrue(response.isEntityAvailable()); - assertNotNull(response.getEntityAsText()); - } else { - assertEquals(Status.SUCCESS_NO_CONTENT, - response.getStatus()); - assertFalse(response.isEntityAvailable()); - } - } finally { - latch.countDown(); - } - }; + final Uniform responseHandler = + (request, response) -> { + String item = request.getResourceRef().getQueryAsForm().getFirstValue("item"); + + try { + assertEquals(item, Integer.toString(response.getAge())); + if (responseEntityExpected(request.getMethod())) { + assertEquals(Status.SUCCESS_OK, response.getStatus()); + assertTrue(response.isEntityAvailable()); + assertNotNull(response.getEntityAsText()); + } else { + assertEquals(Status.SUCCESS_NO_CONTENT, response.getStatus()); + assertFalse(response.isEntityAvailable()); + } + } finally { + latch.countDown(); + } + }; Restlet client = context.getClientDispatcher(); for (int i = 0; i < count; ++i) { @@ -108,43 +105,51 @@ protected void setUpEach() throws Exception { originComponent = new Component(); // Create a new Restlet that will display some path information. - final Restlet trace = new Restlet(originComponent.getContext() - .createChildContext()) { - @Override - public void handle(Request request, Response response) { - // let's set the item number as age ;-) - response.setAge(Integer.parseInt(request.getResourceRef() - .getQueryAsForm().getFirstValue("item"))); - - if (responseEntityExpected(request.getMethod())) { - // Print the requested URI path - String message = "Resource URI: " - + request.getResourceRef() + '\n' - + "Base URI: " - + request.getResourceRef().getBaseRef() + '\n' - + "Remaining part: " - + request.getResourceRef().getRemainingPart() - + '\n' + "Method name: " + request.getMethod() - + '\n'; - - if (requestEntityExpected(request.getMethod())) { - message += request.getEntityAsText(); - request.getEntity().release(); + final Restlet trace = + new Restlet(originComponent.getContext().createChildContext()) { + @Override + public void handle(Request request, Response response) { + // let's set the item number as age ;-) + response.setAge( + Integer.parseInt( + request.getResourceRef() + .getQueryAsForm() + .getFirstValue("item"))); + + if (responseEntityExpected(request.getMethod())) { + // Print the requested URI path + String message = + "Resource URI: " + + request.getResourceRef() + + '\n' + + "Base URI: " + + request.getResourceRef().getBaseRef() + + '\n' + + "Remaining part: " + + request.getResourceRef().getRemainingPart() + + '\n' + + "Method name: " + + request.getMethod() + + '\n'; + + if (requestEntityExpected(request.getMethod())) { + message += request.getEntityAsText(); + request.getEntity().release(); + } + + response.setEntity( + new StringRepresentation(message, MediaType.TEXT_PLAIN)); + + response.setStatus(Status.SUCCESS_OK); + } else { + // consume entity + if (requestEntityExpected(request.getMethod())) + request.getEntityAsText(); + + response.setStatus(Status.SUCCESS_NO_CONTENT); + } } - - response.setEntity(new StringRepresentation(message, - MediaType.TEXT_PLAIN)); - - response.setStatus(Status.SUCCESS_OK); - } else { - // consume entity - if (requestEntityExpected(request.getMethod())) - request.getEntityAsText(); - - response.setStatus(Status.SUCCESS_NO_CONTENT); - } - } - }; + }; originComponent.getDefaultHost().attach("", trace); @@ -165,22 +170,22 @@ public void handle(Request request, Response response) { } @Test - public void testGet() throws Exception { + void testGet() throws Exception { testCall(context, 10, Method.GET); } @Test - public void testPost() throws Exception { + void testPost() throws Exception { testCall(context, 10, Method.POST); } @Test - public void testPut() throws Exception { + void testPut() throws Exception { testCall(context, 10, Method.PUT); } @Test - public void testDelete() throws Exception { + void testDelete() throws Exception { testCall(context, 10, Method.DELETE); } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/BaseConnectorsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/BaseConnectorsTestCase.java index 9a40c8753b..ac0aefa3ba 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/BaseConnectorsTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/BaseConnectorsTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static org.junit.jupiter.api.DynamicTest.dynamicTest; @@ -14,7 +13,6 @@ import java.util.List; import java.util.logging.Level; import java.util.stream.Stream; - import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import org.restlet.Application; @@ -25,8 +23,8 @@ import org.restlet.engine.adapter.HttpServerHelper; /** - * Base test case that will call an abstract method for several client/server - * connectors configurations. + * Base test case that will call an abstract method for several client/server connectors + * configurations. * * @author Kevin Conaway * @author Jerome Louvel @@ -64,19 +62,20 @@ protected void configureServer(final Server server) { protected abstract Application createApplication(); protected List listTestCases() { - return List.of( - new ConnectorsPair(HttpServer.JETTY_HTTP, HttpClient.JETTY)); + return List.of(new ConnectorsPair(HttpServer.JETTY_HTTP, HttpClient.JETTY)); } @TestFactory Stream testsFactory() { - return listTestCases().stream().map(testCase -> dynamicTest( - testCase.getTestLabel(), - () -> runTest(testCase.httpServer, testCase.httpClient))); + return listTestCases().stream() + .map( + testCase -> + dynamicTest( + testCase.getTestLabel(), + () -> runTest(testCase.httpServer, testCase.httpClient))); } - private void runTest(final HttpServer server, final HttpClient client) - throws Exception { + private void runTest(final HttpServer server, final HttpClient client) throws Exception { if (shouldDebug()) { System.setProperty("org.eclipse.jetty.LEVEL", "TRACE"); } @@ -147,5 +146,4 @@ public enum HttpClient { this.clientHelper = clientHelper; } } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/ChunkedEncodingPutTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/ChunkedEncodingPutTestCase.java index 6300d90c83..04d78ff715 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/ChunkedEncodingPutTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/ChunkedEncodingPutTestCase.java @@ -1,15 +1,22 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; -import org.restlet.*; +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.data.MediaType; import org.restlet.data.Method; import org.restlet.data.Protocol; @@ -19,15 +26,11 @@ import org.restlet.resource.ServerResource; import org.restlet.routing.Router; -import static java.lang.String.format; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * This tests the ability of the connectors to handle chunked encoding. * - * The test uses each connector to PUT an entity that will be sent chunked and - * also to receive a chunked response. + *

The test uses each connector to PUT an entity that will be sent chunked and also to receive a + * chunked response. */ public class ChunkedEncodingPutTestCase extends BaseConnectorsTestCase { private static final int LOOP_NUMBER = 20; @@ -55,10 +58,7 @@ public Restlet createInboundRoot() { }; } - /** - * Test resource that answers to PUT requests by sending back the received - * entity. - */ + /** Test resource that answers to PUT requests by sending back the received entity. */ public static class PutTestResource extends ServerResource { public PutTestResource() { getVariants().add(new Variant(MediaType.TEXT_PLAIN)); @@ -81,12 +81,26 @@ private void sendPut(int testIndex, final String uri, final int size) throws Exc System.out.println(response.getStatus()); } - assertNotNull(response.getEntity(), format("test #%d - size %d: response's entity is null", testIndex, size)); + assertNotNull( + response.getEntity(), + format("test #%d - size %d: response's entity is null", testIndex, size)); final String responseEntity = response.getEntity().getText(); - assertNotNull(responseEntity, format("test #%d - size %d: response's entity content is null", testIndex, size)); - assertEquals(size, responseEntity.length(), format("test #%d - size %d: length of response's entity is wrong", testIndex, size)); + assertNotNull( + responseEntity, + format( + "test #%d - size %d: response's entity content is null", + testIndex, size)); + assertEquals( + size, + responseEntity.length(), + format( + "test #%d - size %d: length of response's entity is wrong", + testIndex, size)); final String expectedResponseEntity = createChunkedRepresentation(size).getText(); - assertEquals(expectedResponseEntity, responseEntity, format("test #%d - size %d: response's entity is wrong", testIndex, size)); + assertEquals( + expectedResponseEntity, + responseEntity, + format("test #%d - size %d: response's entity is wrong", testIndex, size)); } finally { response.release(); client.stop(); @@ -96,8 +110,7 @@ private void sendPut(int testIndex, final String uri, final int size) throws Exc /** * Returns a StringRepresentation which size depends on the given argument. * - * @param size - * the size of the representation + * @param size the size of the representation * @return A DomRepresentation. */ private Representation createChunkedRepresentation(int size) { @@ -105,5 +118,4 @@ private Representation createChunkedRepresentation(int size) { rep.setSize(Representation.UNKNOWN_SIZE); // force chunked encoding return rep; } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/ChunkedEncodingTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/ChunkedEncodingTestCase.java index 0521750dfb..0bed46cf58 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/ChunkedEncodingTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/ChunkedEncodingTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static java.lang.String.format; @@ -15,7 +14,6 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; - import org.restlet.Application; import org.restlet.Client; import org.restlet.Message; @@ -38,8 +36,8 @@ /** * This tests the ability of the connectors to handle chunked encoding. * - * The test uses each connector to PUT an entity that will be sent chunked and - * also to receive a chunked response. + *

The test uses each connector to PUT an entity that will be sent chunked and also to receive a + * chunked response. */ public class ChunkedEncodingTestCase extends BaseConnectorsTestCase { @@ -73,7 +71,10 @@ private void sendGet(int testIndex, String uri) throws Exception { final Response response = client.handle(request); try { - assertEquals(Status.SUCCESS_OK, response.getStatus(), format("test #%d: response's status is wrong", testIndex)); + assertEquals( + Status.SUCCESS_OK, + response.getStatus(), + format("test #%d: response's status is wrong", testIndex)); assertXML(testIndex, response.getEntity()); } finally { response.release(); @@ -88,13 +89,15 @@ private void sendPut(int testIndex, String uri) throws Exception { try { assertChunkedHeader(response); - assertEquals(Status.SUCCESS_OK, response.getStatus(), format("test #%d: response's status is wrong", testIndex)); + assertEquals( + Status.SUCCESS_OK, + response.getStatus(), + format("test #%d: response's status is wrong", testIndex)); assertXML(testIndex, response.getEntity()); } finally { response.release(); client.stop(); } - } public static class PutTestResource extends ServerResource { @@ -119,27 +122,29 @@ public Representation put(Representation entity) { private void assertXML(int testIndex, Representation entity) { try { - String expected = ""; + String expected = + ""; String text = entity.getText(); - assertEquals(expected, text, format("test #%d: xml representation is wrong", testIndex)); + assertEquals( + expected, text, format("test #%d: xml representation is wrong", testIndex)); } catch (IOException ex) { fail(ex.getMessage()); } } private static void assertChunkedHeader(Message message) { - final Header transferEncoding = message.getHeaders() - .getFirst(HeaderConstants.HEADER_TRANSFER_ENCODING, true); + final Header transferEncoding = + message.getHeaders().getFirst(HeaderConstants.HEADER_TRANSFER_ENCODING, true); assertNotNull(transferEncoding); assertEquals("chunked", transferEncoding.getValue()); } private static Representation createTestXml() { - String xmlRepresentationAsString = ""; + String xmlRepresentationAsString = + ""; Representation rep = new StringRepresentation(xmlRepresentationAsString); rep.setSize(Representation.UNKNOWN_SIZE); // force chunked encoding return rep; } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/ConnectorsPair.java b/org.restlet/src/test/java/org/restlet/engine/connector/ConnectorsPair.java index 0970bb5b2a..1312e79fcf 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/ConnectorsPair.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/ConnectorsPair.java @@ -1,3 +1,11 @@ +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. + */ package org.restlet.engine.connector; import static java.lang.String.format; @@ -6,7 +14,9 @@ public class ConnectorsPair { final BaseConnectorsTestCase.HttpServer httpServer; final BaseConnectorsTestCase.HttpClient httpClient; - public ConnectorsPair(BaseConnectorsTestCase.HttpServer httpServer, BaseConnectorsTestCase.HttpClient httpClient) { + public ConnectorsPair( + BaseConnectorsTestCase.HttpServer httpServer, + BaseConnectorsTestCase.HttpClient httpClient) { this.httpServer = httpServer; this.httpClient = httpClient; } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/GetChunkedTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/GetChunkedTestCase.java index fc12b16097..01d69d4b3e 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/GetChunkedTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/GetChunkedTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static java.lang.String.format; @@ -38,7 +37,7 @@ */ public class GetChunkedTestCase extends BaseConnectorsTestCase { - private static final String text = "" + "a".repeat(1000) + ""; + private static final String TEXT = "" + "a".repeat(1000) + ""; @Override protected void doTest(final int serverPort) throws Exception { @@ -49,9 +48,10 @@ protected void doTest(final int serverPort) throws Exception { final Response response = client.handle(request); try { - assertEquals(Status.SUCCESS_OK, response.getStatus(), response.getStatus().getDescription()); + assertEquals( + Status.SUCCESS_OK, response.getStatus(), response.getStatus().getDescription()); assertChunkedHeader(response); - assertEquals(text, response.getEntity().getText()); + assertEquals(TEXT, response.getEntity().getText()); } finally { response.release(); client.stop(); @@ -78,17 +78,16 @@ public GetChunkedTestResource() { @Override public Representation get(Variant variant) { - final Representation rep = new StringRepresentation( text, MediaType.APPLICATION_XML); + final Representation rep = new StringRepresentation(TEXT, MediaType.APPLICATION_XML); rep.setSize(Representation.UNKNOWN_SIZE); // force chunked encoding return rep; } } private static void assertChunkedHeader(Message message) { - final Header transferEncoding = message.getHeaders() - .getFirst(HeaderConstants.HEADER_TRANSFER_ENCODING, true); + final Header transferEncoding = + message.getHeaders().getFirst(HeaderConstants.HEADER_TRANSFER_ENCODING, true); assertNotNull(transferEncoding); assertEquals("chunked", transferEncoding.getValue()); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/GetQueryParamTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/GetQueryParamTestCase.java index 8de0c66801..7ac98eb6eb 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/GetQueryParamTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/GetQueryParamTestCase.java @@ -1,15 +1,24 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; -import org.restlet.*; +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.data.Form; import org.restlet.data.Method; import org.restlet.data.Protocol; @@ -18,16 +27,9 @@ import org.restlet.resource.ServerResource; import org.restlet.routing.Router; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; - -import static java.lang.String.format; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test that a simple get with query parameters works for all the connectors. - * + * * @author Kevin Conaway */ public class GetQueryParamTestCase extends BaseConnectorsTestCase { @@ -41,7 +43,8 @@ protected void doTest(final int serverPort) throws Exception { final Response response = client.handle(request); try { - assertEquals(Status.SUCCESS_OK, response.getStatus(), response.getStatus().getDescription()); + assertEquals( + Status.SUCCESS_OK, response.getStatus(), response.getStatus().getDescription()); assertEquals("{q1=a, q2=b}", response.getEntity().getText()); } finally { client.stop(); @@ -50,7 +53,7 @@ protected void doTest(final int serverPort) throws Exception { @Override protected Application createApplication() { - return new Application() { + return new Application() { @Override public Restlet createInboundRoot() { final Router router = new Router(getContext()); @@ -61,6 +64,7 @@ public Restlet createInboundRoot() { } public static class GetTestResource extends ServerResource { + @Override @Get public String toString() { Form query = getQuery(); @@ -69,5 +73,4 @@ public String toString() { return sortedMap.toString(); } } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/GetTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/GetTestCase.java index 7ca389199d..1f52712695 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/GetTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/GetTestCase.java @@ -1,15 +1,21 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; -import org.restlet.*; +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.data.Method; import org.restlet.data.Protocol; import org.restlet.data.Status; @@ -17,9 +23,6 @@ import org.restlet.resource.ServerResource; import org.restlet.routing.Router; -import static java.lang.String.format; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test that a simple get works for all the connectors. * @@ -37,9 +40,7 @@ protected void doTest(final int serverPort) throws Exception { try { assertEquals( - Status.SUCCESS_OK, response.getStatus(), - response.getStatus().getDescription() - ); + Status.SUCCESS_OK, response.getStatus(), response.getStatus().getDescription()); assertEquals("Hello world", response.getEntity().getText()); } finally { response.release(); @@ -60,10 +61,10 @@ public Restlet createInboundRoot() { } public static class GetTestResource extends ServerResource { + @Override @Get public String toString() { return "Hello world"; } } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/HttpTransportProtocolsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/HttpTransportProtocolsTestCase.java index c38802a01d..577635477c 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/HttpTransportProtocolsTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/HttpTransportProtocolsTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static java.lang.String.format; @@ -25,7 +24,6 @@ import java.nio.file.Path; import java.util.List; import java.util.stream.Stream; - import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; @@ -47,10 +45,8 @@ import org.restlet.engine.ssl.DefaultSslContextFactory; import org.restlet.util.Series; -/** - * Test the support of HTTP2 and HTTP3 transport protocols. - */ -public class HttpTransportProtocolsTestCase { +/** Test the support of HTTP2 and HTTP3 transport protocols. */ +class HttpTransportProtocolsTestCase { protected static final String KEYSTORE_FILE_NAME = "dummy.p12"; protected static final String KEYSTORE_PASSWORD = "testtest"; @@ -59,8 +55,10 @@ public class HttpTransportProtocolsTestCase { @BeforeAll public static void setup() throws IOException { - Path keystorePath = Files.createTempFile("HttpTransportProtocolsTestCase", KEYSTORE_FILE_NAME); - final InputStream resourceAsStream = SslBaseConnectorsTestCase.class.getResourceAsStream(KEYSTORE_FILE_NAME); + Path keystorePath = + Files.createTempFile("HttpTransportProtocolsTestCase", KEYSTORE_FILE_NAME); + final InputStream resourceAsStream = + SslBaseConnectorsTestCase.class.getResourceAsStream(KEYSTORE_FILE_NAME); assert resourceAsStream != null; Files.copy(resourceAsStream, keystorePath, REPLACE_EXISTING); testKeystoreFile = keystorePath.toFile(); @@ -77,7 +75,8 @@ class HttpServerTestCase extends HttpTransportProtocolTest { public static void setup() { Engine.clearThreadLocalVariables(); Engine nre = Engine.register(false); - nre.getRegisteredServers().add(0, new org.restlet.engine.connector.HttpServerHelper(null)); + nre.getRegisteredServers() + .add(0, new org.restlet.engine.connector.HttpServerHelper(null)); nre.getRegisteredClients().add(0, new HttpClientHelper(null)); } @@ -105,9 +104,15 @@ List expectedProtocols() { } static Stream validTestCases() { - return Stream.of(Arguments.of(null, newJdkHttpClient(HttpClient.Version.HTTP_1_1)), // server default to - // HTTP1_1 - Arguments.of(null, newJdkHttpClient(HttpClient.Version.HTTP_2)), // server default to HTTP1_1 + return Stream.of( + Arguments.of( + null, + newJdkHttpClient(HttpClient.Version.HTTP_1_1)), // server default to + // HTTP1_1 + Arguments.of( + null, + newJdkHttpClient( + HttpClient.Version.HTTP_2)), // server default to HTTP1_1 Arguments.of("HTTP1_1", newJdkHttpClient(HttpClient.Version.HTTP_1_1)), Arguments.of("HTTP1_1", newJdkHttpClient(HttpClient.Version.HTTP_2)), Arguments.of("HTTP2", newJdkHttpClient(HttpClient.Version.HTTP_1_1)), @@ -122,7 +127,8 @@ static Stream validTestCases() { } static Stream invalidTestCases() { - return Stream.of(Arguments.of(null, newJettyHttpClient("HTTP2")), // server default to HTTP1_1 + return Stream.of( + Arguments.of(null, newJettyHttpClient("HTTP2")), // server default to HTTP1_1 Arguments.of("HTTP1_1", newJettyHttpClient("HTTP2"))); } } @@ -134,9 +140,9 @@ class HttpsServerTestCase extends HttpTransportProtocolTest { public static void setup() { Engine.clearThreadLocalVariables(); Engine nre = Engine.register(false); - nre.getRegisteredServers().add(0, new org.restlet.engine.connector.HttpsServerHelper(null)); + nre.getRegisteredServers() + .add(0, new org.restlet.engine.connector.HttpsServerHelper(null)); nre.getRegisteredClients().add(0, new HttpClientHelper(null)); - } @AfterAll @@ -152,9 +158,15 @@ List expectedProtocols() { } static Stream validTestCases() { - return Stream.of(Arguments.of(null, newJdkHttpsClient(HttpClient.Version.HTTP_1_1)), // server default to - // HTTP1_1 - Arguments.of(null, newJdkHttpsClient(HttpClient.Version.HTTP_2)), // server default to HTTP1_1 + return Stream.of( + Arguments.of( + null, + newJdkHttpsClient(HttpClient.Version.HTTP_1_1)), // server default to + // HTTP1_1 + Arguments.of( + null, + newJdkHttpsClient( + HttpClient.Version.HTTP_2)), // server default to HTTP1_1 Arguments.of("HTTP1_1", newJdkHttpsClient(HttpClient.Version.HTTP_1_1)), Arguments.of("HTTP1_1", newJdkHttpsClient(HttpClient.Version.HTTP_2)), Arguments.of("HTTP2", newJdkHttpsClient(HttpClient.Version.HTTP_2)), @@ -176,7 +188,8 @@ static Stream validTestCases() { } static Stream invalidTestCases() { - return Stream.of(Arguments.of("HTTP2", newJdkHttpsClient(HttpClient.Version.HTTP_1_1)), + return Stream.of( + Arguments.of("HTTP2", newJdkHttpsClient(HttpClient.Version.HTTP_1_1)), Arguments.of(null, newJettyHttpsClient("HTTP2")), // server default to http1 Arguments.of("HTTP1_1", newJettyHttpsClient("HTTP2")), Arguments.of("HTTP2", newJettyHttpsClient("HTTP1_1")), @@ -214,7 +227,8 @@ abstract static class HttpTransportProtocolTest { @ParameterizedTest(name = "server: {0} / client: {1}") @MethodSource("validTestCases") - public void clientCompliesWithServer(final String httpTransportProtocol, final TestHttpClient testHttpClient) + void clientCompliesWithServer( + final String httpTransportProtocol, final TestHttpClient testHttpClient) throws Exception { final Server server = newServer(httpTransportProtocol); server.start(); @@ -224,36 +238,40 @@ public void clientCompliesWithServer(final String httpTransportProtocol, final T @ParameterizedTest(name = "server: {0} / client: {1}") @MethodSource("invalidTestCases") - public void clientDoesNotComplyWithServer(final String httpTransportProtocol, - final TestHttpClient testHttpClient) throws Exception { + void clientDoesNotComplyWithServer( + final String httpTransportProtocol, final TestHttpClient testHttpClient) + throws Exception { final Server server = newServer(httpTransportProtocol); server.start(); - RuntimeException runtimeException = assertThrows(RuntimeException.class, - () -> testHttpClient.sendRequest(server.getActualPort())); + final int actualPort = server.getActualPort(); + RuntimeException runtimeException = + assertThrows( + RuntimeException.class, () -> testHttpClient.sendRequest(actualPort)); assertEquals("Error while sending request", runtimeException.getMessage()); } @ParameterizedTest(name = "server: {0}") - @ValueSource(strings = { - "", "invalid", "http3" }) - public void invalidServerConfiguration(final String httpTransportProtocol) { + @ValueSource(strings = {"", "invalid", "http3"}) + void invalidServerConfiguration(final String httpTransportProtocol) { final Server server = newServer(httpTransportProtocol); final Exception exception = assertThrows(IllegalArgumentException.class, server::start); assertEquals( - format("'%s' is not one of the supported values: %s", httpTransportProtocol, expectedProtocols()), + format( + "'%s' is not one of the supported values: %s", + httpTransportProtocol, expectedProtocols()), exception.getMessage()); } - } - private static final Restlet HELLO_WORLD_RESTLET = new Restlet() { - @Override - public void handle(Request request, Response response) { - response.setEntity("hello, world", MediaType.TEXT_PLAIN); - } - }; + private static final Restlet HELLO_WORLD_RESTLET = + new Restlet() { + @Override + public void handle(Request request, Response response) { + response.setEntity("hello, world", MediaType.TEXT_PLAIN); + } + }; interface TestHttpClient { String sendRequest(final int port) throws Exception; @@ -295,8 +313,11 @@ private JdkTestHttpClient(final Protocol protocol, final HttpClient.Version http sslContextFactory.setTrustStorePath(testKeystoreFile.getPath()); sslContextFactory.setTrustStorePassword(KEYSTORE_PASSWORD); sslContextFactory.setTrustStoreType(KEYSTORE_TYPE); - httpClient = HttpClient.newBuilder().sslContext(sslContextFactory.createSslContext()) - .version(httpVersion).build(); + httpClient = + HttpClient.newBuilder() + .sslContext(sslContextFactory.createSslContext()) + .version(httpVersion) + .build(); } catch (Exception e) { throw new RuntimeException(e); } @@ -305,8 +326,10 @@ private JdkTestHttpClient(final Protocol protocol, final HttpClient.Version http @Override public String sendRequest(int port) { - final HttpRequest requestGet = HttpRequest.newBuilder() - .uri(URI.create(protocol.getSchemeName() + "://localhost:" + port)).build(); + final HttpRequest requestGet = + HttpRequest.newBuilder() + .uri(URI.create(protocol.getSchemeName() + "://localhost:" + port)) + .build(); try { return httpClient.send(requestGet, HttpResponse.BodyHandlers.ofString()).body(); } catch (Exception e) { @@ -332,10 +355,14 @@ private JettyTestHttpClient(final Protocol protocol, final String httpClientTran @Override public String sendRequest(int port) { - final Client client = new Client(newClientContext(), List.of(protocol), - HttpClientHelper.class.getCanonicalName()); - - final Request request = new Request(Method.GET, protocol.getSchemeName() + "://localhost:" + port); + final Client client = + new Client( + newClientContext(), + List.of(protocol), + HttpClientHelper.class.getCanonicalName()); + + final Request request = + new Request(Method.GET, protocol.getSchemeName() + "://localhost:" + port); final Response response = client.handle(request); if (response.getStatus().isError()) { @@ -348,7 +375,6 @@ public String sendRequest(int port) { protected Context newClientContext() { Context context = new Context(); context.getParameters().add("httpClientTransportMode", httpClientTransportMode); - // context.getParameters().add("http3PemWorkDir", ); if (Protocol.HTTPS.equals(protocol)) { context.getParameters().add("truststorePath", testKeystoreFile.getPath()); diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/Lock.java b/org.restlet/src/test/java/org/restlet/engine/connector/Lock.java index 9ae6e934f3..8be610e371 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/Lock.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/Lock.java @@ -1,12 +1,20 @@ +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. + */ package org.restlet.engine.connector; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + import java.time.Duration; import java.time.Instant; import java.util.concurrent.CountDownLatch; import java.util.logging.Logger; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - public class Lock { private static final Logger LOGGER = Logger.getLogger("Lock"); @@ -34,5 +42,4 @@ public boolean awaitForUnlockingFor(final Duration waitTime) throws InterruptedE private static void log(final String message) { LOGGER.fine(Instant.now().toString() + " " + message); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/MultiPartRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/MultiPartRepresentationTestCase.java index c2c6a679ae..cb6319748c 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/MultiPartRepresentationTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/MultiPartRepresentationTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -16,7 +15,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; - import org.eclipse.jetty.client.StringRequestContent; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.MultiPart; @@ -28,30 +26,30 @@ /** * Test case for the {@link MultiPartRepresentation} class in multipart mode. - * + * * @author Jerome Louvel */ -public class MultiPartRepresentationTestCase { +class MultiPartRepresentationTestCase { @Test - public void testWriteFromParts() throws IOException { + void testWriteFromParts() throws IOException { Path textFilePath = Files.createTempFile("multiPart", ""); - Files.write(textFilePath, "this is the content of the file" - .getBytes(StandardCharsets.UTF_8)); - MultiPart.PathPart filePart = new MultiPart.PathPart("icon", "text.txt", - HttpFields.EMPTY, textFilePath); + Files.write( + textFilePath, "this is the content of the file".getBytes(StandardCharsets.UTF_8)); + MultiPart.PathPart filePart = + new MultiPart.PathPart("icon", "text.txt", HttpFields.EMPTY, textFilePath); - MultiPart.ContentSourcePart contentSourcePart = new MultiPart.ContentSourcePart( - "field", null, HttpFields.EMPTY, - new StringRequestContent("foo")); + MultiPart.ContentSourcePart contentSourcePart = + new MultiPart.ContentSourcePart( + "field", null, HttpFields.EMPTY, new StringRequestContent("foo")); final String boundary = "-----------------------------1294919323195"; - MultiPartRepresentation rep = new MultiPartRepresentation( - contentSourcePart, filePart); + MultiPartRepresentation rep = new MultiPartRepresentation(contentSourcePart, filePart); rep.setBoundary(boundary); - final String expected = """ + final String expected = + """ --%s\r Content-Disposition: form-data; name="field"\r \r @@ -62,29 +60,28 @@ public void testWriteFromParts() throws IOException { this is the content of the file\r --%s--\r """ - .replace("%s", boundary); + .replace("%s", boundary); assertEquals(expected, rep.getText()); } - /** - * Tests the multipart content-type. - */ + /** Tests the multipart content-type. */ @Test - public void testContentType() { - MultiPart.ContentSourcePart contentSourcePart = new MultiPart.ContentSourcePart( - "field", null, HttpFields.EMPTY, - new StringRequestContent("foo")); - MultiPartRepresentation rep = new MultiPartRepresentation( - "myInitialBoundary", contentSourcePart); + void testContentType() { + MultiPart.ContentSourcePart contentSourcePart = + new MultiPart.ContentSourcePart( + "field", null, HttpFields.EMPTY, new StringRequestContent("foo")); + MultiPartRepresentation rep = + new MultiPartRepresentation("myInitialBoundary", contentSourcePart); rep.setBoundary("myActualBoundary"); - assertEquals("multipart/form-data; boundary=myActualBoundary", - rep.getMediaType().toString()); + assertEquals( + "multipart/form-data; boundary=myActualBoundary", rep.getMediaType().toString()); } @Test - public void testParseIntoParts() throws IOException { + void testParseIntoParts() throws IOException { final String boundary = "-----------------------------1294919323195"; - final String multipartEntityContent = """ + final String multipartEntityContent = + """ --%s\r Content-Disposition: form-data; name="field"\r \r @@ -95,18 +92,15 @@ public void testParseIntoParts() throws IOException { this is the content of the file\r --%s--\r """ - .replace("%s", boundary); + .replace("%s", boundary); - StringRepresentation multipartEntity = new StringRepresentation( - multipartEntityContent); + StringRepresentation multipartEntity = new StringRepresentation(multipartEntityContent); multipartEntity.setMediaType( MediaType.valueOf("multipart/form-data; boundary=" + boundary)); - Path tempDir = Files - .createTempDirectory("multipartRepresentationTestCase"); - MultiPartRepresentation rep = new MultiPartRepresentation( - multipartEntity, tempDir); + Path tempDir = Files.createTempDirectory("multipartRepresentationTestCase"); + MultiPartRepresentation rep = new MultiPartRepresentation(multipartEntity, tempDir); - Part part1 = rep.getParts().get(0); + Part part1 = rep.getParts().getFirst(); assertEquals("field", part1.getName()); assertNull(part1.getFileName()); assertEquals(3, part1.getLength()); @@ -116,9 +110,6 @@ public void testParseIntoParts() throws IOException { assertEquals("icon", part2.getName()); assertEquals("text.txt", part2.getFileName()); assertEquals(31, part2.getLength()); - assertEquals("this is the content of the file", - part2.getContentAsString(null)); - + assertEquals("this is the content of the file", part2.getContentAsString(null)); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/PostPutTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/PostPutTestCase.java index b75f5d3ae2..403f0184f3 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/PostPutTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/PostPutTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static java.lang.String.format; @@ -25,7 +24,7 @@ /** * Unit tests for POST and PUT requests. - * + * * @author Jerome Louvel */ public class PostPutTestCase extends BaseConnectorsTestCase { @@ -81,5 +80,4 @@ private void testCall(Client client, Method method, String uri) { response.release(); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/RemoteClientAddressTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/RemoteClientAddressTestCase.java index 670e8c5755..fb43597c6f 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/RemoteClientAddressTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/RemoteClientAddressTestCase.java @@ -1,15 +1,26 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; -import org.restlet.*; +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.data.MediaType; import org.restlet.data.Method; import org.restlet.data.Protocol; @@ -20,18 +31,9 @@ import org.restlet.resource.ServerResource; import org.restlet.routing.Router; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.Enumeration; - -import static java.lang.String.format; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - /** * Test that the client address is available for all the connectors - * + * * @author Kevin Conaway */ public class RemoteClientAddressTestCase extends BaseConnectorsTestCase { @@ -75,14 +77,16 @@ public Representation get(Variant variant) { boolean localAddress = false; try { - Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + Enumeration networkInterfaces = + NetworkInterface.getNetworkInterfaces(); while (networkInterfaces.hasMoreElements()) { NetworkInterface networkInterface = networkInterfaces.nextElement(); Enumeration inetAddresses = networkInterface.getInetAddresses(); while (inetAddresses.hasMoreElements()) { final InetAddress inetAddress = inetAddresses.nextElement(); - if (inetAddress.getHostAddress().equals( - getRequest().getClientInfo().getAddress())) { + if (inetAddress + .getHostAddress() + .equals(getRequest().getClientInfo().getAddress())) { localAddress = true; } } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/ServerMaxConnectionsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/ServerMaxConnectionsTestCase.java index f31ddbf75a..d71857fabf 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/ServerMaxConnectionsTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/ServerMaxConnectionsTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static java.lang.String.format; @@ -20,7 +19,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.logging.Logger; - import org.restlet.Application; import org.restlet.Client; import org.restlet.Context; @@ -34,14 +32,13 @@ import org.restlet.data.Status; import org.restlet.util.Series; -/** - * This tests the ability of the server to accept a fixed number of incoming connections. - */ +/** This tests the ability of the server to accept a fixed number of incoming connections. */ public class ServerMaxConnectionsTestCase extends BaseConnectorsTestCase { - private static final Logger LOGGER = Logger.getLogger(ServerMaxConnectionsTestCase.class.getCanonicalName()); - private final static int CONNECTIONS_NUMBER = 1; - private final static int CONCURRENT_REQUESTS = 2; - private final static Duration SERVER_RESOURCE_FREEZE_DURATION = Duration.ofMillis(100); + private static final Logger LOGGER = + Logger.getLogger(ServerMaxConnectionsTestCase.class.getCanonicalName()); + private static final int CONNECTIONS_NUMBER = 1; + private static final int CONCURRENT_REQUESTS = 2; + private static final Duration SERVER_RESOURCE_FREEZE_DURATION = Duration.ofMillis(100); @Override protected void configureServer(final Server server) { @@ -49,7 +46,9 @@ protected void configureServer(final Server server) { final Series parameters = server.getContext().getParameters(); parameters.add("server.maxConnections", Integer.toString(CONNECTIONS_NUMBER)); - parameters.add("connector.acceptors", Integer.toString(CONCURRENT_REQUESTS)); // server can accept all requests + parameters.add( + "connector.acceptors", + Integer.toString(CONCURRENT_REQUESTS)); // server can accept all requests } @Override @@ -61,11 +60,12 @@ protected void doTest(final int serverPort) throws Exception { final List testResults = new ArrayList<>(); final Executor executor = Executors.newFixedThreadPool(CONCURRENT_REQUESTS); - final Runnable runnable = () -> { - testResults.add(sendGet(uri).isSuccess()); - countDownLatch.countDown(); - log("client countDownLatch.countDown done " + Thread.currentThread().getName()); - }; + final Runnable runnable = + () -> { + testResults.add(sendGet(uri).isSuccess()); + countDownLatch.countDown(); + log("client countDownLatch.countDown done " + Thread.currentThread().getName()); + }; for (int i = 0; i < CONCURRENT_REQUESTS; i++) { executor.execute(runnable); @@ -89,7 +89,8 @@ private Status sendGet(final String uri) { log("client send get " + Thread.currentThread().getName()); try { - final Request request = new Request(Method.GET, uri + "/" + Thread.currentThread().getName()); + final Request request = + new Request(Method.GET, uri + "/" + Thread.currentThread().getName()); final Client client = new Client(new Context(), Protocol.HTTP); final Response response = client.handle(request); log("client get sent " + Thread.currentThread().getName()); @@ -125,5 +126,4 @@ public void handle(Request request, Response response) { } }; } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/ShutdownHookTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/ShutdownHookTestCase.java index 4806cf8331..b100942af6 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/ShutdownHookTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/ShutdownHookTestCase.java @@ -1,27 +1,11 @@ /** - * Copyright 2005-2014 Restlet - * - * The contents of this file are subject to the terms of one of the following - * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can - * select the license that you prefer but you may not use this file except in - * compliance with one of these Licenses. - * - * You can obtain a copy of the Apache 2.0 license at - * http://www.opensource.org/licenses/apache-2.0 - * - * You can obtain a copy of the EPL 1.0 license at - * http://www.opensource.org/licenses/eclipse-1.0 - * - * See the Licenses for the specific language governing permissions and - * limitations under the Licenses. - * - * Alternatively, you can obtain a royalty free commercial license with less - * limitations, transferable or non-transferable, directly at - * http://restlet.com/products/restlet-framework - * - * Restlet is a registered trademark of Restlet S.A.S. + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static java.util.Collections.singletonList; @@ -32,9 +16,7 @@ import java.time.Instant; import java.util.logging.Level; import java.util.logging.Logger; - import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.restlet.Context; @@ -48,32 +30,31 @@ import org.restlet.engine.Engine; import org.restlet.resource.ClientResource; -public class ShutdownHookTestCase { +class ShutdownHookTestCase { private static final Logger LOGGER = Logger.getLogger("ShutdownHookTest"); private static boolean shouldDebug = false; @BeforeEach - public void setUp() { + void setUp() { LOGGER.setLevel(Level.FINE); Engine.clearThreadLocalVariables(); Engine.register(true); } @AfterEach - public void tearDown() { + void tearDown() { Engine.clearThreadLocalVariables(); Engine.register(true); } - /** - * Validates that the server stops immediately when no requests are currently handled. - */ + /** Validates that the server stops immediately when no requests are currently handled. */ @Test - public void whenServerIsNotHandlingRequestThenItStopsImmediately() throws Exception { + void whenServerIsNotHandlingRequestThenItStopsImmediately() throws Exception { // Given a server resource that takes 1 min to send a response final Restlet hangingRestlet = newHangingRestlet(Duration.ofMinutes(1)); // Given a server with a 3-seconds graceful shutdown - final Server server = startServerWithGracefulShutdown(Duration.ofSeconds(3), hangingRestlet); + final Server server = + startServerWithGracefulShutdown(Duration.ofSeconds(3), hangingRestlet); // When the server stops final Instant serverAskedToStopInstant = stopServer(server); @@ -85,10 +66,11 @@ public void whenServerIsNotHandlingRequestThenItStopsImmediately() throws Except /** * Validates that hanging requests are aborted immediately when graceful shutdown is OFF. * - * This is done by making a request froze, then stopping the server, and checking that it didn't wait. + *

This is done by making a request froze, then stopping the server, and checking that it + * didn't wait. */ @Test - public void whenServerIsHandlingBlockingRequestThenItStopsImmediately() throws Exception { + void whenServerIsHandlingBlockingRequestThenItStopsImmediately() throws Exception { // Given a server resource that takes 1 min to send a response final Lock lock = new Lock("Server"); final Restlet hangingRestlet = newHangingAndLockedRestlet(Duration.ofMinutes(1), lock); @@ -106,27 +88,33 @@ public void whenServerIsHandlingBlockingRequestThenItStopsImmediately() throws E log("before stopping server while request is pending"); final Instant serverAskedToStopInstant = stopServer(server); log("before client unlock"); - final boolean isClientResourceUnlocked = testClient.lock.awaitForUnlockingFor(Duration.ofSeconds(4)); + final boolean isClientResourceUnlocked = + testClient.lock.awaitForUnlockingFor(Duration.ofSeconds(4)); log("after client unlock"); // Then assertTrue(isResourceUnlocked, "The resource didn't receive the request"); assertTrue(isClientResourceUnlocked, "The client didn't achieve the request"); assertTrue(testClient.cr.getStatus().isError(), "The request should have ended in error"); - assertIntervalBetweenDatesEquals(Duration.ZERO, serverAskedToStopInstant, testClient.stoppedAt); + assertIntervalBetweenDatesEquals( + Duration.ZERO, serverAskedToStopInstant, testClient.stoppedAt); assertIntervalBetweenDatesEquals(Duration.ZERO, serverAskedToStopInstant, Instant.now()); } /** - * Validates that hanging requests are aborted after the server has waited for the timeout when graceful shutdown is ON. + * Validates that hanging requests are aborted after the server has waited for the timeout when + * graceful shutdown is ON. * - * This is done by making a request froze, then stopping the server, and checking that it waited the expected amount of time before shutting down. + *

This is done by making a request froze, then stopping the server, and checking that it + * waited the expected amount of time before shutting down. */ @Test - public void whenServerIsHandlingBlockingRequestThenItGracefullyWaitsFor1SecondBeforeStopping() throws Exception { + void whenServerIsHandlingBlockingRequestThenItGracefullyWaitsFor1SecondBeforeStopping() + throws Exception { // Given a server resource that takes 1 min to send a response final Lock serverLock = new Lock("Server"); - final Restlet hangingRestlet = newHangingAndLockedRestlet(Duration.ofMinutes(1), serverLock); + final Restlet hangingRestlet = + newHangingAndLockedRestlet(Duration.ofMinutes(1), serverLock); // Given a server with a 1-second graceful shutdown final Duration shutdownTimeout = Duration.ofSeconds(1); @@ -137,28 +125,35 @@ public void whenServerIsHandlingBlockingRequestThenItGracefullyWaitsFor1SecondBe new Thread(hangingClient).start(); // When we stop the server while there is a pending request - final boolean isResourceUnlocked = serverLock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); + final boolean isResourceUnlocked = + serverLock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); log("Before ask server to stop"); final Instant serverAskedToStopInstant = stopServer(server); log("After ask server to stop"); - final boolean isClientResourceUnlocked = hangingClient.lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); + final boolean isClientResourceUnlocked = + hangingClient.lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); // Then assertTrue(isResourceUnlocked, "The resource didn't receive the request"); assertTrue(isClientResourceUnlocked, "The client didn't achieved the request"); - assertTrue(hangingClient.cr.getStatus().isError(), "The request should have ended in error"); + assertTrue( + hangingClient.cr.getStatus().isError(), "The request should have ended in error"); - assertIntervalBetweenDatesEquals(shutdownTimeout, serverAskedToStopInstant, hangingClient.stoppedAt); - assertIntervalBetweenDatesEquals(toJettyEffectiveTimeout(shutdownTimeout), serverAskedToStopInstant, Instant.now()); + assertIntervalBetweenDatesEquals( + shutdownTimeout, serverAskedToStopInstant, hangingClient.stoppedAt); + assertIntervalBetweenDatesEquals( + toJettyEffectiveTimeout(shutdownTimeout), serverAskedToStopInstant, Instant.now()); } /** - * Validates that incoming requests are refused after the server is stopping when graceful shutdown is ON. + * Validates that incoming requests are refused after the server is stopping when graceful + * shutdown is ON. * - * This is done by making a request froze, then stopping the server, and checking that a new request is not taken into account. + *

This is done by making a request froze, then stopping the server, and checking that a new + * request is not taken into account. */ @Test - public void whenServerIsHandlingBlockingRequestThenItRefusesNewRequest() throws Exception { + void whenServerIsHandlingBlockingRequestThenItRefusesNewRequest() throws Exception { // Given a server resource that takes 1 min to send a response final Lock lock = new Lock("Server"); final Restlet hangingRestlet = newHangingAndLockedRestlet(Duration.ofMinutes(1), lock); @@ -172,30 +167,43 @@ public void whenServerIsHandlingBlockingRequestThenItRefusesNewRequest() throws new Thread(firstTestClient).start(); // When - final boolean isResourceUnlocked = lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); + final boolean isResourceUnlocked = + lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); final Instant serverAskedToStopInstant = stopServer(server); final TestClient blockedTestClient = new TestClient(server); blockedTestClient.run(); - final boolean isFirstClientResourceUnlocked = firstTestClient.lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); - final boolean isBlockedClientResourceUnlocked = blockedTestClient.lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); + final boolean isFirstClientResourceUnlocked = + firstTestClient.lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); + final boolean isBlockedClientResourceUnlocked = + blockedTestClient.lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); // Then assertTrue(isResourceUnlocked, "The resource didn't receive the request"); assertTrue(isFirstClientResourceUnlocked, "The first client didn't achieved the request"); - assertTrue(isBlockedClientResourceUnlocked, "The \"blocked\" client didn't achieved the request"); - assertTrue(firstTestClient.cr.getStatus().isError(), "The request should have ended in error"); - assertIntervalBetweenDatesEquals(shutdownTimeout, serverAskedToStopInstant, firstTestClient.stoppedAt); - assertTrue(blockedTestClient.cr.getStatus().isConnectorError(), "Any new client is blocked and fails with a connection error"); - assertIntervalBetweenDatesEquals(toJettyEffectiveTimeout(shutdownTimeout), serverAskedToStopInstant, Instant.now()); + assertTrue( + isBlockedClientResourceUnlocked, + "The \"blocked\" client didn't achieved the request"); + assertTrue( + firstTestClient.cr.getStatus().isError(), "The request should have ended in error"); + assertIntervalBetweenDatesEquals( + shutdownTimeout, serverAskedToStopInstant, firstTestClient.stoppedAt); + assertTrue( + blockedTestClient.cr.getStatus().isConnectorError(), + "Any new client is blocked and fails with a connection error"); + assertIntervalBetweenDatesEquals( + toJettyEffectiveTimeout(shutdownTimeout), serverAskedToStopInstant, Instant.now()); } /** - * Validates that hanging requests for a short amount of time are handled before the server has waited for the timeout when graceful shutdown is ON. + * Validates that hanging requests for a short amount of time are handled before the server has + * waited for the timeout when graceful shutdown is ON. * - * This is done by making a request froze for a short amount of time, then stopping the server, and checking that the request has been handled. + *

This is done by making a request froze for a short amount of time, then stopping the + * server, and checking that the request has been handled. */ @Test - public void whenServerIsHandlingLongRequestThenRequestIsHandledCorrectlyBeforeStopping() throws Exception { + void whenServerIsHandlingLongRequestThenRequestIsHandledCorrectlyBeforeStopping() + throws Exception { // Given a server resource that takes 1 sec to send a response final Lock lock = new Lock("Server"); Duration requestHangingTime = Duration.ofSeconds(1); @@ -210,56 +218,80 @@ public void whenServerIsHandlingLongRequestThenRequestIsHandledCorrectlyBeforeSt new Thread(testClient).start(); // When - final boolean isResourceUnlocked = lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); + final boolean isResourceUnlocked = + lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); final Instant serverAskedToStopInstant = stopServer(server); log("Client resource wait lock"); - final boolean isClientResourceUnlocked = testClient.lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); + final boolean isClientResourceUnlocked = + testClient.lock.awaitForUnlockingFor(shutdownTimeout.multipliedBy(2)); log("Client resource unlocked"); // Then assertTrue(isResourceUnlocked, "The resource didn't receive the request"); assertTrue(isClientResourceUnlocked, "The client didn't achieve the request"); assertEquals(Status.SUCCESS_OK, testClient.cr.getStatus()); - assertIntervalBetweenDatesIsLessThan(requestHangingTime, serverAskedToStopInstant, Instant.now()); + assertIntervalBetweenDatesIsLessThan( + requestHangingTime, serverAskedToStopInstant, Instant.now()); assertEquals("hello, world", testClient.responseText); } - private static void assertIntervalBetweenDatesEquals(final Duration expectedDuration, final Instant firstInstant, final Instant secondInstant) { - final Duration dateDifference = Duration.between(firstInstant, secondInstant) - .abs(); + private static void assertIntervalBetweenDatesEquals( + final Duration expectedDuration, + final Instant firstInstant, + final Instant secondInstant) { + final Duration dateDifference = Duration.between(firstInstant, secondInstant).abs(); final Duration tolerance = Duration.ofMillis(200); // let's consider that +/- 200ms is fine - final boolean isDateDifferenceNearlyEqualToExpectedDuration = dateDifference.minus(expectedDuration) - .abs() - .minus(tolerance) - .isNegative(); - assertTrue(isDateDifferenceNearlyEqualToExpectedDuration, String.format("Expected delay: %d second(s) versus %d second(s)\n", expectedDuration.toMillis(), dateDifference.toMillis())); + final boolean isDateDifferenceNearlyEqualToExpectedDuration = + dateDifference.minus(expectedDuration).abs().minus(tolerance).isNegative(); + assertTrue( + isDateDifferenceNearlyEqualToExpectedDuration, + String.format( + "Expected delay: %d second(s) versus %d second(s)%n", + expectedDuration.toMillis(), dateDifference.toMillis())); } - private static void assertIntervalBetweenDatesIsLessThan(final Duration expectedDuration, final Instant firstInstant, final Instant secondInstant) { + private static void assertIntervalBetweenDatesIsLessThan( + final Duration expectedDuration, + final Instant firstInstant, + final Instant secondInstant) { final Duration dateDifference = Duration.between(firstInstant, secondInstant).abs(); final Duration tolerance = Duration.ofMillis(200); // let's consider that +/- 200ms is fine - final boolean dateDifferenceIsLessThanExpectedDuration = dateDifference.minus(expectedDuration) - .abs() - .minus(tolerance) - .isNegative(); - assertTrue(dateDifferenceIsLessThanExpectedDuration, String.format("difference between dates [%d second(s)] is higher than expected: %d second(s)\n", dateDifference.getSeconds(), expectedDuration.getSeconds())); + final boolean dateDifferenceIsLessThanExpectedDuration = + dateDifference.minus(expectedDuration).abs().minus(tolerance).isNegative(); + assertTrue( + dateDifferenceIsLessThanExpectedDuration, + String.format( + "difference between dates [%d second(s)] is higher than expected: %d second(s)%n", + dateDifference.getSeconds(), expectedDuration.getSeconds())); } private Server startServerWithoutGracefulShutdown(final Restlet restlet) throws Exception { return startServer(false, Duration.ZERO, restlet); } - private Server startServerWithGracefulShutdown(final Duration timeout, final Restlet restlet) throws Exception { + private Server startServerWithGracefulShutdown(final Duration timeout, final Restlet restlet) + throws Exception { return startServer(true, timeout, restlet); } - private Server startServer(final boolean graceful, final Duration timeout, final Restlet restlet) throws Exception { + private Server startServer( + final boolean graceful, final Duration timeout, final Restlet restlet) + throws Exception { - Engine.getInstance().getRegisteredServers().add(0, new HttpServerHelper(null)); // Creates a Jetty server helper manually + Engine.getInstance() + .getRegisteredServers() + .addFirst(new HttpServerHelper(null)); // Creates a Jetty server helper manually // 0 port means it will be computed when the server starts - Server server = new Server(new Context(), singletonList(Protocol.HTTP), null, 0, restlet, HttpServerHelper.class.getCanonicalName()); + Server server = + new Server( + new Context(), + singletonList(Protocol.HTTP), + null, + 0, + restlet, + HttpServerHelper.class.getCanonicalName()); if (shouldDebug) { server.getContext().getParameters().add("tracing", "true"); @@ -269,21 +301,26 @@ private Server startServer(final boolean graceful, final Duration timeout, final if (graceful) { // Don't let the lowResource monitor mess with the current test - server.getContext().getParameters().add("lowResource.idleTimeout", Long.toString(timeout.toMillis() * 10)); + server.getContext() + .getParameters() + .add("lowResource.idleTimeout", Long.toString(timeout.toMillis() * 10)); } server.getContext().getParameters().add("shutdown.gracefully", Boolean.toString(graceful)); - server.getContext().getParameters().add("shutdown.timeout", Long.toString(timeout.toMillis())); + server.getContext() + .getParameters() + .add("shutdown.timeout", Long.toString(timeout.toMillis())); server.start(); - log( "Server started on port " + server.getEphemeralPort()); + log("Server started on port " + server.getEphemeralPort()); return server; } /** - * Creates a resource that hangs for a specific amount of time before answering and acknowledges incoming requests - * by unlocking the given lock. + * Creates a resource that hangs for a specific amount of time before answering and acknowledges + * incoming requests by unlocking the given lock. */ - private static Restlet newHangingAndLockedRestlet(final Duration requestHangingTime, final Lock lock) { + private static Restlet newHangingAndLockedRestlet( + final Duration requestHangingTime, final Lock lock) { return new Restlet() { @Override public void handle(final Request request, final Response response) { @@ -293,7 +330,8 @@ public void handle(final Request request, final Response response) { try { Thread.sleep(requestHangingTime.toMillis()); } catch (Exception e) { - // silently stops, especially when Jetty server will abruptly quit after time out + // silently stops, especially when Jetty server will abruptly quit after time + // out LOGGER.log(Level.FINE, "Restlet error", e); } log("Restlet woke up, answering"); @@ -302,9 +340,7 @@ public void handle(final Request request, final Response response) { }; } - /** - * Creates a resource that hangs for a specific amount of time before answering. - */ + /** Creates a resource that hangs for a specific amount of time before answering. */ private static Restlet newHangingRestlet(final Duration requestHangingTime) { return new Restlet() { @Override @@ -312,7 +348,8 @@ public void handle(final Request request, final Response response) { try { Thread.sleep(requestHangingTime.toMillis()); } catch (Exception e) { - // silently stops, especially when Jetty server will abruptly quit after time out + // silently stops, especially when Jetty server will abruptly quit after time + // out LOGGER.log(Level.FINE, "Restlet error", e); } LOGGER.log(Level.FINE, "Restlet woke up, answering"); @@ -343,7 +380,7 @@ public void run() { } finally { stoppedAt = Instant.now(); lock.unlock(); - LOGGER.log(Level.FINE, "Client state: " + cr.getStatus()); + LOGGER.log(Level.FINE, "Client state: {0}", cr.getStatus()); } } } @@ -352,7 +389,9 @@ private synchronized Instant stopServer(final Server server) { log("Server stopping"); Instant serverAskedToStopInstant = Instant.now(); try { - final HttpServerHelper serverHelper = (HttpServerHelper) server.getContext().getAttributes().get("org.restlet.engine.helper"); + final HttpServerHelper serverHelper = + (HttpServerHelper) + server.getContext().getAttributes().get("org.restlet.engine.helper"); serverHelper.getWrappedServer().stop(); log("Server stopped"); } catch (Exception e) { @@ -363,7 +402,8 @@ private synchronized Instant stopServer(final Server server) { } /** - * Returns the effective Jetty timeout since there is an extra half-timeout in the {@link org.eclipse.jetty.util.thread.QueuedThreadPool}. + * Returns the effective Jetty timeout since there is an extra half-timeout in the {@link + * org.eclipse.jetty.util.thread.QueuedThreadPool}. */ private Duration toJettyEffectiveTimeout(final Duration timeout) { return timeout.plusMillis(500); // FIXME: needs improvements @@ -374,7 +414,6 @@ private static void log(final String message) { } private static void log(final String message, final Exception exception) { - LOGGER.log(Level.INFO, Instant.now().toString() + " " + message, exception); + LOGGER.log(Level.INFO, exception, () -> Instant.now().toString() + " " + message); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/SslBaseConnectorsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/SslBaseConnectorsTestCase.java index a369c415e4..ea7f3dfb67 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/SslBaseConnectorsTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/SslBaseConnectorsTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import java.io.File; @@ -16,7 +15,6 @@ import java.io.OutputStream; import java.nio.file.Files; import java.util.List; - import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.restlet.Client; @@ -29,9 +27,9 @@ import org.restlet.util.Series; /** - * Base test case that will call an abstract method for several client/server - * connectors configurations. (Modified for SSL support.) - * + * Base test case that will call an abstract method for several client/server connectors + * configurations. (Modified for SSL support.) + * * @author Kevin Conaway * @author Bruno Harbulot * @author Jerome Louvel @@ -48,13 +46,12 @@ public abstract class SslBaseConnectorsTestCase extends BaseConnectorsTestCase { public static void globalSetUp() throws IOException { Engine.clearThreadLocalVariables(); Engine.register(); - testKeystoreFile = Files - .createTempFile("sslBaseConnectorsTest", KEYSTORE_FILE_NAME) - .toFile(); + testKeystoreFile = + Files.createTempFile("sslBaseConnectorsTest", KEYSTORE_FILE_NAME).toFile(); testKeystoreFile.delete(); - InputStream resourceAsStream = SslBaseConnectorsTestCase.class - .getResourceAsStream(KEYSTORE_FILE_NAME); + InputStream resourceAsStream = + SslBaseConnectorsTestCase.class.getResourceAsStream(KEYSTORE_FILE_NAME); if (resourceAsStream != null) { OutputStream outputStream = new FileOutputStream(testKeystoreFile); IoUtils.copy(resourceAsStream, outputStream); @@ -63,13 +60,11 @@ public static void globalSetUp() throws IOException { } else { throw new RuntimeException("Can't find the key store"); } - } @Override protected List listTestCases() { - return List.of(new ConnectorsPair(HttpServer.JETTY_HTTPS, - HttpClient.JETTY)); + return List.of(new ConnectorsPair(HttpServer.JETTY_HTTPS, HttpClient.JETTY)); } @Override @@ -120,5 +115,4 @@ protected static void tearDown() { Engine.clearThreadLocalVariables(); org.restlet.engine.Engine.register(); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/SslClientContextGetTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/SslClientContextGetTestCase.java index ade864cfd0..2cf2dd42a1 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/SslClientContextGetTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/SslClientContextGetTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static java.lang.String.format; @@ -49,8 +48,8 @@ protected void doTest(final int serverPort) throws Exception { final Request request = new Request(Method.GET, uri); final Response response = client.handle(request); - assertEquals(Status.SUCCESS_OK, response.getStatus(), - response.getStatus().getDescription()); + assertEquals( + Status.SUCCESS_OK, response.getStatus(), response.getStatus().getDescription()); assertEquals("Hello world", response.getEntity().getText()); client.stop(); } @@ -86,9 +85,7 @@ public GetTestResource() { @Override public Representation get(Variant variant) { - return new StringRepresentation("Hello world", - MediaType.TEXT_PLAIN); + return new StringRepresentation("Hello world", MediaType.TEXT_PLAIN); } } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/connector/SslGetTestCase.java b/org.restlet/src/test/java/org/restlet/engine/connector/SslGetTestCase.java index af38cacb29..dd701dcdc5 100644 --- a/org.restlet/src/test/java/org/restlet/engine/connector/SslGetTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/connector/SslGetTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.connector; import static java.lang.String.format; @@ -30,7 +29,7 @@ /** * Test that a simple get using SSL works for all the connectors. - * + * * @author Kevin Conaway * @author Bruno Harbulot */ @@ -45,7 +44,8 @@ protected void doTest(final int serverPort) throws Exception { final Request request = new Request(Method.GET, format("https://localhost:%d", serverPort)); final Response response = client.handle(request); - assertEquals(Status.SUCCESS_OK, response.getStatus(), response.getStatus().getDescription()); + assertEquals( + Status.SUCCESS_OK, response.getStatus(), response.getStatus().getDescription()); assertEquals("Hello world", response.getEntity().getText()); client.stop(); @@ -74,5 +74,4 @@ public Representation get(Variant variant) { return new StringRepresentation("Hello world", MediaType.TEXT_PLAIN); } } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/header/ContentTypeTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/ContentTypeTestCase.java index cc06ff3476..5df054627b 100644 --- a/org.restlet/src/test/java/org/restlet/engine/header/ContentTypeTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/header/ContentTypeTestCase.java @@ -1,35 +1,34 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; + /** * Test {@link ContentType} - * + * * @author Jerome Louvel */ -public class ContentTypeTestCase { +class ContentTypeTestCase { @Test - public void testParsingInvalid() { + void testParsingInvalid() { String h1 = "application/docbook+xml; version='my version 1.0'"; assertThrows(IllegalArgumentException.class, () -> new ContentType(h1)); } @Test - public void testParsing() { + void testParsing() { String h1 = "application/docbook+xml; version=\"my version 1.0\""; String h2 = "application/docbook+xml; version='my%20version%201.0'"; @@ -37,12 +36,11 @@ public void testParsing() { ContentType ct2 = new ContentType(h2); assertEquals(h1, ct1.getMediaType().getName()); - assertEquals("my version 1.0", ct1.getMediaType().getParameters() - .getFirstValue("version")); + assertEquals("my version 1.0", ct1.getMediaType().getParameters().getFirstValue("version")); assertEquals(h2, ct2.getMediaType().getName()); - assertEquals("'my%20version%201.0'", ct2.getMediaType().getParameters() - .getFirstValue("version")); + assertEquals( + "'my%20version%201.0'", + ct2.getMediaType().getParameters().getFirstValue("version")); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/header/CookieReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/CookieReaderTestCase.java index 31a2c0ce7a..3bfbb107a8 100644 --- a/org.restlet/src/test/java/org/restlet/engine/header/CookieReaderTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/header/CookieReaderTestCase.java @@ -1,38 +1,35 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.junit.jupiter.api.Test; -import org.restlet.data.Cookie; -import org.restlet.data.CookieSetting; -import org.restlet.engine.util.DateUtils; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.restlet.data.Cookie; +import org.restlet.data.CookieSetting; +import org.restlet.engine.util.DateUtils; /** * Unit tests for the Cookie related classes. - * + * * @author Jerome Louvel */ -public class CookieReaderTestCase { +class CookieReaderTestCase { /** * Test one cookie header. - * - * @param headerValue - * The cookie header value. + * + * @param headerValue The cookie header value. * @throws IOException */ private void testCookie(String headerValue) throws IOException { @@ -54,9 +51,8 @@ private void testCookie(String headerValue) throws IOException { /** * Test one cookie header. - * - * @param headerValue - * The cookie header value. + * + * @param headerValue The cookie header value. * @throws IOException */ private void testCookieValues(String headerValue) throws IOException { @@ -90,16 +86,14 @@ private void testCookieValues(String headerValue) throws IOException { /** * Test a cookie date value. - * - * @param dateValue - * The cookie date value. + * + * @param dateValue The cookie date value. */ private void testCookieDate(String dateValue) { final Date date = DateUtils.parse(dateValue, DateUtils.FORMAT_RFC_1036); // Rewrite the date - final String newDateValue = DateUtils.format(date, - DateUtils.FORMAT_RFC_1036.get(0)); + final String newDateValue = DateUtils.format(date, DateUtils.FORMAT_RFC_1036.getFirst()); // Compare initial and new headers assertEquals(dateValue, newDateValue); @@ -107,16 +101,12 @@ private void testCookieDate(String dateValue) { /** * Test one set cookie header. - * - * @param headerValue - * The set cookie header value. - * @param compare - * Indicates if the new header should be compared with the old - * one. + * + * @param headerValue The set cookie header value. + * @param compare Indicates if the new header should be compared with the old one. * @throws IOException */ - private void testCookieSetting(String headerValue, boolean compare) - throws IOException { + private void testCookieSetting(String headerValue, boolean compare) throws IOException { CookieSettingReader cr = new CookieSettingReader(headerValue); CookieSetting cookie = cr.readValue(); @@ -125,17 +115,14 @@ private void testCookieSetting(String headerValue, boolean compare) // Compare initial and new headers if (compare) { - boolean result = newHeaderValue.toLowerCase().startsWith( - headerValue.toLowerCase()); + boolean result = newHeaderValue.toLowerCase().startsWith(headerValue.toLowerCase()); assertTrue(result); } } - /** - * Tests the parsing of cookies. - */ + /** Tests the parsing of cookies. */ @Test - public void testParsing() throws IOException { + void testParsing() throws IOException { // Netscape specification testCookie("CUSTOMER=WILE_E_COYOTE"); testCookie("CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001"); @@ -151,38 +138,36 @@ public void testParsing() throws IOException { // RFC 2109 testCookie("$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\""); - testCookie("$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\""); - testCookie("$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; Shipping=\"FedEx\"; $Path=\"/acme\""); - testCookie("$Version=\"1\"; Part_Number=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\""); - + testCookie( + "$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\""); + testCookie( + "$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; Shipping=\"FedEx\"; $Path=\"/acme\""); + testCookie( + "$Version=\"1\"; Part_Number=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\""); + + testCookieSetting("Customer=\"WILE_E_COYOTE\"; Version=\"1\"; Path=\"/acme\"", true); testCookieSetting( - "Customer=\"WILE_E_COYOTE\"; Version=\"1\"; Path=\"/acme\"", - true); + "Part_Number=\"Rocket_Launcher_0001\"; Version=\"1\"; Path=\"/acme\"", true); + testCookieSetting("Shipping=\"FedEx\"; Version=\"1\"; Path=\"/acme\"", true); testCookieSetting( - "Part_Number=\"Rocket_Launcher_0001\"; Version=\"1\"; Path=\"/acme\"", - true); - testCookieSetting("Shipping=\"FedEx\"; Version=\"1\"; Path=\"/acme\"", - true); + "Part_Number=\"Rocket_Launcher_0001\"; Version=\"1\"; Path=\"/acme\"", true); testCookieSetting( - "Part_Number=\"Rocket_Launcher_0001\"; Version=\"1\"; Path=\"/acme\"", - true); - testCookieSetting( - "Part_Number=\"Riding_Rocket_0023\"; Version=\"1\"; Path=\"/acme/ammo\"", - true); + "Part_Number=\"Riding_Rocket_0023\"; Version=\"1\"; Path=\"/acme/ammo\"", true); // Bug #49 testCookieSetting( "RMS_ADMETA_VISITOR_RMS=27756847%3A240105; expires=Thu, 02 Mar 2006 21:09:00 GMT; path=/; domain=.admeta.com", false); - testCookieValues("Cookie 1=One; Cookie 2=Two; Cookie 3=Three; Cookie 4=Four; Cookie 5=\"Five\"; Cookie 6=\"Six\""); + testCookieValues( + "Cookie 1=One; Cookie 2=Two; Cookie 3=Three; Cookie 4=Four; Cookie 5=\"Five\"; Cookie 6=\"Six\""); } @Test - public void testParsingTooLongMaxAgeShouldBeCapedToIntegerMAX_VALUE() throws IOException { - CookieSettingReader cr = new CookieSettingReader( - "RMS_ADMETA_VISITOR_RMS=27756847%3A240105; max-age=31536000000; path=/; domain=.admeta.com"); + void testParsingTooLongMaxAgeShouldBeCapedToIntegerMAX_VALUE() throws IOException { + CookieSettingReader cr = + new CookieSettingReader( + "RMS_ADMETA_VISITOR_RMS=27756847%3A240105; max-age=31536000000; path=/; domain=.admeta.com"); CookieSetting cookie = cr.readValue(); - assertEquals(cookie.getMaxAge(), Integer.MAX_VALUE); + assertEquals(Integer.MAX_VALUE, cookie.getMaxAge()); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/header/DispositionReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/DispositionReaderTestCase.java index 412852cf04..e8d1e7a61e 100644 --- a/org.restlet/src/test/java/org/restlet/engine/header/DispositionReaderTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/header/DispositionReaderTestCase.java @@ -1,63 +1,54 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.junit.jupiter.api.Test; -import org.restlet.data.Disposition; - -import java.io.IOException; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import java.io.IOException; +import org.junit.jupiter.api.Test; +import org.restlet.data.Disposition; + /** * Unit tests for the HTTP calls. - * + * * @author Kevin Conaway */ -public class DispositionReaderTestCase { +class DispositionReaderTestCase { @Test - public void testParseContentDisposition() throws IOException { - Disposition disposition = new DispositionReader( - "attachment; fileName=\"file.txt\"").readValue(); - assertEquals("file.txt", - disposition.getParameters().getFirstValue("fileName")); - - disposition = new DispositionReader("attachment; fileName=file.txt") - .readValue(); - assertEquals("file.txt", - disposition.getParameters().getFirstValue("fileName")); - - disposition = new DispositionReader( - "attachment; filename=\"file with space.txt\"").readValue(); - assertEquals("file with space.txt", disposition.getParameters() - .getFirstValue("filename")); - - disposition = new DispositionReader("attachment; filename=\"\"") - .readValue(); + void testParseContentDisposition() throws IOException { + Disposition disposition = + new DispositionReader("attachment; fileName=\"file.txt\"").readValue(); + assertEquals("file.txt", disposition.getParameters().getFirstValue("fileName")); + + disposition = new DispositionReader("attachment; fileName=file.txt").readValue(); + assertEquals("file.txt", disposition.getParameters().getFirstValue("fileName")); + + disposition = + new DispositionReader("attachment; filename=\"file with space.txt\"").readValue(); + assertEquals("file with space.txt", disposition.getParameters().getFirstValue("filename")); + + disposition = new DispositionReader("attachment; filename=\"\"").readValue(); assertEquals("", disposition.getParameters().getFirstValue("filename")); - disposition = new DispositionReader("attachment; filename=") - .readValue(); + disposition = new DispositionReader("attachment; filename=").readValue(); assertNull(disposition.getParameters().getFirstValue("filename")); disposition = new DispositionReader("attachment; filenam").readValue(); assertNull(disposition.getParameters().getFirstValue("filename")); - disposition = new DispositionReader( - "attachment; modification-date=\"Wed, 11 Nov 09 22:11:12 GMT\"") - .readValue(); - String str = disposition.getParameters().getFirstValue( - "modification-date"); + disposition = + new DispositionReader( + "attachment; modification-date=\"Wed, 11 Nov 09 22:11:12 GMT\"") + .readValue(); + String str = disposition.getParameters().getFirstValue("modification-date"); assertEquals("Wed, 11 Nov 09 22:11:12 GMT", str); - } } diff --git a/org.restlet/src/test/java/org/restlet/engine/header/DispositionWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/DispositionWriterTestCase.java index 986baf3867..059c4a79bb 100644 --- a/org.restlet/src/test/java/org/restlet/engine/header/DispositionWriterTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/header/DispositionWriterTestCase.java @@ -1,51 +1,48 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.junit.jupiter.api.Test; -import org.restlet.data.Disposition; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import org.restlet.data.Disposition; /** * Unit tests for the HTTP calls. - * + * * @author Kevin Conaway */ -public class DispositionWriterTestCase { +class DispositionWriterTestCase { @Test - public void testFormatContentDisposition() { + void testFormatContentDisposition() { Disposition disposition = new Disposition(); assertEquals("", DispositionWriter.write(disposition)); disposition = new Disposition(Disposition.TYPE_ATTACHMENT); assertEquals("attachment", DispositionWriter.write(disposition)); disposition.setFilename(""); - assertEquals("attachment; filename=", - DispositionWriter.write(disposition)); + assertEquals("attachment; filename=", DispositionWriter.write(disposition)); disposition.setFilename("test.txt"); - assertEquals("attachment; filename=test.txt", - DispositionWriter.write(disposition)); + assertEquals("attachment; filename=test.txt", DispositionWriter.write(disposition)); disposition.setFilename("file with space.txt"); - assertEquals("attachment; filename=\"file with space.txt\"", + assertEquals( + "attachment; filename=\"file with space.txt\"", DispositionWriter.write(disposition)); disposition.setType(Disposition.TYPE_INLINE); - assertEquals("inline; filename=\"file with space.txt\"", - DispositionWriter.write(disposition)); + assertEquals( + "inline; filename=\"file with space.txt\"", DispositionWriter.write(disposition)); disposition.getParameters().clear(); Calendar c = new GregorianCalendar(Locale.ENGLISH); @@ -59,8 +56,8 @@ public void testFormatContentDisposition() { c.set(Calendar.MILLISECOND, 13); c.setTimeZone(TimeZone.getTimeZone("GMT")); disposition.setCreationDate(c.getTime()); - assertEquals("inline; creation-date=\"Wed, 11 Nov 09 10:11:12 GMT\"", + assertEquals( + "inline; creation-date=\"Wed, 11 Nov 09 10:11:12 GMT\"", DispositionWriter.write(disposition)); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/header/HeaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/HeaderTestCase.java index c116b96517..0377c383e5 100644 --- a/org.restlet/src/test/java/org/restlet/engine/header/HeaderTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/header/HeaderTestCase.java @@ -1,14 +1,23 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import org.junit.jupiter.api.Test; import org.restlet.data.ClientInfo; import org.restlet.data.Encoding; @@ -16,35 +25,26 @@ import org.restlet.data.MediaType; import org.restlet.engine.util.DateUtils; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - /** * Unit tests for the header. * * @author Jerome Louvel */ -public class HeaderTestCase { - /** - * Test the {@link HeaderReader#addValues(java.util.Collection)} method. - */ +class HeaderTestCase { + /** Test the {@link HeaderReader#addValues(java.util.Collection)} method. */ @Test - public void testAddValues() { + void testAddValues() { List list = new ArrayList<>(); new EncodingReader("gzip,deflate").addValues(list); - assertEquals(list.size(), 2); - assertEquals(list.get(0), Encoding.GZIP); - assertEquals(list.get(1), Encoding.DEFLATE); + assertEquals(2, list.size()); + assertEquals(Encoding.GZIP, list.get(0)); + assertEquals(Encoding.DEFLATE, list.get(1)); list = new ArrayList<>(); new EncodingReader("gzip,identity, deflate").addValues(list); - assertEquals(list.size(), 2); - assertEquals(list.get(0), Encoding.GZIP); - assertEquals(list.get(1), Encoding.DEFLATE); + assertEquals(2, list.size()); + assertEquals(Encoding.GZIP, list.get(0)); + assertEquals(Encoding.DEFLATE, list.get(1)); list = new ArrayList<>(); new EncodingReader("identity").addValues(list); @@ -69,35 +69,31 @@ public void testAddValues() { tr = new TokenReader("bytes,"); l = tr.readValues(); assertTrue(l.contains("bytes")); - assertEquals(l.size(), 1); + assertEquals(1, l.size()); tr = new TokenReader(""); l = tr.readValues(); - assertEquals(l.size(), 1); + assertEquals(1, l.size()); } @Test - public void testInvalidDate() { + void testInvalidDate() { final String headerValue = "-1"; - final Date date = DateUtils.parse(headerValue, - DateUtils.FORMAT_RFC_1123); + final Date date = DateUtils.parse(headerValue, DateUtils.FORMAT_RFC_1123); assertNull(date); - final Date unmodifiableDate = DateUtils.unmodifiable(date); - assertNull(unmodifiableDate); + assertNull(DateUtils.unmodifiable(date)); } - /** - * Tests the parsing. - */ + /** Tests the parsing. */ @Test - public void testParsing() { + void testParsing() { String header1 = "Accept-Encoding,User-Agent"; String header2 = "Accept-Encoding , User-Agent"; final String header3 = "Accept-Encoding,\r\tUser-Agent"; final String header4 = "Accept-Encoding,\r User-Agent"; final String header5 = "Accept-Encoding, \r \t User-Agent"; - String[] values = new String[]{"Accept-Encoding", "User-Agent"}; + String[] values = new String[] {"Accept-Encoding", "User-Agent"}; testValues(header1, values); testValues(header2, values); testValues(header3, values); @@ -106,7 +102,7 @@ public void testParsing() { header1 = "Accept-Encoding, Accept-Language, Accept"; header2 = "Accept-Encoding,Accept-Language,Accept"; - values = new String[]{"Accept-Encoding", "Accept-Language", "Accept"}; + values = new String[] {"Accept-Encoding", "Accept-Language", "Accept"}; testValues(header1, values); testValues(header2, values); @@ -114,62 +110,45 @@ public void testParsing() { header1 = "gzip;q=1.0, identity;q=0.5 , *;q=0"; ClientInfo clientInfo = new ClientInfo(); PreferenceReader.addEncodings(header1, clientInfo); - assertEquals(clientInfo.getAcceptedEncodings().get(0).getMetadata(), - Encoding.GZIP); - assertEquals(clientInfo.getAcceptedEncodings().get(0).getQuality(), - 1.0F); - assertEquals(clientInfo.getAcceptedEncodings().get(1).getMetadata(), - Encoding.IDENTITY); - assertEquals(clientInfo.getAcceptedEncodings().get(1).getQuality(), - 0.5F); - assertEquals(clientInfo.getAcceptedEncodings().get(2).getMetadata(), - Encoding.ALL); - assertEquals(clientInfo.getAcceptedEncodings().get(2).getQuality(), 0F); + assertEquals(Encoding.GZIP, clientInfo.getAcceptedEncodings().get(0).getMetadata()); + assertEquals(1.0F, clientInfo.getAcceptedEncodings().get(0).getQuality()); + assertEquals(Encoding.IDENTITY, clientInfo.getAcceptedEncodings().get(1).getMetadata()); + assertEquals(0.5F, clientInfo.getAcceptedEncodings().get(1).getQuality()); + assertEquals(Encoding.ALL, clientInfo.getAcceptedEncodings().get(2).getMetadata()); + assertEquals(0F, clientInfo.getAcceptedEncodings().get(2).getQuality()); // Test the parsing of a "Accept" header header1 = "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"; clientInfo = new ClientInfo(); PreferenceReader.addMediaTypes(header1, clientInfo); - assertEquals(clientInfo.getAcceptedMediaTypes().get(0).getMetadata(), - MediaType.TEXT_HTML); - assertEquals(clientInfo.getAcceptedMediaTypes().get(0).getQuality(), - 1.0F); - assertEquals(clientInfo.getAcceptedMediaTypes().get(1).getMetadata(), - MediaType.IMAGE_GIF); - assertEquals(clientInfo.getAcceptedMediaTypes().get(1).getQuality(), - 1.0F); - assertEquals(clientInfo.getAcceptedMediaTypes().get(2).getMetadata(), - MediaType.IMAGE_JPEG); - assertEquals(clientInfo.getAcceptedMediaTypes().get(2).getQuality(), - 1.0F); - assertEquals(clientInfo.getAcceptedMediaTypes().get(3).getMetadata(), - new MediaType("*")); - assertEquals(clientInfo.getAcceptedMediaTypes().get(3).getQuality(), - 0.2F); - assertEquals(clientInfo.getAcceptedMediaTypes().get(4).getMetadata(), - MediaType.ALL); - assertEquals(clientInfo.getAcceptedMediaTypes().get(4).getQuality(), - 0.2F); + assertEquals(MediaType.TEXT_HTML, clientInfo.getAcceptedMediaTypes().get(0).getMetadata()); + assertEquals(1.0F, clientInfo.getAcceptedMediaTypes().get(0).getQuality()); + assertEquals(MediaType.IMAGE_GIF, clientInfo.getAcceptedMediaTypes().get(1).getMetadata()); + assertEquals(1.0F, clientInfo.getAcceptedMediaTypes().get(1).getQuality()); + assertEquals(MediaType.IMAGE_JPEG, clientInfo.getAcceptedMediaTypes().get(2).getMetadata()); + assertEquals(1.0F, clientInfo.getAcceptedMediaTypes().get(2).getQuality()); + assertEquals(new MediaType("*"), clientInfo.getAcceptedMediaTypes().get(3).getMetadata()); + assertEquals(0.2F, clientInfo.getAcceptedMediaTypes().get(3).getQuality()); + assertEquals(MediaType.ALL, clientInfo.getAcceptedMediaTypes().get(4).getMetadata()); + assertEquals(0.2F, clientInfo.getAcceptedMediaTypes().get(4).getQuality()); // Test a more complex header - header1 = "text/html, application/vnd.wap.xhtml+xml, " - + "application/xhtml+xml; profile=\"http://www.wapforum.org/xhtml\", " - + "image/gif, image/jpeg, image/pjpeg, audio/amr, */*"; + header1 = + "text/html, application/vnd.wap.xhtml+xml, " + + "application/xhtml+xml; profile=\"http://www.wapforum.org/xhtml\", " + + "image/gif, image/jpeg, image/pjpeg, audio/amr, */*"; clientInfo = new ClientInfo(); PreferenceReader.addMediaTypes(header1, clientInfo); - assertEquals(clientInfo.getAcceptedMediaTypes().get(0).getMetadata(), - MediaType.TEXT_HTML); - assertEquals(clientInfo.getAcceptedMediaTypes().get(0).getQuality(), - 1.0F); + assertEquals( + MediaType.TEXT_HTML, clientInfo.getAcceptedMediaTypes().getFirst().getMetadata()); + assertEquals(1.0F, clientInfo.getAcceptedMediaTypes().getFirst().getQuality()); } /** * Test that the parsing of a header returns the given array of values. * - * @param header - * The header value to parse. - * @param values - * The parsed values. + * @param header The header value to parse. + * @param values The parsed values. */ public void testValues(String header, String[] values) { HeaderReader hr = new HeaderReader<>(header); @@ -184,7 +163,7 @@ public void testValues(String header, String[] values) { } @Test - public void testEmptyValue() throws IOException { + void testEmptyValue() throws IOException { Header result = HeaderReader.readHeader("My-Header: "); assertNotNull(result); assertEquals("My-Header", result.getName()); diff --git a/org.restlet/src/test/java/org/restlet/engine/header/HeaderUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/HeaderUtilsTestCase.java index 65bc8bc0ce..3d37da7596 100644 --- a/org.restlet/src/test/java/org/restlet/engine/header/HeaderUtilsTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/header/HeaderUtilsTestCase.java @@ -1,29 +1,21 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file are subject to the terms of one of the following - * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can - * select the license that you prefer but you may not use this file except in - * compliance with one of these Licenses. - * - * You can obtain a copy of the Apache 2.0 license at - * http://www.opensource.org/licenses/apache-2.0 - * - * You can obtain a copy of the EPL 1.0 license at - * http://www.opensource.org/licenses/eclipse-1.0 - * - * See the Licenses for the specific language governing permissions and - * limitations under the Licenses. - * - * Alternatively, you can obtain a royalty free commercial license with less - * limitations, transferable or non-transferable, directly at - * https://restlet.talend.com/ - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.restlet.data.Digest.ALGORITHM_MD5; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; import org.junit.jupiter.api.Test; import org.restlet.Request; import org.restlet.Response; @@ -31,71 +23,62 @@ import org.restlet.representation.Representation; import org.restlet.util.Series; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class HeaderUtilsTestCase { - - @Test - public void whenHeaderRetryAfterIsDecimalThenParsingIsStillFine() { - // Given a retry_after header that contains a decimal value - Header header = new Header(HeaderConstants.HEADER_RETRY_AFTER, "2.1"); - - // Given a response - Response response = new Response(new Request()); - - // When I copy the retry_after header to the response - HeaderUtils.copyResponseTransportHeaders(new Series<>(Header.class, Collections.singletonList(header)), - response); - - // Then the response contains a valid value for the retry_after header - assertNotNull(response.getRetryAfter()); - } - - @Test - public void whenHeaderRetryAfterIsAlphabeticalThenParsingFailsSilently() { - // Given a retry_after header that contains an alphabetical value - Header header = new Header(HeaderConstants.HEADER_RETRY_AFTER, "2.1a"); - - // Given a response - Response response = new Response(new Request()); - - // When I copy the retry_after header to the response - HeaderUtils.copyResponseTransportHeaders(new Series<>(Header.class, Collections.singletonList(header)), - response); - - // Then the response does not contain a retry_after header - assertNull(response.getRetryAfter()); - } - - @Test - public void testExtracting() { - ArrayList

headers = new ArrayList<>(); - String md5hash = "aaaaaaaaaaaaaaaa"; - // encodes to "YWFhYWFhYWFhYWFhYWFhYQ==", the "==" at the end is padding - String encodedWithPadding = Base64.getEncoder().encodeToString(md5hash.getBytes()); - String encodedNoPadding = encodedWithPadding.substring(0, 22); - - Header header = new Header(HeaderConstants.HEADER_CONTENT_MD5, encodedWithPadding); - headers.add(header); - - // extract Content-MD5 header with padded Base64 encoding, make sure it - // decodes to original hash - Representation rep = HeaderUtils.extractEntityHeaders(headers, null); - assertEquals(rep.getDigest().getAlgorithm(), - org.restlet.data.Digest.ALGORITHM_MD5); - assertEquals(new String(rep.getDigest().getValue()), md5hash); - - // extract header with UNpadded encoding, make sure it also decodes to - // original hash - header.setValue(encodedNoPadding); - rep = HeaderUtils.extractEntityHeaders(headers, null); - assertEquals(rep.getDigest().getAlgorithm(), - org.restlet.data.Digest.ALGORITHM_MD5); - assertEquals(new String(rep.getDigest().getValue()), md5hash); - } +class HeaderUtilsTestCase { + + @Test + void whenHeaderRetryAfterIsDecimalThenParsingIsStillFine() { + // Given a retry_after header that contains a decimal value + Header header = new Header(HeaderConstants.HEADER_RETRY_AFTER, "2.1"); + + // Given a response + Response response = new Response(new Request()); + + // When I copy the retry_after header to the response + HeaderUtils.copyResponseTransportHeaders( + new Series<>(Header.class, Collections.singletonList(header)), response); + + // Then the response contains a valid value for the retry_after header + assertNotNull(response.getRetryAfter()); + } + + @Test + void whenHeaderRetryAfterIsAlphabeticalThenParsingFailsSilently() { + // Given a retry_after header that contains an alphabetical value + Header header = new Header(HeaderConstants.HEADER_RETRY_AFTER, "2.1a"); + + // Given a response + Response response = new Response(new Request()); + + // When I copy the retry_after header to the response + HeaderUtils.copyResponseTransportHeaders( + new Series<>(Header.class, Collections.singletonList(header)), response); + + // Then the response does not contain a retry_after header + assertNull(response.getRetryAfter()); + } + + @Test + void testExtracting() { + ArrayList
headers = new ArrayList<>(); + String md5hash = "aaaaaaaaaaaaaaaa"; + // encodes to "YWFhYWFhYWFhYWFhYWFhYQ==", the "==" at the end is padding + String encodedWithPadding = Base64.getEncoder().encodeToString(md5hash.getBytes()); + String encodedNoPadding = encodedWithPadding.substring(0, 22); + + Header header = new Header(HeaderConstants.HEADER_CONTENT_MD5, encodedWithPadding); + headers.add(header); + + // extract Content-MD5 header with padded Base64 encoding, make sure it + // decodes to the original hash + Representation rep = HeaderUtils.extractEntityHeaders(headers, null); + assertEquals(ALGORITHM_MD5, rep.getDigest().getAlgorithm()); + assertEquals(md5hash, new String(rep.getDigest().getValue())); + + // extract the header with UNpadded encoding, make sure it also decodes to + // the original hash + header.setValue(encodedNoPadding); + rep = HeaderUtils.extractEntityHeaders(headers, null); + assertEquals(ALGORITHM_MD5, rep.getDigest().getAlgorithm()); + assertEquals(md5hash, new String(rep.getDigest().getValue())); + } } diff --git a/org.restlet/src/test/java/org/restlet/engine/header/PreferenceReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/PreferenceReaderTestCase.java index 1adbb80b5b..4e4809393b 100644 --- a/org.restlet/src/test/java/org/restlet/engine/header/PreferenceReaderTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/header/PreferenceReaderTestCase.java @@ -1,38 +1,35 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; -import org.junit.jupiter.api.Test; -import org.restlet.data.MediaType; -import org.restlet.data.Preference; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import org.restlet.data.MediaType; +import org.restlet.data.Preference; /** * Unit tests for the Preference related classes. - * + * * @author Jerome Louvel */ -public class PreferenceReaderTestCase { +class PreferenceReaderTestCase { /** * Tests the parsing of a single preference header. - * - * @param headerValue - * The preference header. + * + * @param headerValue The preference header. */ private void testMediaType(String headerValue, boolean testEquals) { - PreferenceReader pr = new PreferenceReader<>( - PreferenceReader.TYPE_MEDIA_TYPE, headerValue); + PreferenceReader pr = + new PreferenceReader<>(PreferenceReader.TYPE_MEDIA_TYPE, headerValue); List> prefs = new ArrayList<>(); pr.addValues(prefs); @@ -40,8 +37,7 @@ private void testMediaType(String headerValue, boolean testEquals) { String newHeaderValue = PreferenceWriter.write(prefs); // Reread and rewrite the header (prevent formatting issues) - pr = new PreferenceReader<>(PreferenceReader.TYPE_MEDIA_TYPE, - headerValue); + pr = new PreferenceReader<>(PreferenceReader.TYPE_MEDIA_TYPE, headerValue); prefs = new ArrayList<>(); pr.addValues(prefs); String newHeaderValue2 = PreferenceWriter.write(prefs); @@ -52,11 +48,9 @@ private void testMediaType(String headerValue, boolean testEquals) { } } - /** - * Tests the preferences parsing. - */ + /** Tests the preferences parsing. */ @Test - public void testParsing() { + void testParsing() { testMediaType( "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;LEVEL=2;q=0.4;ext1, */*;q=0.5", true); diff --git a/org.restlet/src/test/java/org/restlet/engine/header/RangeReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/RangeReaderTestCase.java index 6c9e8d28c6..8271dcfea5 100644 --- a/org.restlet/src/test/java/org/restlet/engine/header/RangeReaderTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/header/RangeReaderTestCase.java @@ -1,32 +1,32 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.header; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Unit tests for the header. * * @author Thierry Boileau */ -public class RangeReaderTestCase { +class RangeReaderTestCase { @Test - public void testUpdateRangeFirst9Bytes() { + void testUpdateRangeFirst9Bytes() { final String contentRangeHeaderValue = "bytes 1-9/10"; - final Representation representation = new StringRepresentation("0123456789", MediaType.TEXT_PLAIN); + final Representation representation = + new StringRepresentation("0123456789", MediaType.TEXT_PLAIN); RangeReader.update(contentRangeHeaderValue, representation); @@ -37,9 +37,10 @@ public void testUpdateRangeFirst9Bytes() { } @Test - public void testUpdateRange0To100Bytes() { + void testUpdateRange0To100Bytes() { final String contentRangeHeaderValue = "bytes 0-100/10"; - final Representation representation = new StringRepresentation("0123456789", MediaType.TEXT_PLAIN); + final Representation representation = + new StringRepresentation("0123456789", MediaType.TEXT_PLAIN); RangeReader.update(contentRangeHeaderValue, representation); @@ -50,9 +51,10 @@ public void testUpdateRange0To100Bytes() { } @Test - public void testUpdateRange1To9Bytes() { + void testUpdateRange1To9Bytes() { final String contentRangeHeaderValue = "bytes 1-9/*"; - final Representation representation = new StringRepresentation("0123456789", MediaType.TEXT_PLAIN); + final Representation representation = + new StringRepresentation("0123456789", MediaType.TEXT_PLAIN); RangeReader.update(contentRangeHeaderValue, representation); @@ -61,5 +63,4 @@ public void testUpdateRange1To9Bytes() { assertEquals(1, representation.getRange().getIndex()); assertEquals(9, representation.getRange().getSize()); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/io/IoUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/io/IoUtilsTestCase.java index 6e2348c8e1..2c71855d54 100644 --- a/org.restlet/src/test/java/org/restlet/engine/io/IoUtilsTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/io/IoUtilsTestCase.java @@ -1,33 +1,35 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import org.restlet.data.CharacterSet; import org.restlet.data.MediaType; import org.restlet.representation.OutputRepresentation; -import java.io.*; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test case for the ByteUtils class. - * + * * @author Kevin Conaway */ -public class IoUtilsTestCase { +class IoUtilsTestCase { @Test - public void testGetStream() throws IOException { + void testGetStream() throws IOException { StringWriter writer = new StringWriter(); OutputStream out = IoUtils.getStream(writer, CharacterSet.UTF_8); out.write("testé".getBytes(StandardCharsets.UTF_8)); @@ -37,17 +39,17 @@ public void testGetStream() throws IOException { } @Test - public void testPipe() throws IOException { - final byte[] content = new byte[] { 1, 2, 3, -1, -2, -3, 4, 5, 6 }; + void testPipe() throws IOException { + final byte[] content = new byte[] {1, 2, 3, -1, -2, -3, 4, 5, 6}; ByteArrayInputStream bais = new ByteArrayInputStream(content); - OutputRepresentation or = new OutputRepresentation( - MediaType.APPLICATION_OCTET_STREAM) { - @Override - public void write(OutputStream outputStream) throws IOException { - outputStream.write(content); - } - }; + OutputRepresentation or = + new OutputRepresentation(MediaType.APPLICATION_OCTET_STREAM) { + @Override + public void write(OutputStream outputStream) throws IOException { + outputStream.write(content); + } + }; InputStream is = or.getStream(); int result = 0; @@ -58,5 +60,4 @@ public void write(OutputStream outputStream) throws IOException { System.out.println(result); } } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/io/ReaderInputStreamTestCase.java b/org.restlet/src/test/java/org/restlet/engine/io/ReaderInputStreamTestCase.java index 49b0849f54..b88e8e87db 100644 --- a/org.restlet/src/test/java/org/restlet/engine/io/ReaderInputStreamTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/io/ReaderInputStreamTestCase.java @@ -1,35 +1,32 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; -import org.junit.jupiter.api.Test; -import org.restlet.data.CharacterSet; -import org.restlet.representation.InputRepresentation; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import org.restlet.data.CharacterSet; +import org.restlet.representation.InputRepresentation; /** - * Test the conversion from {@link Reader} to {@link InputStream} and the other - * way around. + * Test the conversion from {@link Reader} to {@link InputStream} and the other way around. * * @author Jerome Louvel */ -public class ReaderInputStreamTestCase { +class ReaderInputStreamTestCase { @Test - public void testConversion() throws IOException { + void testConversion() throws IOException { StringBuilder buf = new StringBuilder(); for (int i = 0; i < 5000; i++) { @@ -45,5 +42,4 @@ public void testConversion() throws IOException { assertEquals(s, ir.getText()); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/io/UnclosableInputStreamTestCase.java b/org.restlet/src/test/java/org/restlet/engine/io/UnclosableInputStreamTestCase.java index bedd17530d..738ebfc1c6 100644 --- a/org.restlet/src/test/java/org/restlet/engine/io/UnclosableInputStreamTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/io/UnclosableInputStreamTestCase.java @@ -1,28 +1,26 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.InputStream; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; /** * Unit tests for the HTTP KeepAlive. * * @author Kevin Conaway */ -public class UnclosableInputStreamTestCase { +class UnclosableInputStreamTestCase { static class MockInputStream extends InputStream { boolean closed = false; @@ -39,7 +37,7 @@ public int read() { } @Test - public void testClose() throws IOException { + void testClose() throws IOException { final MockInputStream mock = new MockInputStream(); final InputStream keepAlive = new UnclosableInputStream(mock); diff --git a/org.restlet/src/test/java/org/restlet/engine/io/UnclosableOutputStreamTestCase.java b/org.restlet/src/test/java/org/restlet/engine/io/UnclosableOutputStreamTestCase.java index 3e99c03f49..d38f822597 100644 --- a/org.restlet/src/test/java/org/restlet/engine/io/UnclosableOutputStreamTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/io/UnclosableOutputStreamTestCase.java @@ -1,28 +1,28 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.io; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; - -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; /** * Unit tests for the HTTP KeepAlive. - * + * * @author Kevin Conaway */ -public class UnclosableOutputStreamTestCase { +class UnclosableOutputStreamTestCase { static class MockOutputStream extends OutputStream { boolean closed = false; @@ -34,11 +34,12 @@ public void close() { @Override public void write(int b) { + // Do nothing, only closing is relevant for this test } } @Test - public void testClose() throws IOException { + void testClose() throws IOException { final MockOutputStream stream = new MockOutputStream(); final OutputStream out = new UnclosableOutputStream(stream); out.close(); @@ -49,20 +50,19 @@ public void testClose() throws IOException { } @Test - public void testWrite() throws IOException { + void testWrite() throws IOException { final ByteArrayOutputStream stream = new ByteArrayOutputStream(); final OutputStream out = new UnclosableOutputStream(stream); out.write('a'); assertEquals("a", stream.toString()); - out.write(new byte[] { 'b', 'c' }); + out.write(new byte[] {'b', 'c'}); assertEquals("abc", stream.toString()); - out.write(new byte[] { 'd', 'e', 'f', 'g' }, 0, 2); + out.write(new byte[] {'d', 'e', 'f', 'g'}, 0, 2); assertEquals("abcde", stream.toString()); out.close(); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AbstractAnnotatedServerResource03.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AbstractAnnotatedServerResource03.java index 8916c33028..6a00aa6ead 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AbstractAnnotatedServerResource03.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AbstractAnnotatedServerResource03.java @@ -1,19 +1,18 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import org.restlet.resource.ServerResource; /** * Abstract {@link ServerResource} that implements several annotated interfaces. - * + * * @author Thierry Boileau */ public abstract class AbstractAnnotatedServerResource03 extends ServerResource @@ -26,5 +25,4 @@ public String accept() { public String asText() { return "asText"; } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03.java index 53779b8b24..3a5a84fd9d 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03.java @@ -1,20 +1,17 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; /** - * Annotated interface that extends {@link AnnotatedInterface03_01} and - * {@link AnnotatedInterface03_02}. - * + * Annotated interface that extends {@link AnnotatedInterface03_01} and {@link + * AnnotatedInterface03_02}. + * * @author Thierry Boileau - * */ -public interface AnnotatedInterface03 extends AnnotatedInterface03_01, AnnotatedInterface03_02 { -} +public interface AnnotatedInterface03 extends AnnotatedInterface03_01, AnnotatedInterface03_02 {} diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03_01.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03_01.java index 0e6132535c..e888496f8f 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03_01.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03_01.java @@ -1,25 +1,22 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import org.restlet.resource.Get; /** * Annotated interface that declares a single "Get" method. - * + * * @author Thierry Boileau - * */ public interface AnnotatedInterface03_01 { @Get String asText(); - } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03_02.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03_02.java index ec12c95fbe..4e6d7136a7 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03_02.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedInterface03_02.java @@ -1,25 +1,22 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import org.restlet.resource.Post; /** * Annotated interface that declares a single "Post" method. - * + * * @author Thierry Boileau - * */ public interface AnnotatedInterface03_02 { @Post String accept(); - } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource09TestCase.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource09TestCase.java index 99c4581845..7066f5446b 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource09TestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource09TestCase.java @@ -1,22 +1,22 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.restlet.data.MediaType.*; +import static org.restlet.data.MediaType.APPLICATION_XML; +import static org.restlet.data.MediaType.TEXT_HTML; +import static org.restlet.data.MediaType.TEXT_PLAIN; import java.io.IOException; import java.util.Arrays; import java.util.stream.Stream; - import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.restlet.Application; @@ -34,19 +34,19 @@ * * @author Thierry Boileau */ -public class AnnotatedResource09TestCase extends JettyConnectorTestCase { +class AnnotatedResource09TestCase extends JettyConnectorTestCase { - public static final Method SI = new Method("SI", "What a method!", - "http://restlet.org", true, true); + public static final Method SI = + new Method("SI", "What a method!", "http://restlet.org", true, true); - public static final Method SNI = new Method("SNI", "What a method!", - "http://restlet.org", true, false); + public static final Method SNI = + new Method("SNI", "What a method!", "http://restlet.org", true, false); - public static final Method USI = new Method("USI", "What a method!", - "http://restlet.org", false, true); + public static final Method USI = + new Method("USI", "What a method!", "http://restlet.org", false, true); - public static final Method USNI = new Method("USNI", "What a method!", - "http://restlet.org", false, false); + public static final Method USNI = + new Method("USNI", "What a method!", "http://restlet.org", false, false); protected Application createApplication(final String path) { return new Application() { @@ -59,8 +59,8 @@ public Restlet createInboundRoot() { }; } - private final static String TEXT = "text"; - private final static Form FORM = new Form("name=value"); + private static final String TEXT = "text"; + private static final Form FORM = new Form("name=value"); static Stream methodsProvider() { return Stream.of(SI, SNI, USI, USNI); @@ -68,7 +68,7 @@ static Stream methodsProvider() { @ParameterizedTest @MethodSource("methodsProvider") - public void testCustomMethod(final Method method) throws IOException, ResourceException { + void testCustomMethod(final Method method) throws IOException, ResourceException { final String methodName = method.getName().toLowerCase(); Request request = createRequest(method); @@ -76,7 +76,8 @@ public void testCustomMethod(final Method method) throws IOException, ResourceEx assertTrue(response.getStatus().isSuccess()); releaseResponse(response); - // the annotated method of the ServerResource that generates HTML and does not require an entity is invoked + // the annotated method of the ServerResource that generates HTML and does not require an + // entity is invoked request = createRequest(method); request.getClientInfo().getAcceptedMediaTypes().add(new Preference<>(TEXT_HTML)); response = handle(request); @@ -87,7 +88,8 @@ public void testCustomMethod(final Method method) throws IOException, ResourceEx // Invokes the annotated method of the ServerResource that // - generates XML // - does not require an entity - // - which annotation does not handle input media type (@SIMethod(":xml") is preferred over @SIMethod("xml")) + // - which annotation does not handle input media type (@SIMethod(":xml") is preferred over + // @SIMethod("xml")) request = createRequest(method); request.getClientInfo().getAcceptedMediaTypes().add(new Preference<>(APPLICATION_XML)); response = handle(request); @@ -116,14 +118,17 @@ public void testCustomMethod(final Method method) throws IOException, ResourceEx assertEquals(TEXT_PLAIN, response.getEntity().getMediaType()); assertEquals(methodName + "-txt|text", response.getEntity().getText()); - // Without client's preference, one of the annotated method of the ServerResource that handles a Form is invoked + // Without client's preference, one of the annotated method of the ServerResource that + // handles a Form is invoked // (declarations order in class cannot be guaranteed) request = createRequest(method); request.setEntity(FORM.getWebRepresentation()); response = handle(request); - assertTrue(Arrays.asList(TEXT_PLAIN, TEXT_HTML).contains(response.getEntity().getMediaType())); - assertTrue(Arrays.asList(methodName + "-form:txt", methodName + "-form:html").contains(response.getEntity().getText())); + assertTrue( + Arrays.asList(TEXT_PLAIN, TEXT_HTML).contains(response.getEntity().getMediaType())); + assertTrue( + Arrays.asList(methodName + "-form:txt", methodName + "-form:html") + .contains(response.getEntity().getText())); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource10TestCase.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource10TestCase.java index d359b07b4f..31c2be7686 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource10TestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource10TestCase.java @@ -1,18 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; - import org.junit.jupiter.api.Test; import org.restlet.Application; import org.restlet.Request; @@ -24,12 +22,12 @@ import org.restlet.routing.Router; /** - * Test annotated resource inheriting abstract super class that implements - * several annotated interfaces. + * Test annotated resource inheriting abstract super class that implements several annotated + * interfaces. * * @author Thierry Boileau */ -public class AnnotatedResource10TestCase extends JettyConnectorTestCase { +class AnnotatedResource10TestCase extends JettyConnectorTestCase { protected Application createApplication(final String path) { return new Application() { @@ -49,7 +47,7 @@ public Restlet createInboundRoot() { * @throws ResourceException */ @Test - public void test() throws IOException, ResourceException { + void test() throws IOException, ResourceException { Request request = createRequest(Method.GET); Response response = handle(request); assertEquals(Status.SUCCESS_OK, response.getStatus()); @@ -62,5 +60,4 @@ public void test() throws IOException, ResourceException { assertEquals("accept", response.getEntity().getText()); response.getEntity().release(); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource11TestCase.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource11TestCase.java index de66cc0779..7accc54e4e 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource11TestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/AnnotatedResource11TestCase.java @@ -1,18 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; - import org.junit.jupiter.api.Test; import org.restlet.Application; import org.restlet.Request; @@ -24,12 +22,12 @@ import org.restlet.routing.Router; /** - * Test annotated resource that reimplements of one the annotated method from - * its abstract super class that implements several annotated interfaces. - * + * Test annotated resource that reimplements of one the annotated method from its abstract super + * class that implements several annotated interfaces. + * * @author Thierry Boileau */ -public class AnnotatedResource11TestCase extends JettyConnectorTestCase { +class AnnotatedResource11TestCase extends JettyConnectorTestCase { protected Application createApplication(final String path) { return new Application() { @@ -44,12 +42,12 @@ public Restlet createInboundRoot() { /** * Test annotated methods. - * + * * @throws IOException * @throws ResourceException */ @Test - public void test() throws IOException, ResourceException { + void test() throws IOException, ResourceException { Request request = createRequest(Method.GET); Response response = handle(request); assertEquals(Status.SUCCESS_OK, response.getStatus()); @@ -62,5 +60,4 @@ public void test() throws IOException, ResourceException { assertEquals("accept", response.getEntity().getText()); response.getEntity().release(); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/JettyConnectorTestCase.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/JettyConnectorTestCase.java index d13463aa8b..a703947806 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/JettyConnectorTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/JettyConnectorTestCase.java @@ -1,16 +1,14 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import java.util.logging.Level; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.restlet.Application; @@ -27,9 +25,8 @@ import org.restlet.representation.ObjectRepresentation; /** - * All test cases relying on a client and a server should inherit from this - * class. - * + * All test cases relying on a client and a server should inherit from this class. + * * @author Jerome Louvel */ public abstract class JettyConnectorTestCase { @@ -40,7 +37,7 @@ public abstract class JettyConnectorTestCase { private String uri; - public JettyConnectorTestCase() { + protected JettyConnectorTestCase() { super(); } @@ -117,5 +114,4 @@ protected void tearDownServer() throws Exception { c.stop(); c = null; } - -} \ No newline at end of file +} diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource09.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource09.java index 5907c408d7..b4dae75fff 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource09.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource09.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import org.restlet.data.Form; diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource10.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource10.java index 8057381e01..78e6fd734c 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource10.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource10.java @@ -1,20 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; /** * Resource that inherits annotated methods. - * + * * @author Thierry Boileau - * */ -public class MyResource10 extends AbstractAnnotatedServerResource03 { - -} +public class MyResource10 extends AbstractAnnotatedServerResource03 {} diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource11.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource11.java index bf38331cc4..a225f9983b 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource11.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/MyResource11.java @@ -1,22 +1,19 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import org.restlet.resource.Get; /** - * Resource that precises the media type of one of its inherited annotated - * methods. - * + * Resource that precises the media type of one of its inherited annotated methods. + * * @author Thierry Boileau - * */ public class MyResource11 extends AbstractAnnotatedServerResource03 { @@ -24,5 +21,5 @@ public class MyResource11 extends AbstractAnnotatedServerResource03 { @Override public String asText() { return "asText-txt"; - }; + } } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/SIMethod.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/SIMethod.java index 559ce70201..71fc739a5c 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/SIMethod.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/SIMethod.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import java.lang.annotation.Documented; @@ -14,7 +13,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import org.restlet.engine.connector.Method; @Documented @@ -24,5 +22,4 @@ public @interface SIMethod { String value() default ""; - } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/SNIMethod.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/SNIMethod.java index 61c28765de..7ddabbd8ce 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/SNIMethod.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/SNIMethod.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import java.lang.annotation.Documented; @@ -14,7 +13,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import org.restlet.engine.connector.Method; @Documented @@ -24,5 +22,4 @@ public @interface SNIMethod { String value() default ""; - } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/USIMethod.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/USIMethod.java index e9edf364ef..4ae5939759 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/USIMethod.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/USIMethod.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import java.lang.annotation.Documented; @@ -14,7 +13,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import org.restlet.engine.connector.Method; @Documented @@ -24,5 +22,4 @@ public @interface USIMethod { String value() default ""; - } diff --git a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/USNIMethod.java b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/USNIMethod.java index 68d5fc3f37..56c03c343b 100644 --- a/org.restlet/src/test/java/org/restlet/engine/jetty/resource/USNIMethod.java +++ b/org.restlet/src/test/java/org/restlet/engine/jetty/resource/USNIMethod.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.jetty.resource; import java.lang.annotation.Documented; @@ -14,7 +13,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - import org.restlet.engine.connector.Method; @Documented @@ -24,5 +22,4 @@ public @interface USNIMethod { String value() default ""; - } diff --git a/org.restlet/src/test/java/org/restlet/engine/resource/AnnotationUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/resource/AnnotationUtilsTestCase.java index 694606446d..4fecc573d2 100644 --- a/org.restlet/src/test/java/org/restlet/engine/resource/AnnotationUtilsTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/resource/AnnotationUtilsTestCase.java @@ -1,34 +1,30 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; import org.junit.jupiter.api.Test; import org.restlet.data.Method; import org.restlet.resource.Get; import org.restlet.resource.Put; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - /** * Test case for generic interfaces. * * @author Valdis Rigdon */ -public class AnnotationUtilsTestCase { +class AnnotationUtilsTestCase { - public interface IChild extends IParent { - - } + public interface IChild extends IParent {} public interface IParent { @@ -37,11 +33,10 @@ public interface IParent { @Put void update(T generic); - } @Test - public void testGetAnnotationsWithGenericParameterType() { + void testGetAnnotationsWithGenericParameterType() { List infos = AnnotationUtils.getInstance().getAnnotations(IChild.class); assertEquals(4, infos.size(), "Wrong count: " + infos); boolean found = false; @@ -62,7 +57,7 @@ public void testGetAnnotationsWithGenericParameterType() { } @Test - public void testGetAnnotationsWithGenericReturnType() { + void testGetAnnotationsWithGenericReturnType() { List infos = AnnotationUtils.getInstance().getAnnotations(IChild.class); assertEquals(4, infos.size(), "Wrong count: " + infos); boolean found = false; diff --git a/org.restlet/src/test/java/org/restlet/engine/util/AlphaNumericComparatorTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/AlphaNumericComparatorTestCase.java index 17706c04e2..7c563a418d 100644 --- a/org.restlet/src/test/java/org/restlet/engine/util/AlphaNumericComparatorTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/util/AlphaNumericComparatorTestCase.java @@ -1,33 +1,31 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.restlet.data.Reference; import org.restlet.resource.Directory; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - /** * Test case for the alphanum algorithm used by {@link Directory}. * * @author Davide Angelocola */ -public class AlphaNumericComparatorTestCase { +class AlphaNumericComparatorTestCase { private static List refs(String... uris) { List result = new LinkedList<>(); @@ -39,29 +37,23 @@ private static List refs(String... uris) { return result; } - private static final List unsorted = refs("1", "2", "3", "1.0", "1.1", - "1.1.1", "2.0", "2.2", "2.2.2", "3.0", "3.3"); + private static final List unsorted = + refs("1", "2", "3", "1.0", "1.1", "1.1.1", "2.0", "2.2", "2.2.2", "3.0", "3.3"); - private static final List expected = refs("1", "1.0", "1.1", "1.1.1", - "2", "2.0", "2.2", "2.2.2", "3", "3.0", "3.3"); + private static final List expected = + refs("1", "1.0", "1.1", "1.1.1", "2", "2.0", "2.2", "2.2.2", "3", "3.0", "3.3"); @Test - public void testBug() { + void testBug() { List result = new ArrayList<>(unsorted); result.sort(new AlphaNumericComparator()); assertEquals(expected, result); } @ParameterizedTest - @CsvSource({ - "Intel 5000X,Intel 5500", - "3,66", - "200,66", - "18,2" - }) + @CsvSource({"Intel 5000X,Intel 5500", "3,66", "200,66", "18,2"}) void testFirstIsLessThan(final String first, final String second) { AlphaNumericComparator anc = new AlphaNumericComparator(); assertTrue(anc.compare(first, second) < 0); } - } diff --git a/org.restlet/src/test/java/org/restlet/engine/util/DateUtilsTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/DateUtilsTestCase.java index a4966e7204..21ede9e04d 100644 --- a/org.restlet/src/test/java/org/restlet/engine/util/DateUtilsTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/util/DateUtilsTestCase.java @@ -1,121 +1,92 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; /** * Test {@link DateUtils} - * + * * @author Jerome Louvel */ -public class DateUtilsTestCase { - - private final String DATE_RFC3339_1 = "1985-04-12T23:20:50.52Z"; - - private final String DATE_RFC3339_2 = "1996-12-19T16:39:57-08:00"; - - private final String DATE_RFC3339_3 = "1990-12-31T23:59:60Z"; - - private final String DATE_RFC3339_4 = "1990-12-31T15:59:60-08:00"; - - private final String DATE_RFC3339_5 = "1937-01-01T12:00:27.87+00:20"; - - private final String DATE_ASC_1 = "Fri Apr 12 23:20:50 1985"; - - private final String DATE_RFC1036_1 = "Friday, 12-Apr-85 23:20:50 GMT"; - - private final String DATE_RFC1123_1 = "Fri, 12 Apr 1985 23:20:50 GMT"; - - private final String DATE_RFC822_1 = "Fri, 12 Apr 85 23:20:50 GMT"; - - /** - * Tests for dates in the RFC 822 format. - */ +class DateUtilsTestCase { + + private static final String DATE_RFC3339_1 = "1985-04-12T23:20:50.52Z"; + private static final String DATE_RFC3339_2 = "1996-12-19T16:39:57-08:00"; + private static final String DATE_RFC3339_3 = "1990-12-31T23:59:60Z"; + private static final String DATE_RFC3339_4 = "1990-12-31T15:59:60-08:00"; + private static final String DATE_RFC3339_5 = "1937-01-01T12:00:27.87+00:20"; + private static final String DATE_ASC_1 = "Fri Apr 12 23:20:50 1985"; + private static final String DATE_RFC1036_1 = "Friday, 12-Apr-85 23:20:50 GMT"; + private static final String DATE_RFC1123_1 = "Fri, 12 Apr 1985 23:20:50 GMT"; + private static final String DATE_RFC822_1 = "Fri, 12 Apr 85 23:20:50 GMT"; + + /** Tests for dates in the RFC 822 format. */ @Test - public void testRfc822() { + void testRfc822() { Date date1 = DateUtils.parse(DATE_RFC822_1, DateUtils.FORMAT_RFC_822); - String dateFormat1 = DateUtils.format(date1, - DateUtils.FORMAT_RFC_822.get(0)); + String dateFormat1 = DateUtils.format(date1, DateUtils.FORMAT_RFC_822.get(0)); assertEquals(DATE_RFC822_1, dateFormat1); } - /** - * Tests for dates in the RFC 1123 format. - */ + /** Tests for dates in the RFC 1123 format. */ @Test - public void testRfc1123() { + void testRfc1123() { Date date1 = DateUtils.parse(DATE_RFC1123_1, DateUtils.FORMAT_RFC_1123); - String dateFormat1 = DateUtils.format(date1, - DateUtils.FORMAT_RFC_1123.get(0)); + String dateFormat1 = DateUtils.format(date1, DateUtils.FORMAT_RFC_1123.get(0)); assertEquals(DATE_RFC1123_1, dateFormat1); } - /** - * Tests for dates in the RFC 1036 format. - */ + /** Tests for dates in the RFC 1036 format. */ @Test - public void testRfc1036() { + void testRfc1036() { Date date1 = DateUtils.parse(DATE_RFC1036_1, DateUtils.FORMAT_RFC_1036); - String dateFormat1 = DateUtils.format(date1, - DateUtils.FORMAT_RFC_1036.get(0)); + String dateFormat1 = DateUtils.format(date1, DateUtils.FORMAT_RFC_1036.getFirst()); assertEquals(DATE_RFC1036_1, dateFormat1); } - /** - * Tests for dates in the RFC 3339 format. - */ + /** Tests for dates in the RFC 3339 format. */ @Test - public void testAsc() { + void testAsc() { Date date1 = DateUtils.parse(DATE_ASC_1, DateUtils.FORMAT_ASC_TIME); - String dateFormat1 = DateUtils.format(date1, - DateUtils.FORMAT_ASC_TIME.get(0)); + String dateFormat1 = DateUtils.format(date1, DateUtils.FORMAT_ASC_TIME.getFirst()); assertEquals(DATE_ASC_1, dateFormat1); } - /** - * Tests for dates in the RFC 3339 format. - */ + /** Tests for dates in the RFC 3339 format. */ @Test - public void testRfc3339() { + void testRfc3339() { Date date1 = DateUtils.parse(DATE_RFC3339_1, DateUtils.FORMAT_RFC_3339); Date date2 = DateUtils.parse(DATE_RFC3339_2, DateUtils.FORMAT_RFC_3339); Date date3 = DateUtils.parse(DATE_RFC3339_3, DateUtils.FORMAT_RFC_3339); Date date4 = DateUtils.parse(DATE_RFC3339_4, DateUtils.FORMAT_RFC_3339); Date date5 = DateUtils.parse(DATE_RFC3339_5, DateUtils.FORMAT_RFC_3339); - String dateFormat1 = DateUtils.format(date1, - DateUtils.FORMAT_RFC_3339.get(0)); - String dateFormat2 = DateUtils.format(date2, - DateUtils.FORMAT_RFC_3339.get(0)); - String dateFormat3 = DateUtils.format(date3, - DateUtils.FORMAT_RFC_3339.get(0)); - String dateFormat4 = DateUtils.format(date4, - DateUtils.FORMAT_RFC_3339.get(0)); - String dateFormat5 = DateUtils.format(date5, - DateUtils.FORMAT_RFC_3339.get(0)); + String dateFormat1 = DateUtils.format(date1, DateUtils.FORMAT_RFC_3339.getFirst()); + String dateFormat2 = DateUtils.format(date2, DateUtils.FORMAT_RFC_3339.getFirst()); + String dateFormat3 = DateUtils.format(date3, DateUtils.FORMAT_RFC_3339.getFirst()); + String dateFormat4 = DateUtils.format(date4, DateUtils.FORMAT_RFC_3339.getFirst()); + String dateFormat5 = DateUtils.format(date5, DateUtils.FORMAT_RFC_3339.getFirst()); assertEquals(DATE_RFC3339_1, dateFormat1); assertEquals("1996-12-20T00:39:57Z", dateFormat2); @@ -125,7 +96,7 @@ public void testRfc3339() { } @Test - public void unmodifiableDates() { + void unmodifiableDates() { Date now = new Date(); Calendar yesterdayCal = new GregorianCalendar(); yesterdayCal.add(Calendar.DAY_OF_MONTH, -1); @@ -135,12 +106,10 @@ public void unmodifiableDates() { assertTrue(now.after(yesterday)); assertTrue(now.after(DateUtils.unmodifiable(yesterday))); assertTrue(DateUtils.unmodifiable(now).after(yesterday)); - assertTrue(DateUtils.unmodifiable(now).after( - DateUtils.unmodifiable(yesterday))); + assertTrue(DateUtils.unmodifiable(now).after(DateUtils.unmodifiable(yesterday))); assertTrue(yesterday.before(DateUtils.unmodifiable(now))); - assertTrue(DateUtils.unmodifiable(yesterday).before( - DateUtils.unmodifiable(now))); + assertTrue(DateUtils.unmodifiable(yesterday).before(DateUtils.unmodifiable(now))); assertTrue(DateUtils.unmodifiable(yesterday).before(now)); } } diff --git a/org.restlet/src/test/java/org/restlet/engine/util/FormReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/FormReaderTestCase.java new file mode 100644 index 0000000000..db6a51b2e3 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/engine/util/FormReaderTestCase.java @@ -0,0 +1,208 @@ +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. + */ +package org.restlet.engine.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.restlet.util.Series; + +class FormReaderTestCase { + + @Nested + class ReadParameter { + /** Returns null when no parameter matches the requested name. */ + @Test + void shouldReturnNullWhenNameNotFound() throws IOException { + FormReader reader = new FormReader("foo=bar&baz=qux", '&'); + + Object result = reader.readParameter("missing"); + + assertNull(result); + } + + /** Returns the value string when the name is found exactly once. */ + @Test + void shouldReturnSingleValue() throws IOException { + FormReader reader = new FormReader("color=red&size=large", '&'); + + Object result = reader.readParameter("color"); + + assertEquals("red", result); + } + + /** Returns {@link Series#EMPTY_VALUE} when the parameter has no value (no '=' sign). */ + @Test + void shouldReturnEmptyValueForParameterWithoutValue() throws IOException { + FormReader reader = new FormReader("flag&other=val", '&'); + + Object result = reader.readParameter("flag"); + + assertSame(Series.EMPTY_VALUE, result); + } + + /** Returns a List containing both values when the name appears twice. */ + @Test + void shouldReturnListForDuplicateNames() throws IOException { + FormReader reader = new FormReader("accept=text/html&accept=application/json", '&'); + + Object result = reader.readParameter("accept"); + + assertInstanceOf(List.class, result); + @SuppressWarnings("unchecked") + List list = (List) result; + assertEquals(2, list.size()); + assertEquals("text/html", list.get(0)); + assertEquals("application/json", list.get(1)); + } + + /** A third occurrence is appended to the existing list (not nested). */ + @Test + void shouldReturnListForTriplicateNames() throws IOException { + FormReader reader = new FormReader("x=1&x=2&x=3", '&'); + + Object result = reader.readParameter("x"); + + assertInstanceOf(List.class, result); + @SuppressWarnings("unchecked") + List list = (List) result; + assertEquals(3, list.size()); + assertEquals("1", list.get(0)); + assertEquals("2", list.get(1)); + assertEquals("3", list.get(2)); + } + + /** Null values in a multi-value entry are stored as {@link Series#EMPTY_VALUE}. */ + @Test + void nullValuesInListStoredAsEmptyValue() throws IOException { + FormReader reader = new FormReader("h&h", '&'); + + Object result = reader.readParameter("h"); + + assertInstanceOf(List.class, result); + @SuppressWarnings("unchecked") + List list = (List) result; + assertEquals(2, list.size()); + assertSame(Series.EMPTY_VALUE, list.get(0)); + assertSame(Series.EMPTY_VALUE, list.get(1)); + } + } + + @Nested + class ReadParameters { + /** A parameter whose name is not a key in the map is silently ignored. */ + @Test + void shouldIgnoreUnknownName() throws IOException { + FormReader reader = new FormReader("foo=bar&baz=qux", '&'); + Map params = map("other"); + + reader.readParameters(params); + + assertNull(params.get("other")); + } + + /** A matching parameter stores its value directly (not in a list). */ + @Test + void shouldStoreSingleValue() throws IOException { + FormReader reader = new FormReader("color=red&size=large", '&'); + Map params = map("color"); + + reader.readParameters(params); + + assertEquals("red", params.get("color")); + } + + /** A parameter with no value stores {@link Series#EMPTY_VALUE} as a sentinel. */ + @Test + void shouldStoreEmptyValueWhenNullValue() throws IOException { + FormReader reader = new FormReader("flag&other=val", '&'); + Map params = map("flag"); + + reader.readParameters(params); + + assertSame(Series.EMPTY_VALUE, params.get("flag")); + } + + /** Two parameters with the same name produce a List containing both values. */ + @Test + void shouldProduceListWhenDuplicateNames() throws IOException { + FormReader reader = new FormReader("accept=text/html&accept=application/json", '&'); + Map params = map("accept"); + + reader.readParameters(params); + + assertInstanceOf(List.class, params.get("accept")); + @SuppressWarnings("unchecked") + List list = (List) params.get("accept"); + assertEquals(2, list.size()); + assertEquals("text/html", list.get(0)); + assertEquals("application/json", list.get(1)); + } + + /** Three parameters with the same name all end up in a single growing List. */ + @Test + void shouldStoreListOfThreeWhenTriplicateNames() throws IOException { + FormReader reader = new FormReader("x=1&x=2&x=3", '&'); + Map params = map("x"); + + reader.readParameters(params); + + @SuppressWarnings("unchecked") + List list = (List) params.get("x"); + assertEquals(3, list.size()); + assertEquals("1", list.get(0)); + assertEquals("2", list.get(1)); + assertEquals("3", list.get(2)); + } + + /** Null values in a multi-value list entry are stored as {@link Series#EMPTY_VALUE}. */ + @Test + void shouldStoreEmptyValuesWhenNullValuesInList() throws IOException { + FormReader reader = new FormReader("h&h", '&'); + Map params = map("h"); + + reader.readParameters(params); + + @SuppressWarnings("unchecked") + List list = (List) params.get("h"); + assertEquals(2, list.size()); + assertSame(Series.EMPTY_VALUE, list.get(0)); + assertSame(Series.EMPTY_VALUE, list.get(1)); + } + + /** Only keys already present in the map are populated; unrelated parameters are skipped. */ + @Test + void shouldPopulateOnlyMapKeys() throws IOException { + FormReader reader = new FormReader("a=1&b=2&c=3", '&'); + Map params = map("a", "c"); + + reader.readParameters(params); + + assertEquals("1", params.get("a")); + assertEquals("3", params.get("c")); + assertEquals(2, params.size(), "Key 'b' should not have been inserted"); + } + } + + private static Map map(String... keys) { + Map m = new HashMap<>(); + for (String key : keys) { + m.put(key, null); + } + return m; + } +} diff --git a/org.restlet/src/test/java/org/restlet/engine/util/HtmlEncodingTestCase.java b/org.restlet/src/test/java/org/restlet/engine/util/HtmlEncodingTestCase.java index 7ce91b5e1a..a8ca484f5b 100644 --- a/org.restlet/src/test/java/org/restlet/engine/util/HtmlEncodingTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/util/HtmlEncodingTestCase.java @@ -1,46 +1,46 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.engine.util; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.params.provider.Arguments.arguments; - /** * Unit tests for the HTML encoding/decoding. * * @author Thierry Boileau */ -public class HtmlEncodingTestCase { +class HtmlEncodingTestCase { - private static final String MIXED_ENCODED_STRING = "azertyéè@à&%ù€®®®&&&;&testest"; - private static final String ENCODED_STRING = "<test>azertyéè@à&%ù€®&#174;&reg;</test>&&&;&testest"; + private static final String MIXED_ENCODED_STRING = + "azertyéè@à&%ù€®®®&&&;&testest"; + private static final String ENCODED_STRING = + "<test>azertyéè@à&%ù€®&#174;&reg;</test>&&&;&testest"; private static final String DECODED_STRING = "azertyéè@à&%ù€®®®&&&;&testest"; static Stream htmlEscapeArgumentsProvider() { return Stream.of( arguments("", ""), arguments(null, null), - arguments(MIXED_ENCODED_STRING, ENCODED_STRING) - ); + arguments(MIXED_ENCODED_STRING, ENCODED_STRING)); } @ParameterizedTest @MethodSource("htmlEscapeArgumentsProvider") - public void testEncoding(final String toEscape, final String expected) { - assertEquals(expected, StringUtils.htmlEscape(toEscape), "Error while escaping " + toEscape); + void testEncoding(final String toEscape, final String expected) { + assertEquals( + expected, StringUtils.htmlEscape(toEscape), "Error while escaping " + toEscape); } static Stream htmlUnescapeArgumentsProvider() { @@ -49,14 +49,15 @@ static Stream htmlUnescapeArgumentsProvider() { arguments(null, null), arguments(MIXED_ENCODED_STRING, DECODED_STRING), arguments(ENCODED_STRING, MIXED_ENCODED_STRING), - arguments(DECODED_STRING, DECODED_STRING) - ); + arguments(DECODED_STRING, DECODED_STRING)); } @ParameterizedTest @MethodSource("htmlUnescapeArgumentsProvider") - public void testDecoding(final String toUnescape, final String expected) { - assertEquals(expected, StringUtils.htmlUnescape(toUnescape), "Error while unescaping " + toUnescape); + void testDecoding(final String toUnescape, final String expected) { + assertEquals( + expected, + StringUtils.htmlUnescape(toUnescape), + "Error while unescaping " + toUnescape); } - } diff --git a/org.restlet/src/test/java/org/restlet/representation/AppendableRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/AppendableRepresentationTestCase.java index 9eca7e2d2e..9cd3c750cb 100644 --- a/org.restlet/src/test/java/org/restlet/representation/AppendableRepresentationTestCase.java +++ b/org.restlet/src/test/java/org/restlet/representation/AppendableRepresentationTestCase.java @@ -1,31 +1,29 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + /** * Unit test case for the {@link AppendableRepresentation} class. - * + * * @author Jerome Louvel */ -public class AppendableRepresentationTestCase { +class AppendableRepresentationTestCase { @Test - public void testAppendable() throws Exception { + void testAppendable() throws Exception { AppendableRepresentation ar = new AppendableRepresentation(); ar.append("abcd"); ar.append("efgh"); assertEquals("abcdefgh", ar.getText()); } - } diff --git a/org.restlet/src/test/java/org/restlet/representation/DigesterRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/DigesterRepresentationTestCase.java index a4a67f5268..7503c4d5d1 100644 --- a/org.restlet/src/test/java/org/restlet/representation/DigesterRepresentationTestCase.java +++ b/org.restlet/src/test/java/org/restlet/representation/DigesterRepresentationTestCase.java @@ -1,38 +1,44 @@ /** - * Copyright 2005-2024 Qlik - *

- * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - *

+ * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.restlet.*; +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Component; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.Server; import org.restlet.data.Method; import org.restlet.data.Protocol; import org.restlet.data.Status; import org.restlet.engine.Engine; import org.restlet.routing.Router; -import java.io.IOException; -import java.security.NoSuchAlgorithmException; - -import static org.junit.jupiter.api.Assertions.*; - /** * Test {@link DigesterRepresentation}. * * @author Thierry Boileau */ -public class DigesterRepresentationTestCase { +class DigesterRepresentationTestCase { /** Component used for the tests. */ private Component component; + private int serverPort; @BeforeEach @@ -55,9 +61,10 @@ protected void tearDownEach() throws Exception { } @Test - public void checkDigestSentByClient() { + void checkDigestSentByClient() { Client client = new Client(Protocol.HTTP); - Request request = new Request(Method.PUT, "http://localhost:" + serverPort + "/checkRequestEntity"); + Request request = + new Request(Method.PUT, "http://localhost:" + serverPort + "/checkRequestEntity"); request.setEntity(getDigestedRepresentation("0123456789")); Response response = client.handle(request); @@ -65,41 +72,44 @@ public void checkDigestSentByClient() { } @Test - public void checkDigestSentByServer() { + void checkDigestSentByServer() { Client client = new Client(Protocol.HTTP); - Request request = new Request(Method.GET, "http://localhost:" + serverPort + "/checkResponseEntity"); + Request request = + new Request(Method.GET, "http://localhost:" + serverPort + "/checkResponseEntity"); Response response = client.handle(request); assertEquals(Status.SUCCESS_OK, response.getStatus()); assertTrue(checkRepresentation(response.getEntity())); } - /** - * Internal class used for test purpose. - * - */ + /** Internal class used for test purpose. */ private class TestDigestApplication extends Application { @Override public Restlet createInboundRoot() { Router router = new Router(getContext()); - router.attach("/checkRequestEntity", new Restlet() { - @Override - public void handle(Request request, Response response) { - Status responseStatus = checkRepresentation(request.getEntity()) - ? Status.SUCCESS_OK - : Status.CLIENT_ERROR_BAD_REQUEST; - response.setStatus(responseStatus); - } - }); - - router.attach("/checkResponseEntity", new Restlet() { - @Override - public void handle(Request request, Response response) { - response.setEntity(getDigestedRepresentation("9876543210")); - } - }); + router.attach( + "/checkRequestEntity", + new Restlet() { + @Override + public void handle(Request request, Response response) { + Status responseStatus = + checkRepresentation(request.getEntity()) + ? Status.SUCCESS_OK + : Status.CLIENT_ERROR_BAD_REQUEST; + response.setStatus(responseStatus); + } + }); + + router.attach( + "/checkResponseEntity", + new Restlet() { + @Override + public void handle(Request request, Response response) { + response.setEntity(getDigestedRepresentation("9876543210")); + } + }); return router; } } @@ -117,7 +127,8 @@ private boolean checkRepresentation(final Representation representation) { private DigesterRepresentation getDigestedRepresentation(final String string) { try { - DigesterRepresentation digester = new DigesterRepresentation(new StringRepresentation(string)); + DigesterRepresentation digester = + new DigesterRepresentation(new StringRepresentation(string)); // Consume first digester.exhaust(); // Set the digest @@ -128,5 +139,4 @@ private DigesterRepresentation getDigestedRepresentation(final String string) { throw new RuntimeException(e); } } - } diff --git a/org.restlet/src/test/java/org/restlet/representation/FileRepresentationTestCase.java b/org.restlet/src/test/java/org/restlet/representation/FileRepresentationTestCase.java index 6dbb6d3ca2..7bde190854 100644 --- a/org.restlet/src/test/java/org/restlet/representation/FileRepresentationTestCase.java +++ b/org.restlet/src/test/java/org/restlet/representation/FileRepresentationTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.representation; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -15,7 +14,6 @@ import java.io.File; import java.nio.file.Files; import java.nio.file.Path; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -39,7 +37,7 @@ * * @author Kevin Conaway */ -public class FileRepresentationTestCase { +class FileRepresentationTestCase { private Component component; @@ -69,7 +67,7 @@ protected void tearDownEach() throws Exception { } @Test - public void testConstructors() { + void testConstructors() { final File file = new File("test.txt"); final FileRepresentation r = new FileRepresentation(file, MediaType.TEXT_PLAIN); @@ -80,20 +78,24 @@ public void testConstructors() { } @Test - public void testFileName() throws Exception { - Application application = new Application() { - @Override - public Restlet createInboundRoot() { - return new Restlet() { + void testFileName() throws Exception { + Application application = + new Application() { @Override - public void handle(Request request, Response response) { - response.setEntity(new FileRepresentation(filePath.toFile(), MediaType.TEXT_PLAIN)); - response.getEntity().getDisposition() - .setType(Disposition.TYPE_ATTACHMENT); + public Restlet createInboundRoot() { + return new Restlet() { + @Override + public void handle(Request request, Response response) { + response.setEntity( + new FileRepresentation( + filePath.toFile(), MediaType.TEXT_PLAIN)); + response.getEntity() + .getDisposition() + .setType(Disposition.TYPE_ATTACHMENT); + } + }; } }; - } - }; component.getDefaultHost().attach(application); @@ -107,5 +109,4 @@ public void handle(Request request, Response response) { assertEquals(filePath.toFile().getName(), entity.getDisposition().getFilename()); client.stop(); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceTestCase.java b/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceTestCase.java index 3badf2983b..723ea64625 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceTestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; import org.junit.jupiter.api.AfterEach; @@ -42,5 +41,4 @@ protected void tearDownEach() { clientResource.release(); clientResource = null; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceWithFinderTestCase.java b/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceWithFinderTestCase.java index 98d758bd64..a59471b431 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceWithFinderTestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceWithFinderTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** @@ -14,7 +13,8 @@ * * @author Jerome Louvel */ -public abstract class AbstractAnnotatedResourceWithFinderTestCase extends AbstractAnnotatedResourceTestCase { +public abstract class AbstractAnnotatedResourceWithFinderTestCase + extends AbstractAnnotatedResourceTestCase { @Override protected void configureClientResource(final ClientResource clientResource) { @@ -24,5 +24,4 @@ protected void configureClientResource(final ClientResource clientResource) { } abstract void configureFinder(final Finder finder); - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AbstractGenericAnnotatedServerResource.java b/org.restlet/src/test/java/org/restlet/resource/AbstractGenericAnnotatedServerResource.java index 4035a8512c..08ca81f96f 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AbstractGenericAnnotatedServerResource.java +++ b/org.restlet/src/test/java/org/restlet/resource/AbstractGenericAnnotatedServerResource.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; public abstract class AbstractGenericAnnotatedServerResource extends ServerResource { diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource01TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource01TestCase.java index b039fdc0a9..f865e653cc 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource01TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource01TestCase.java @@ -1,30 +1,30 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.data.Status; import org.restlet.representation.ObjectRepresentation; import org.restlet.representation.StringRepresentation; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.*; - /** * Test the annotated resources, client and server sides. * * @author Jerome Louvel */ -public class AnnotatedResource01TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource01TestCase extends AbstractAnnotatedResourceWithFinderTestCase { private MyResource01 myResource; @@ -40,12 +40,12 @@ void configureFinder(Finder finder) { } @Test - public void testDelete() { + void testDelete() { assertEquals("Done", myResource.remove()); } @Test - public void testGet() throws IOException, ResourceException { + void testGet() throws IOException, ResourceException { MyBean myBean = myResource.represent(); assertNotNull(myBean); assertEquals("myName", myBean.getName()); @@ -53,25 +53,25 @@ public void testGet() throws IOException, ResourceException { ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED = true; String result = clientResource.get(MediaType.APPLICATION_JAVA_OBJECT_XML).getText(); - assertTrue(result - .startsWith("") - && result.contains("") + && result.contains(" + * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.Representation; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * Test the annotated resources, client and server sides. - * + * * @author Jerome Louvel */ -public class AnnotatedResource02TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource02TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -31,11 +29,10 @@ void configureFinder(Finder finder) { } @Test - public void testGet() throws IOException, ResourceException { + void testGet() throws IOException, ResourceException { Representation result = clientResource.get(MediaType.APPLICATION_ATOM); assertNotNull(result); assertEquals("", result.getText()); assertEquals(MediaType.TEXT_XML, result.getMediaType()); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource03TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource03TestCase.java index 5316b8e9b7..8ee49d19c9 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource03TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource03TestCase.java @@ -1,25 +1,24 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.restlet.data.Status; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test the annotated resources, client and server sides. * * @author Jerome Louvel */ -public class AnnotatedResource03TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource03TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -27,7 +26,7 @@ void configureFinder(Finder finder) { } @Test - public void testGet() throws ResourceException { + void testGet() throws ResourceException { Status status = null; try { clientResource.get(); @@ -37,5 +36,4 @@ public void testGet() throws ResourceException { } assertEquals(Status.CLIENT_ERROR_NOT_FOUND, status); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource04TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource04TestCase.java index 417cfa327c..655ed4b61d 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource04TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource04TestCase.java @@ -1,32 +1,30 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.restlet.data.MediaType; import org.restlet.representation.Representation; -import java.io.IOException; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * Test the annotated resources, client and server sides. - * + * * @author Jerome Louvel */ -public class AnnotatedResource04TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource04TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -35,7 +33,8 @@ void configureFinder(Finder finder) { @ParameterizedTest @MethodSource("argumentsProvider") - public void testGet(final MediaType responseMediaType, final String responseBody) throws IOException, ResourceException { + void testGet(final MediaType responseMediaType, final String responseBody) + throws IOException, ResourceException { Representation result = clientResource.get(responseMediaType); assertNotNull(result); assertEquals(responseBody, result.getText()); @@ -46,8 +45,6 @@ static Stream argumentsProvider() { return Stream.of( Arguments.arguments(MediaType.TEXT_HTML, "root"), Arguments.arguments(MediaType.APPLICATION_JSON, "[\"root\"]"), - Arguments.arguments(MediaType.APPLICATION_XML, "") - ); + Arguments.arguments(MediaType.APPLICATION_XML, "")); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource05TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource05TestCase.java index 7e5cc4e9b6..08c1164b3b 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource05TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource05TestCase.java @@ -1,29 +1,27 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.Representation; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * Test the annotated resources, client and server sides. - * + * * @author Jerome Louvel */ -public class AnnotatedResource05TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource05TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -31,7 +29,7 @@ void configureFinder(Finder finder) { } @Test - public void testPost() throws IOException, ResourceException { + void testPost() throws IOException, ResourceException { Representation result = clientResource.post("[\"root\"]", MediaType.APPLICATION_JSON); assertNotNull(result); assertEquals("[\"root\"]", result.getText()); @@ -42,5 +40,4 @@ public void testPost() throws IOException, ResourceException { assertEquals("", result.getText()); assertEquals(MediaType.APPLICATION_XML, result.getMediaType()); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource06TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource06TestCase.java index 1c2052e051..34d7d055ca 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource06TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource06TestCase.java @@ -1,29 +1,27 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.Representation; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * Test the annotated resources, client and server sides. - * + * * @author Jerome Louvel */ -public class AnnotatedResource06TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource06TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -31,9 +29,8 @@ void configureFinder(Finder finder) { } @Test - public void testPost() throws IOException, ResourceException { - Representation result = clientResource.post("[\"root\"]", - MediaType.APPLICATION_JSON); + void testPost() throws IOException, ResourceException { + Representation result = clientResource.post("[\"root\"]", MediaType.APPLICATION_JSON); assertNotNull(result); assertEquals("[\"root\"]2", result.getText()); assertEquals(MediaType.APPLICATION_JSON, result.getMediaType()); @@ -43,5 +40,4 @@ public void testPost() throws IOException, ResourceException { assertEquals("1", result.getText()); assertEquals(MediaType.APPLICATION_XML, result.getMediaType()); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource07TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource07TestCase.java index 7bb0a87eca..997f28a97a 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource07TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource07TestCase.java @@ -1,30 +1,28 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * Test the annotated resources, client and server sides. - * + * * @author Jerome Louvel */ -public class AnnotatedResource07TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource07TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -32,9 +30,8 @@ void configureFinder(Finder finder) { } @Test - public void testPost() throws IOException, ResourceException { - Representation input = new StringRepresentation("[\"root\"]", - MediaType.APPLICATION_JSON); + void testPost() throws IOException, ResourceException { + Representation input = new StringRepresentation("[\"root\"]", MediaType.APPLICATION_JSON); Representation result = clientResource.post(input); assertNotNull(result); assertEquals("[\"root\"]1", result.getText()); @@ -46,5 +43,4 @@ public void testPost() throws IOException, ResourceException { assertEquals("2", result.getText()); assertEquals(MediaType.APPLICATION_XML, result.getMediaType()); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource08TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource08TestCase.java index 876935cdda..5b6d69daae 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource08TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource08TestCase.java @@ -1,14 +1,22 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.APPLICATION_WWW_FORM; +import static org.restlet.data.MediaType.APPLICATION_XML; +import static org.restlet.data.MediaType.TEXT_HTML; + +import java.io.IOException; +import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -16,19 +24,12 @@ import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; -import java.io.IOException; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.restlet.data.MediaType.*; - /** * Test the annotated resources, client and server sides. - * + * * @author Jerome Louvel */ -public class AnnotatedResource08TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource08TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -37,7 +38,12 @@ void configureFinder(Finder finder) { @ParameterizedTest @MethodSource("argumentsProvider") - public void testPost(final MediaType requestBodyMediaType, final String requestBody, final MediaType responseBodyMediaType, final String responseBody) throws IOException, ResourceException { + void testPost( + final MediaType requestBodyMediaType, + final String requestBody, + final MediaType responseBodyMediaType, + final String responseBody) + throws IOException, ResourceException { Representation input = new StringRepresentation(requestBody, requestBodyMediaType); Representation result = clientResource.post(input, responseBodyMediaType); assertNotNull(result); @@ -51,8 +57,6 @@ static Stream argumentsProvider() { Arguments.arguments(APPLICATION_XML, "root", APPLICATION_JSON, "root1"), Arguments.arguments(APPLICATION_JSON, "root", APPLICATION_XML, "root1"), Arguments.arguments(APPLICATION_WWW_FORM, "root", APPLICATION_WWW_FORM, "root2"), - Arguments.arguments(APPLICATION_WWW_FORM, "root", TEXT_HTML, "root2") - ); + Arguments.arguments(APPLICATION_WWW_FORM, "root", TEXT_HTML, "root2")); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource12TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource12TestCase.java index 49382079dc..525e63f8a6 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource12TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource12TestCase.java @@ -1,25 +1,26 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + import org.junit.jupiter.api.Test; import org.restlet.data.Form; -import static org.junit.jupiter.api.Assertions.*; - /** * Test the annotated resources, client and server sides. * * @author Jerome Louvel */ -public class AnnotatedResource12TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource12TestCase extends AbstractAnnotatedResourceWithFinderTestCase { private MyResource12 myResource; @@ -35,7 +36,7 @@ void configureFinder(Finder finder) { } @Test - public void testPutGet() { + void testPutGet() { Form myForm = myResource.represent(); assertNull(myForm); @@ -49,5 +50,4 @@ public void testPutGet() { assertEquals("value1", myForm.getFirstValue("param1")); assertEquals("value2", myForm.getFirstValue("param2")); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource13TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource13TestCase.java index 39d85d1fe1..ca0babcd20 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource13TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource13TestCase.java @@ -1,28 +1,26 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.Serializable; import java.util.Date; - -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import org.junit.jupiter.api.Test; /** * Test the annotated resources, client and server sides. - * + * * @author Jerome Louvel */ -public class AnnotatedResource13TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource13TestCase extends AbstractAnnotatedResourceWithFinderTestCase { private MyResource13 myResource; @@ -43,12 +41,12 @@ protected void tearDownEach() { } @Test - public void testQuery() { + void testQuery() { Contact contact = myResource.retrieve(); assertNotNull(contact); LightContact lightContact = myResource.retrieveLight(); - assertNotEquals(lightContact.getClass(), Contact.class); + assertNotEquals(Contact.class, lightContact.getClass()); assertNotNull(lightContact); FullContact fullContact = myResource.retrieveFull(); @@ -67,14 +65,21 @@ public LightContact retrieveLight() { } public Contact retrieve() { - return new Contact("test@domain.com", "Scott", "Tiger", new Date(), - "test@perso.fr"); + return new Contact("test@domain.com", "Scott", "Tiger", new Date(), "test@perso.fr"); } public FullContact retrieveFull() { - return new FullContact("test@domain.com", "Scott", "Tiger", new Date(), - "test@perso.fr", "1 Main Street", "Restlet city, 0102", - "RESTland", "+123.456", "+789012"); + return new FullContact( + "test@domain.com", + "Scott", + "Tiger", + new Date(), + "test@perso.fr", + "1 Main Street", + "Restlet city, 0102", + "RESTland", + "+123.456", + "+789012"); } } @@ -84,8 +89,8 @@ public static class Contact extends LightContact implements Serializable { private String email2; - public Contact(String email, String firstName, String lastName, - Date birthDate, String email2) { + public Contact( + String email, String firstName, String lastName, Date birthDate, String email2) { super(email, firstName, lastName); this.birthDate = birthDate; email = email2; @@ -106,7 +111,6 @@ public void setBirthDate(Date birthDate) { public void setEmail2(String email) { this.email2 = email; } - } public static class FullContact extends Contact implements Serializable { @@ -117,9 +121,17 @@ public static class FullContact extends Contact implements Serializable { private String address3; - public FullContact(String email, String firstName, String lastName, - Date birthDate, String email2, String address1, String address2, - String address3, String fax, String phone) { + public FullContact( + String email, + String firstName, + String lastName, + Date birthDate, + String email2, + String address1, + String address2, + String address3, + String fax, + String phone) { super(email, firstName, lastName, birthDate, email2); this.address1 = address1; this.address2 = address2; @@ -171,7 +183,6 @@ public void setFax(String fax) { public void setPhone(String phone) { this.phone = phone; } - } public static class LightContact implements Serializable { @@ -212,7 +223,6 @@ public void setFirstName(String firstName) { public void setLastName(String lastName) { this.lastName = lastName; } - } public interface MyResource13 { @@ -225,7 +235,5 @@ public interface MyResource13 { @Get("?deep") FullContact retrieveFull(); - } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource14TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource14TestCase.java index a8ac2f7251..c86a3daffa 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource14TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource14TestCase.java @@ -1,14 +1,21 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.restlet.data.MediaType.APPLICATION_JSON; +import static org.restlet.data.MediaType.APPLICATION_XML; +import static org.restlet.data.MediaType.TEXT_PLAIN; + +import java.io.IOException; +import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -16,19 +23,12 @@ import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; -import java.io.IOException; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.restlet.data.MediaType.*; - /** * Test the annotated resources, client and server sides. * * @author Jerome Louvel */ -public class AnnotatedResource14TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource14TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -41,18 +41,23 @@ static Stream argumentsProvider() { Arguments.arguments(APPLICATION_XML, APPLICATION_JSON, "xml:json"), Arguments.arguments(APPLICATION_XML, APPLICATION_XML, "xml"), Arguments.arguments(TEXT_PLAIN, null, "*"), - Arguments.arguments(TEXT_PLAIN, TEXT_PLAIN, "*") - ); + Arguments.arguments(TEXT_PLAIN, TEXT_PLAIN, "*")); } @ParameterizedTest @MethodSource("argumentsProvider") - public void testQuery(final MediaType requestEntityMediaType, final MediaType responseMediaTypePreference, final String responseEntityAsText) throws IOException { - StringRepresentation requestEntity = new StringRepresentation("test", requestEntityMediaType); + void testQuery( + final MediaType requestEntityMediaType, + final MediaType responseMediaTypePreference, + final String responseEntityAsText) + throws IOException { + StringRepresentation requestEntity = + new StringRepresentation("test", requestEntityMediaType); - Representation rep = responseMediaTypePreference == null - ? clientResource.put(requestEntity) - : clientResource.put(requestEntity, responseMediaTypePreference); + Representation rep = + responseMediaTypePreference == null + ? clientResource.put(requestEntity) + : clientResource.put(requestEntity, responseMediaTypePreference); assertNotNull(rep); if (responseMediaTypePreference != null) { diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource15OkTestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource15OkTestCase.java index 412afa1932..5c428e6e1c 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource15OkTestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource15OkTestCase.java @@ -1,27 +1,26 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.Representation; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * Test the annotated resources, client and server sides. * * @author Jerome Louvel */ -public class AnnotatedResource15OkTestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource15OkTestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -29,7 +28,7 @@ void configureFinder(Finder finder) { } @Test - public void shouldFailBecauseServerResourceAnnotationResidesOnGenericServerResource() { + void shouldFailBecauseServerResourceAnnotationResidesOnGenericServerResource() { MyBean myBean = new MyBean("test", "description"); Representation rep = clientResource.post(myBean, MediaType.APPLICATION_JAVA_OBJECT); assertNotNull(rep); diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource15TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource15TestCase.java index 181ae0493b..afdedb02fd 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource15TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource15TestCase.java @@ -1,27 +1,26 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.data.Status; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - /** * Test the annotated resources, client and server sides. * * @author Jerome Louvel */ -public class AnnotatedResource15TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource15TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -30,14 +29,17 @@ void configureFinder(Finder finder) { /** * The Java serialization (ObjectRepresentation) works well when the target class is correctly - * available at runtime. The usage of annotation like @Post on a generic method leads - * to the DefaultConverter to handle Object class and not Serializable interface. - * (cf AbstractGenericAnnotatedServerResource class). + * available at runtime. The usage of annotation like @Post on a generic method leads to the + * DefaultConverter to handle Object class and not Serializable interface. (cf + * AbstractGenericAnnotatedServerResource class). */ @Test - public void shouldFailBecauseServerResourceAnnotationResidesOnGenericServerResource() { + void shouldFailBecauseServerResourceAnnotationResidesOnGenericServerResource() { MyBean myBean = new MyBean("test", "description"); - ResourceException resourceException = assertThrows(ResourceException.class, () -> clientResource.post(myBean, MediaType.APPLICATION_JAVA_OBJECT)); + ResourceException resourceException = + assertThrows( + ResourceException.class, + () -> clientResource.post(myBean, MediaType.APPLICATION_JAVA_OBJECT)); assertEquals(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE, resourceException.getStatus()); } } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource16TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource16TestCase.java index 84c7bc8d47..067786542d 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource16TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource16TestCase.java @@ -1,28 +1,27 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.ObjectRepresentation; import org.restlet.representation.Representation; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * Test the annotated resources, client and server sides. * * @author Jerome Louvel */ -public class AnnotatedResource16TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource16TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -30,10 +29,11 @@ void configureFinder(Finder finder) { } @Test - public void testQuery() { + void testQuery() { final MyBean myBean = new MyBean("test", "description"); - final Representation rep = clientResource.post(new ObjectRepresentation<>(myBean), - MediaType.APPLICATION_JAVA_OBJECT); + final Representation rep = + clientResource.post( + new ObjectRepresentation<>(myBean), MediaType.APPLICATION_JAVA_OBJECT); assertNotNull(rep); assertEquals(MediaType.APPLICATION_JAVA_OBJECT, rep.getMediaType()); diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource17TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource17TestCase.java index 446d7d71c6..c10b819af6 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource17TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource17TestCase.java @@ -1,31 +1,29 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.ObjectRepresentation; import org.restlet.representation.Representation; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * Test the annotated resources, client and server sides. - * + * * @author Jerome Louvel */ -public class AnnotatedResource17TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource17TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -33,12 +31,15 @@ void configureFinder(Finder finder) { } @Test - public void testQuery() throws IOException, ClassNotFoundException { + void testQuery() throws IOException, ClassNotFoundException { MyBean myBean = new MyBean("test", "description"); - Representation rep = clientResource.post(new ObjectRepresentation<>(myBean), MediaType.APPLICATION_JAVA_OBJECT); + Representation rep = + clientResource.post( + new ObjectRepresentation<>(myBean), MediaType.APPLICATION_JAVA_OBJECT); assertNotNull(rep); assertEquals(MediaType.APPLICATION_JAVA_OBJECT, rep.getMediaType()); - ObjectRepresentation jr = new ObjectRepresentation<>(rep, MyBean.class.getClassLoader()); + ObjectRepresentation jr = + new ObjectRepresentation<>(rep, MyBean.class.getClassLoader()); assertNotNull(jr.getObject()); Assertions.assertEquals("test", jr.getObject().getName()); } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource18TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource18TestCase.java index cc71fef0bc..1707ea07d8 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource18TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource18TestCase.java @@ -1,31 +1,29 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; import org.restlet.representation.ObjectRepresentation; import org.restlet.representation.Representation; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * Test the annotated resources, client and server sides. - * + * * @author Jerome Louvel */ -public class AnnotatedResource18TestCase extends AbstractAnnotatedResourceWithFinderTestCase { +class AnnotatedResource18TestCase extends AbstractAnnotatedResourceWithFinderTestCase { @Override void configureFinder(Finder finder) { @@ -33,12 +31,15 @@ void configureFinder(Finder finder) { } @Test - public void testQuery() throws IOException, ClassNotFoundException { + void testQuery() throws IOException, ClassNotFoundException { MyBean myBean = new MyBean("test", "description"); - Representation rep = clientResource.post(new ObjectRepresentation<>(myBean), MediaType.APPLICATION_JAVA_OBJECT); + Representation rep = + clientResource.post( + new ObjectRepresentation<>(myBean), MediaType.APPLICATION_JAVA_OBJECT); assertNotNull(rep); assertEquals(MediaType.APPLICATION_JAVA_OBJECT, rep.getMediaType()); - ObjectRepresentation jr = new ObjectRepresentation<>(rep, MyBean.class.getClassLoader()); + ObjectRepresentation jr = + new ObjectRepresentation<>(rep, MyBean.class.getClassLoader()); assertNotNull(jr.getObject()); Assertions.assertEquals("test", jr.getObject().getName()); } diff --git a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource20TestCase.java b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource20TestCase.java index fbcf72d6de..015ee774a0 100644 --- a/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource20TestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/AnnotatedResource20TestCase.java @@ -1,27 +1,26 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.Test; import org.restlet.Application; import org.restlet.data.MediaType; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - /** * Test the annotated resources, client and server sides. - * + * * @author Jerome Louvel */ -public class AnnotatedResource20TestCase extends AbstractAnnotatedResourceTestCase { +class AnnotatedResource20TestCase extends AbstractAnnotatedResourceTestCase { private MyResource20 myResource; @@ -38,14 +37,16 @@ void configureClientResource(ClientResource clientResource) { } @Test - public void testGet() { + void testGet() { assertThrows(MyException01.class, () -> myResource.represent()); assertEquals(400, clientResource.getStatus().getCode()); } @Test - public void testGetAndSerializeException() { - MyException02 e = assertThrows(MyException02.class, () -> myResource.representAndSerializeException()); + void testGetAndSerializeException() { + MyException02 e = + assertThrows( + MyException02.class, () -> myResource.representAndSerializeException()); assertEquals("my custom error", e.getCustomProperty()); assertEquals(400, clientResource.getStatus().getCode()); } diff --git a/org.restlet/src/test/java/org/restlet/resource/DirectoryTestCase.java b/org.restlet/src/test/java/org/restlet/resource/DirectoryTestCase.java index db70890c28..7b6a26caa8 100644 --- a/org.restlet/src/test/java/org/restlet/resource/DirectoryTestCase.java +++ b/org.restlet/src/test/java/org/restlet/resource/DirectoryTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; import static java.io.File.createTempFile; @@ -32,11 +31,12 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; - import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.restlet.Application; import org.restlet.Component; import org.restlet.Request; @@ -58,10 +58,10 @@ /** * Unit tests for the Directory class. - * + * * @author Thierry Boileau */ -public class DirectoryTestCase { +class DirectoryTestCase { String webSiteURL = "http://myapplication/"; @@ -73,11 +73,9 @@ public class DirectoryTestCase { String baseFileUrlFrBis = this.webSiteURL.concat("fichier.fr.txt"); - String percentEncodedFileUrl = this.webSiteURL.concat(Reference - .encode("a new %file.txt.fr")); + String percentEncodedFileUrl = this.webSiteURL.concat(Reference.encode("a new %file.txt.fr")); - String percentEncodedFileUrlBis = this.webSiteURL - .concat("a+new%20%25file.txt.fr"); + String percentEncodedFileUrlBis = this.webSiteURL.concat("a+new%20%25file.txt.fr"); /** Tests the creation of directory with unknown parent directories. */ String testCreationDirectory = webSiteURL.concat("dir/does/not/exist"); @@ -86,15 +84,14 @@ public class DirectoryTestCase { String testCreationFile = webSiteURL.concat("file/does/not/exist.xml"); /** Tests the creation of text file with unknown parent directories. */ - String testCreationTextFile = webSiteURL - .concat("text/file/does/not/exist.txt"); + String testCreationTextFile = webSiteURL.concat("text/file/does/not/exist.txt"); private static Component clientComponent; private static MyApplication application; File testDir; @BeforeAll - public static void setUp() throws Exception { + static void setUp() throws Exception { Engine.getInstance().getRegisteredConverters().clear(); Engine.getInstance().registerDefaultConverters(); Engine.getInstance().getRegisteredClients().add(new FileClientHelper(null)); @@ -117,58 +114,34 @@ public static void setUp() throws Exception { } @AfterAll - public static void tearDown() throws Exception { + static void tearDown() throws Exception { // Now, let's stop the component! clientComponent.stop(); } @AfterEach - public void afterEach() { + void afterEach() { IoUtils.delete(this.testDir, true); } - @Test - public void testDirectoryWithExtensionTunnelAndIndexName() throws IOException { - application.getTunnelService().setExtensionsTunnel(true); - this.testDir = Files.createTempDirectory("testDirectoryWithExtensionTunnelAndIndexName").toFile(); - application.setTestDirectory(testDir); - - // Test the directory Restlet with an index name - testDirectory(application, application.getDirectory(), "index"); - } - - @Test - public void testDirectoryWithExtensionTunnelAndWithoutIndexName() throws IOException { - application.getTunnelService().setExtensionsTunnel(true); - this.testDir = Files.createTempDirectory("testDirectoryWithExtensionTunnelAndWithoutIndexName").toFile(); - application.setTestDirectory(testDir); - - // Test the directory Restlet with no index name - testDirectory(application, application.getDirectory(), ""); - } - - @Test - public void testDirectoryWithoutExtensionTunnelAndIndexName() throws IOException { - application.getTunnelService().setExtensionsTunnel(false); - this.testDir = Files.createTempDirectory("testDirectoryWithoutExtensionTunnelAndIndexName").toFile(); - application.setTestDirectory(testDir); - - // Test the directory Restlet with an index name - testDirectory(application, application.getDirectory(), "index"); - } - - @Test - public void testDirectoryWithoutExtensionTunnelAndWithoutIndexName() throws IOException { - application.getTunnelService().setExtensionsTunnel(false); - this.testDir = Files.createTempDirectory("testDirectoryWithoutExtensionTunnelAndWithoutIndexName").toFile(); + @ParameterizedTest + @CsvSource({ + "true,index,testDirectoryWithExtensionTunnelAndIndexName", + "true,'',testDirectoryWithExtensionTunnelAndWithoutIndexName", + "false,index,testDirectoryWithoutExtensionTunnelAndIndexName", + "false,'',testDirectoryWithoutExtensionTunnelAndWithoutIndexName" + }) + void testDirectoryWithOrWithoutExtensionTunnel( + boolean extensionsTunnel, String indexName, String testDirectoryName) + throws IOException { + application.getTunnelService().setExtensionsTunnel(extensionsTunnel); + this.testDir = Files.createTempDirectory(testDirectoryName).toFile(); application.setTestDirectory(testDir); - - // Test the directory Restlet with no index name - testDirectory(application, application.getDirectory(), ""); + testDirectory(application, application.getDirectory(), indexName); } @Test - public void testDirectoryDeeplyAccessible() throws IOException { + void testDirectoryDeeplyAccessible() throws IOException { this.testDir = Files.createTempDirectory("testDirectoryDeeplyAccessible").toFile(); application.setTestDirectory(testDir); @@ -178,30 +151,34 @@ public void testDirectoryDeeplyAccessible() throws IOException { application.getDirectory().setDeeplyAccessible(true); application.getDirectory().setListingAllowed(true); - Response response = new TestRequest(this.webSiteURL, "dir/subDir/") - .baseRef(this.webSiteURL) - .handle(GET); + Response response = + new TestRequest(this.webSiteURL, "dir/subDir/") + .baseRef(this.webSiteURL) + .handle(GET); assertEquals(SUCCESS_OK, response.getStatus()); - response = new TestRequest(this.webSiteURL, "dir/subDir/", testFile.getName()) - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "dir/subDir/", testFile.getName()) + .baseRef(this.webSiteURL) + .handle(GET); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); application.getDirectory().setDeeplyAccessible(false); - response = new TestRequest(this.webSiteURL, "dir/subDir/") - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "dir/subDir/") + .baseRef(this.webSiteURL) + .handle(GET); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); - response = new TestRequest(this.webSiteURL, "dir/subDir/", testFile.getName()) - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "dir/subDir/", testFile.getName()) + .baseRef(this.webSiteURL) + .handle(GET); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); } @Test - public void testParentDirectoryInaccessible() throws IOException { + void testParentDirectoryInaccessible() throws IOException { application.getTunnelService().setExtensionsTunnel(false); this.testDir = Files.createTempDirectory("testParentDirectoryInaccessible").toFile(); @@ -221,83 +198,79 @@ public void testParentDirectoryInaccessible() throws IOException { Response response; - response = new TestRequest(this.webSiteURL, "file.txt") - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "file.txt").baseRef(this.webSiteURL).handle(GET); // assert no content as the file is empty assertEquals(Status.SUCCESS_NO_CONTENT, response.getStatus()); - response = new TestRequest(this.webSiteURL, "%2e%2e/child%20dir/file.txt") - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "%2e%2e/child%20dir/file.txt") + .baseRef(this.webSiteURL) + .handle(GET); // assert no content as the file is empty assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); - response = new TestRequest(this.webSiteURL, "%2e%2e%2fchild%20dir/file.txt") - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "%2e%2e%2fchild%20dir/file.txt") + .baseRef(this.webSiteURL) + .handle(GET); // assert no content as the file is empty assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); - response = new TestRequest(this.webSiteURL, "../child%20dir/file.txt") - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "../child%20dir/file.txt") + .baseRef(this.webSiteURL) + .handle(GET); // assert no content as the file is empty assertEquals(Status.SUCCESS_NO_CONTENT, response.getStatus()); - response = new TestRequest(this.webSiteURL, "..") - .baseRef(this.webSiteURL) - .handle(GET); + response = new TestRequest(this.webSiteURL, "..").baseRef(this.webSiteURL).handle(GET); assertEquals(CLIENT_ERROR_FORBIDDEN, response.getStatus()); - response = new TestRequest(this.webSiteURL, "../") - .baseRef(this.webSiteURL) - .handle(GET); + response = new TestRequest(this.webSiteURL, "../").baseRef(this.webSiteURL).handle(GET); assertEquals(CLIENT_ERROR_FORBIDDEN, response.getStatus()); - response = new TestRequest(this.webSiteURL, "../private.txt") - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "../private.txt") + .baseRef(this.webSiteURL) + .handle(GET); assertEquals(CLIENT_ERROR_FORBIDDEN, response.getStatus()); - response = new TestRequest(this.webSiteURL, "%2e%2e") - .baseRef(this.webSiteURL) - .handle(GET); + response = new TestRequest(this.webSiteURL, "%2e%2e").baseRef(this.webSiteURL).handle(GET); assertEquals(CLIENT_ERROR_FORBIDDEN, response.getStatus()); - response = new TestRequest(this.webSiteURL, "%2e%2e/") - .baseRef(this.webSiteURL) - .handle(GET); + response = new TestRequest(this.webSiteURL, "%2e%2e/").baseRef(this.webSiteURL).handle(GET); assertEquals(CLIENT_ERROR_FORBIDDEN, response.getStatus()); - response = new TestRequest(this.webSiteURL, "%2e%2e%2f") - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "%2e%2e%2f").baseRef(this.webSiteURL).handle(GET); assertEquals(CLIENT_ERROR_FORBIDDEN, response.getStatus()); - response = new TestRequest(this.webSiteURL, "%2e%2e/private.txt") - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "%2e%2e/private.txt") + .baseRef(this.webSiteURL) + .handle(GET); assertEquals(CLIENT_ERROR_FORBIDDEN, response.getStatus()); - response = new TestRequest(this.webSiteURL, "%2e%2e%2fprivate.txt") - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.webSiteURL, "%2e%2e%2fprivate.txt") + .baseRef(this.webSiteURL) + .handle(GET); assertEquals(CLIENT_ERROR_FORBIDDEN, response.getStatus()); } - /** - * Test content negotiation based on client preferences. - */ + /** Test content negotiation based on client preferences. */ @Test - public void testUserPreferences() throws IOException { + void testUserPreferences() throws IOException { application.getTunnelService().setExtensionsTunnel(true); // Allow extensions tunneling - application.getMetadataService().setDefaultLanguage(Language.ENGLISH); // default language is not es or fr. + application + .getMetadataService() + .setDefaultLanguage(Language.ENGLISH); // default language is not es or fr. this.testDir = Files.createTempDirectory("testUserPreferences").toFile(); System.out.println(this.testDir.getAbsolutePath()); application.setTestDirectory(testDir); @@ -309,45 +282,35 @@ public void testUserPreferences() throws IOException { final String testEsTxtFileUrl = this.webSiteURL.concat("test.es.txt"); // Create two files - Response response = new TestRequest(testFrTxtFileUrl) - .baseRef(this.webSiteURL) - .entity("fr") - .handle(PUT); + Response response = + new TestRequest(testFrTxtFileUrl).baseRef(this.webSiteURL).entity("fr").handle(PUT); assertEquals(SUCCESS_CREATED, response.getStatus()); - response = new TestRequest(testEsTxtFileUrl) - .baseRef(this.webSiteURL) - .entity("es") - .handle(PUT); + response = + new TestRequest(testEsTxtFileUrl).baseRef(this.webSiteURL).entity("es").handle(PUT); assertEquals(SUCCESS_CREATED, response.getStatus()); - response = new TestRequest(testFileUrl) - .baseRef(this.webSiteURL) - .accept(SPANISH) - .handle(GET); + response = + new TestRequest(testFileUrl).baseRef(this.webSiteURL).accept(SPANISH).handle(GET); assertEquals(SUCCESS_OK, response.getStatus()); assertEquals("es", response.getEntityAsText()); - response = new TestRequest(testFileUrl) - .baseRef(this.webSiteURL) - .accept(FRENCH) - .handle(GET); + response = new TestRequest(testFileUrl).baseRef(this.webSiteURL).accept(FRENCH).handle(GET); assertEquals(SUCCESS_OK, response.getStatus()); assertEquals("fr", response.getEntityAsText()); - response = new TestRequest(testTxtFileUrl) - .baseRef(this.webSiteURL) - .accept(SPANISH) - .handle(GET); + response = + new TestRequest(testTxtFileUrl) + .baseRef(this.webSiteURL) + .accept(SPANISH) + .handle(GET); assertEquals(SUCCESS_OK, response.getStatus()); assertEquals("es", response.getEntityAsText()); - response = new TestRequest(testTxtFileUrl) - .baseRef(this.webSiteURL) - .accept(FRENCH) - .handle(GET); + response = + new TestRequest(testTxtFileUrl).baseRef(this.webSiteURL).accept(FRENCH).handle(GET); assertEquals(SUCCESS_OK, response.getStatus()); assertEquals("fr", response.getEntityAsText()); } @@ -364,8 +327,7 @@ private static class MyApplication extends Application { /** * Constructor. * - * @param testDirectory - * The test directory. + * @param testDirectory The test directory. */ public MyApplication(File testDirectory) { // Create a DirectoryHandler that manages a local Directory @@ -401,7 +363,6 @@ private void resetDirectoryToDefault() { this.directory.setModifiable(false); this.directory.setNegotiatingContent(true); } - } private static class TestRequest { @@ -448,20 +409,15 @@ protected TestRequest query(String name, String value) { protected Response handle(Method method) { final Response response = new Response(request); request.setMethod(method); - request.setOriginalRef(ReferenceUtils.getOriginalRef(request.getResourceRef(), request.getHeaders())); + request.setOriginalRef( + ReferenceUtils.getOriginalRef(request.getResourceRef(), request.getHeaders())); Application.getCurrent().handle(request, response); return response; } } - /** - * Helper - * - * @param directory - * @param indexName - * @throws IOException - */ - private void testDirectory(MyApplication application, Directory directory, String indexName) throws IOException { + private void testDirectory(MyApplication application, Directory directory, String indexName) + throws IOException { // Create a temporary file for the tests (the tests directory is not empty) final File testFile = File.createTempFile("test", ".txt", this.testDir); // Create a temporary directory @@ -472,344 +428,301 @@ private void testDirectory(MyApplication application, Directory directory, Strin final String testDirectoryUrl = this.webSiteURL.concat(testDirectory.getName()); directory.setIndexName(indexName); - // Test 1a : directory does not allow to GET its content + // Test 1a: directory does not allow to GET its content directory.setListingAllowed(false); - Response response = new TestRequest(this.webSiteURL) - .baseRef(this.webSiteURL) - .handle(GET); + Response response = new TestRequest(this.webSiteURL).baseRef(this.webSiteURL).handle(GET); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); - // Test 1b : directory allows to GET its content + // Test 1b: directory allows to GET its content directory.setListingAllowed(true); - response = new TestRequest(this.webSiteURL) - .baseRef(this.webSiteURL) - .handle(GET); + response = new TestRequest(this.webSiteURL).baseRef(this.webSiteURL).handle(GET); assertEquals(SUCCESS_OK, response.getStatus()); // should list all files in the directory (at least the temporary file generated before) assertTrue(response.getEntityAsText().contains(testFile.getName())); - // Test 2a : tests the HEAD method - response = new TestRequest(testFileUrl) - .baseRef(this.webSiteURL) - .handle(GET); + // Test 2a: tests the HEAD method + response = new TestRequest(testFileUrl).baseRef(this.webSiteURL).handle(GET); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); - response = new TestRequest(testFileUrl) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(testFileUrl).baseRef(this.webSiteURL).handle(HEAD); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); - // Test 2b : try to GET a file that does not exist - response = new TestRequest(this.webSiteURL, "123456.txt") - .baseRef(this.webSiteURL) - .handle(GET); + // Test 2b: try to GET a file that does not exist + response = + new TestRequest(this.webSiteURL, "123456.txt").baseRef(this.webSiteURL).handle(GET); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); - // Test 3a : try to put a new representation, but the directory is read only + // Test 3a: try to put a new representation, but the directory is read only directory.setModifiable(false); - response = new TestRequest(this.baseFileUrl) - .baseRef(this.webSiteURL) - .entity("this is test 3a") - .handle(PUT); + response = + new TestRequest(this.baseFileUrl) + .baseRef(this.webSiteURL) + .entity("this is test 3a") + .handle(PUT); assertEquals(CLIENT_ERROR_METHOD_NOT_ALLOWED, response.getStatus()); - // Test 3b : try to put a new representation, the directory is no more - // read only + // Test 3b: try to put a new representation, the directory is no more read-only directory.setModifiable(true); - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .entity("this is test 3b") - .entityLanguage(FRENCH) - .handle(PUT); + response = + new TestRequest(this.baseFileUrlFr) + .baseRef(this.webSiteURL) + .entity("this is test 3b") + .entityLanguage(FRENCH) + .handle(PUT); assertEquals(SUCCESS_CREATED, response.getStatus()); - response = new TestRequest(this.baseFileUrl) - .baseRef(this.webSiteURL) - .query("x", "y") - .handle(GET); + response = + new TestRequest(this.baseFileUrl) + .baseRef(this.webSiteURL) + .query("x", "y") + .handle(GET); assertEquals(SUCCESS_OK, response.getStatus()); assertEquals("this is test 3b", response.getEntityAsText()); - // Test 4 : Try to get the representation of the new file - response = new TestRequest(this.baseFileUrl) - .baseRef(this.webSiteURL) - .handle(GET); + // Test 4: Try to get the representation of the new file + response = new TestRequest(this.baseFileUrl).baseRef(this.webSiteURL).handle(GET); assertEquals(SUCCESS_OK, response.getStatus()); assertEquals("this is test 3b", response.getEntityAsText()); - // Test 5 : add a new representation of the same base file - response = new TestRequest(this.baseFileUrlEn) - .baseRef(this.webSiteURL) - .entity("this is a test - En") - .handle(PUT); + // Test 5: add a new representation of the same base file + response = + new TestRequest(this.baseFileUrlEn) + .baseRef(this.webSiteURL) + .entity("this is a test - En") + .handle(PUT); assertEquals(SUCCESS_CREATED, response.getStatus()); - response = new TestRequest(this.baseFileUrl) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrl).baseRef(this.webSiteURL).handle(HEAD); assertEquals(SUCCESS_OK, response.getStatus()); - response = new TestRequest(this.baseFileUrlEn) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrlEn).baseRef(this.webSiteURL).handle(HEAD); assertEquals(SUCCESS_OK, response.getStatus()); - // Test 6a : delete a file - response = new TestRequest(testFileUrl) - .baseRef(this.webSiteURL) - .handle(DELETE); + // Test 6a: delete a file + response = new TestRequest(testFileUrl).baseRef(this.webSiteURL).handle(DELETE); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); - response = new TestRequest(testFileUrl) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(testFileUrl).baseRef(this.webSiteURL).handle(HEAD); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); - // Test 6b : delete a file that does not exist - response = new TestRequest(testFileUrl) - .baseRef(this.webSiteURL) - .handle(DELETE); + // Test 6b: delete a file that does not exist + response = new TestRequest(testFileUrl).baseRef(this.webSiteURL).handle(DELETE); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); - // Test 6c : delete a directory (without and with trailing slash) + // Test 6c: delete a directory (without and with trailing slash) // Distinct behaviors if an index has been defined or not if (indexName.isEmpty()) { - response = new TestRequest(testDirectoryUrl) - .baseRef(this.webSiteURL) - .handle(DELETE); + response = new TestRequest(testDirectoryUrl).baseRef(this.webSiteURL).handle(DELETE); assertEquals(REDIRECTION_SEE_OTHER, response.getStatus()); - response = new TestRequest(response.getLocationRef().getIdentifier()) - .baseRef(response.getLocationRef().getIdentifier()) - .handle(DELETE); + response = + new TestRequest(response.getLocationRef().getIdentifier()) + .baseRef(response.getLocationRef().getIdentifier()) + .handle(DELETE); assertEquals(CLIENT_ERROR_FORBIDDEN, response.getStatus()); - response = new TestRequest(this.webSiteURL) - .baseRef(this.webSiteURL) - .handle(DELETE); + response = new TestRequest(this.webSiteURL).baseRef(this.webSiteURL).handle(DELETE); assertEquals(CLIENT_ERROR_FORBIDDEN, response.getStatus()); } else { // As there is no index file in the directory, the response must // return Status.CLIENT_ERROR_NOT_FOUND - response = new TestRequest(testDirectoryUrl, "/") - .baseRef(this.webSiteURL) - .handle(DELETE); + response = + new TestRequest(testDirectoryUrl, "/").baseRef(this.webSiteURL).handle(DELETE); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); - response = new TestRequest(this.webSiteURL) - .baseRef(this.webSiteURL) - .handle(DELETE); + response = new TestRequest(this.webSiteURL).baseRef(this.webSiteURL).handle(DELETE); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); } // Test 7a : put one representation of the base file (in French language) - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .entity("message de test") - .handle(PUT); + response = + new TestRequest(this.baseFileUrlFr) + .baseRef(this.webSiteURL) + .entity("message de test") + .handle(PUT); assertTrue(response.getStatus().isSuccess()); - // Test 7b : put another representation of the base file (in French - // language) but the extensions are mixed + // Test 7b: put another representation of the base file (in French + // language), but the extensions are mixed // and there is no content negotiation directory.setNegotiatingContent(false); - response = new TestRequest(this.baseFileUrlFrBis) - .baseRef(this.webSiteURL) - .entity("message de test") - .handle(PUT); + response = + new TestRequest(this.baseFileUrlFrBis) + .baseRef(this.webSiteURL) + .entity("message de test") + .handle(PUT); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); // The 2 resources in French must be present (the same actually) - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrlFr).baseRef(this.webSiteURL).handle(HEAD); assertEquals(Status.SUCCESS_OK, response.getStatus()); - response = new TestRequest(this.baseFileUrlFrBis) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrlFrBis).baseRef(this.webSiteURL).handle(HEAD); assertEquals(Status.SUCCESS_OK, response.getStatus()); - // Test 7c : delete the file representation of the resources with no content negotiation + // Test 7c: delete the file representation of the resources with no content negotiation // The 2 French resources are deleted (there were only one) - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .handle(DELETE); + response = new TestRequest(this.baseFileUrlFr).baseRef(this.webSiteURL).handle(DELETE); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrlFr).baseRef(this.webSiteURL).handle(HEAD); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); - response = new TestRequest(this.baseFileUrlFrBis) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrlFrBis).baseRef(this.webSiteURL).handle(HEAD); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); - response = new TestRequest(this.baseFileUrlFrBis) - .baseRef(this.webSiteURL) - .handle(DELETE); + response = new TestRequest(this.baseFileUrlFrBis).baseRef(this.webSiteURL).handle(DELETE); assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); - // Test 7d : put another representation of the base file (in French - // language) but the extensions are mixed + // Test 7d: put another representation of the base file (in French + // language), but the extensions are mixed // and there is content negotiation directory.setNegotiatingContent(true); - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .entity("message de test") - .handle(PUT); + response = + new TestRequest(this.baseFileUrlFr) + .baseRef(this.webSiteURL) + .entity("message de test") + .handle(PUT); assertEquals(SUCCESS_CREATED, response.getStatus()); - response = new TestRequest(this.baseFileUrlFrBis) - .baseRef(this.webSiteURL) - .entity("message de test Bis") - .handle(PUT); + response = + new TestRequest(this.baseFileUrlFrBis) + .baseRef(this.webSiteURL) + .entity("message de test Bis") + .handle(PUT); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); // only one resource in French must be present - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrlFr).baseRef(this.webSiteURL).handle(HEAD); assertEquals(SUCCESS_OK, response.getStatus()); - response = new TestRequest(this.baseFileUrlFrBis) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrlFrBis).baseRef(this.webSiteURL).handle(HEAD); assertEquals(SUCCESS_OK, response.getStatus()); - // TBOI : not sure this test is correct + // TBOI: not sure this test is correct // Check if only one resource has been created directory.setNegotiatingContent(false); - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrlFr).baseRef(this.webSiteURL).handle(HEAD); assertEquals(SUCCESS_OK, response.getStatus()); - // Test 7e : delete the file representation of the resources with content negotiation + // Test 7e: delete the file representation of the resources with content negotiation directory.setNegotiatingContent(true); - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .handle(DELETE); + response = new TestRequest(this.baseFileUrlFr).baseRef(this.webSiteURL).handle(DELETE); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrlFr).baseRef(this.webSiteURL).handle(HEAD); if (application.getTunnelService().isExtensionsTunnel()) { assertEquals(SUCCESS_OK, response.getStatus()); } else { assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); } - response = new TestRequest(this.baseFileUrlFrBis) - .baseRef(this.webSiteURL) - .handle(HEAD); + response = new TestRequest(this.baseFileUrlFrBis).baseRef(this.webSiteURL).handle(HEAD); if (application.getTunnelService().isExtensionsTunnel()) { assertEquals(SUCCESS_OK, response.getStatus()); } else { assertEquals(CLIENT_ERROR_NOT_FOUND, response.getStatus()); } - // Test 8 : must delete the English representation - response = new TestRequest(this.baseFileUrlFr) - .baseRef(this.webSiteURL) - .handle(DELETE); - response = new TestRequest(this.baseFileUrlEn) - .baseRef(this.webSiteURL) - .handle(DELETE); + // Test 8: must delete the English representation + new TestRequest(this.baseFileUrlFr).baseRef(this.webSiteURL).handle(DELETE); + response = new TestRequest(this.baseFileUrlEn).baseRef(this.webSiteURL).handle(DELETE); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); // Test 9a : put a new representation, the resource's URI contains // percent-encoded characters directory.setModifiable(true); - response = new TestRequest(this.percentEncodedFileUrl) - .baseRef(this.webSiteURL) - .entity("this is test 9a") - .entityLanguage(FRENCH) - .handle(PUT); + response = + new TestRequest(this.percentEncodedFileUrl) + .baseRef(this.webSiteURL) + .entity("this is test 9a") + .entityLanguage(FRENCH) + .handle(PUT); assertEquals(SUCCESS_CREATED, response.getStatus()); - // Test 9b : Try to get the representation of the new file - response = new TestRequest(this.percentEncodedFileUrl) - .baseRef(this.webSiteURL) - .handle(GET); + // Test 9b: Try to get the representation of the new file + response = new TestRequest(this.percentEncodedFileUrl).baseRef(this.webSiteURL).handle(GET); assertEquals(SUCCESS_OK, response.getStatus()); assertEquals("this is test 9a", response.getEntityAsText()); - // Test 9c : Try to get the representation of the new file with an + // Test 9c: Try to get the representation of the new file with an // equivalent URI - response = new TestRequest(this.percentEncodedFileUrlBis) - .baseRef(this.webSiteURL) - .handle(GET); + response = + new TestRequest(this.percentEncodedFileUrlBis).baseRef(this.webSiteURL).handle(GET); assertEquals(SUCCESS_OK, response.getStatus()); assertEquals("this is test 9a", response.getEntityAsText()); - // Test 9d : Try to delete the file - response = new TestRequest(this.percentEncodedFileUrl) - .baseRef(this.webSiteURL) - .handle(DELETE); + // Test 9d: Try to delete the file + response = + new TestRequest(this.percentEncodedFileUrl).baseRef(this.webSiteURL).handle(DELETE); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); - // Test 10a : Try to create a directory with an unknown hierarchy of + // Test 10a: Try to create a directory with an unknown hierarchy of // parent directories. - response = new TestRequest(this.testCreationDirectory) - .baseRef(this.webSiteURL) - .entity("useless entity") - .handle(PUT); + response = + new TestRequest(this.testCreationDirectory) + .baseRef(this.webSiteURL) + .entity("useless entity") + .handle(PUT); assertEquals(SUCCESS_CREATED, response.getStatus()); - // Test 10b : Try to create a directory (with the trailing "/") with an + // Test 10b: Try to create a directory (with the trailing "/") with an // unknown hierarchy of parent directories. - response = new TestRequest(this.testCreationDirectory, "/") - .baseRef(this.webSiteURL) - .entity("useless entity") - .handle(PUT); + response = + new TestRequest(this.testCreationDirectory, "/") + .baseRef(this.webSiteURL) + .entity("useless entity") + .handle(PUT); assertEquals(SUCCESS_NO_CONTENT, response.getStatus()); - // Test 10c : Try to create a file with an unknown hierarchy of + // Test 10c: Try to create a file with an unknown hierarchy of // parent directories. The name and the metadata of the provided entity // don't match - response = new TestRequest(testCreationFile) - .baseRef(this.webSiteURL) - .entity("file entity") - .handle(PUT); + response = + new TestRequest(testCreationFile) + .baseRef(this.webSiteURL) + .entity("file entity") + .handle(PUT); assertEquals(CLIENT_ERROR_BAD_REQUEST, response.getStatus()); - // Test 10d : Try to create a file with an unknown hierarchy of + // Test 10d: Try to create a file with an unknown hierarchy of // parent directories. The name and the metadata of the provided entity // match - response = new TestRequest(testCreationTextFile) - .baseRef(this.webSiteURL) - .entity("file entity") - .handle(PUT); + response = + new TestRequest(testCreationTextFile) + .baseRef(this.webSiteURL) + .entity("file entity") + .handle(PUT); assertEquals(SUCCESS_CREATED, response.getStatus()); - // Test 11 : redirection for Directory - response = new TestRequest(testDirectoryUrl) - .baseRef(this.webSiteURL) - .entity("file entity") - .handle(GET); + // Test 11: redirection for Directory + response = + new TestRequest(testDirectoryUrl) + .baseRef(this.webSiteURL) + .entity("file entity") + .handle(GET); assertEquals(REDIRECTION_SEE_OTHER, response.getStatus()); assertEquals("http://myapplication/try/", response.getLocationRef().toString()); - // Test 12 : redirection for Directory with proxy forwarding - response = new TestRequest(testDirectoryUrl) - .baseRef(this.webSiteURL) - .header(new Header(HeaderConstants.HEADER_X_FORWARDED_PROTO, "https")) - .header(new Header(HeaderConstants.HEADER_X_FORWARDED_PORT, "123")) - .handle(GET); + // Test 12: redirection for Directory with proxy forwarding + response = + new TestRequest(testDirectoryUrl) + .baseRef(this.webSiteURL) + .header(new Header(HeaderConstants.HEADER_X_FORWARDED_PROTO, "https")) + .header(new Header(HeaderConstants.HEADER_X_FORWARDED_PORT, "123")) + .handle(GET); assertEquals(REDIRECTION_SEE_OTHER, response.getStatus()); assertEquals("https://myapplication:123/try/", response.getLocationRef().toString()); - // Test 13 : creation of resource with proxy forwarding - response = new TestRequest(this.percentEncodedFileUrl) - .baseRef(this.webSiteURL) - .entity("this is test") - .handle(PUT); + // Test 13: creation of resource with proxy forwarding + response = + new TestRequest(this.percentEncodedFileUrl) + .baseRef(this.webSiteURL) + .entity("this is test") + .handle(PUT); assertEquals(SUCCESS_CREATED, response.getStatus()); - response = new TestRequest(this.percentEncodedFileUrl) - .baseRef(this.webSiteURL) - .header(new Header(HeaderConstants.HEADER_X_FORWARDED_PROTO, "https")) - .header(new Header(HeaderConstants.HEADER_X_FORWARDED_PORT, "123")) - .handle(GET); - assertEquals("https://myapplication:123/a%20new%20%25file.txt.fr", response.getEntity().getLocationRef() - .toString()); + response = + new TestRequest(this.percentEncodedFileUrl) + .baseRef(this.webSiteURL) + .header(new Header(HeaderConstants.HEADER_X_FORWARDED_PROTO, "https")) + .header(new Header(HeaderConstants.HEADER_X_FORWARDED_PORT, "123")) + .handle(GET); + assertEquals( + "https://myapplication:123/a%20new%20%25file.txt.fr", + response.getEntity().getLocationRef().toString()); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/GenericAnnotatedServerResource.java b/org.restlet/src/test/java/org/restlet/resource/GenericAnnotatedServerResource.java index 697ab7f5af..e858a66858 100644 --- a/org.restlet/src/test/java/org/restlet/resource/GenericAnnotatedServerResource.java +++ b/org.restlet/src/test/java/org/restlet/resource/GenericAnnotatedServerResource.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; public class GenericAnnotatedServerResource extends AbstractGenericAnnotatedServerResource { diff --git a/org.restlet/src/test/java/org/restlet/resource/GenericServerResource16.java b/org.restlet/src/test/java/org/restlet/resource/GenericServerResource16.java index 8a63c66199..2564e40ec8 100644 --- a/org.restlet/src/test/java/org/restlet/resource/GenericServerResource16.java +++ b/org.restlet/src/test/java/org/restlet/resource/GenericServerResource16.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; public class GenericServerResource16 extends AbstractGenericAnnotatedServerResource { @@ -14,6 +13,5 @@ public class GenericServerResource16 extends AbstractGenericAnnotatedServerRe @Override public E addResponse(E representation) { return representation; - }; - + } } diff --git a/org.restlet/src/test/java/org/restlet/resource/GenericServerResource17.java b/org.restlet/src/test/java/org/restlet/resource/GenericServerResource17.java index daca53d726..a2d0fbfc5f 100644 --- a/org.restlet/src/test/java/org/restlet/resource/GenericServerResource17.java +++ b/org.restlet/src/test/java/org/restlet/resource/GenericServerResource17.java @@ -1,18 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; public class GenericServerResource17 extends ServerResource implements MyResource17 { public E add(E rep) { return rep; - }; - + } } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyBean.java b/org.restlet/src/test/java/org/restlet/resource/MyBean.java index 37f0d481ed..5a11b8352e 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyBean.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyBean.java @@ -1,21 +1,20 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; import java.io.Serializable; - +import java.util.Objects; import org.restlet.engine.util.SystemUtils; /** * Test bean to be serialized. - * + * * @author Jerome Louvel */ public class MyBean implements Serializable { @@ -26,8 +25,7 @@ public class MyBean implements Serializable { private String name; - public MyBean() { - } + public MyBean() {} public MyBean(String name, String description) { super(); @@ -35,23 +33,17 @@ public MyBean(String name, String description) { this.description = description; } + /** {@inheritDoc} */ @Override public boolean equals(Object obj) { - if (this == obj) + if (obj == this) { return true; - if (obj == null) + } + if (!(obj instanceof MyBean that)) { return false; - if (getClass() != obj.getClass()) - return false; - MyBean other = (MyBean) obj; - if (description == null) { - if (other.description != null) - return false; - } else if (!description.equals(other.description)) - return false; - if (name == null) { - return other.name == null; - } else return name.equals(other.name); + } + return Objects.equals(getName(), that.getName()) + && Objects.equals(getDescription(), that.getDescription()); } public String getDescription() { @@ -74,5 +66,4 @@ public void setDescription(String description) { public void setName(String name) { this.name = name; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyException01.java b/org.restlet/src/test/java/org/restlet/resource/MyException01.java index 620bf91493..e73bd1fabe 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyException01.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyException01.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; import java.util.Date; @@ -18,8 +17,7 @@ public class MyException01 extends Throwable { private Date date; - public MyException01() { - } + public MyException01() {} public MyException01(Date date) { this.date = date; @@ -32,5 +30,4 @@ public Date getDate() { public void setDate(Date date) { this.date = date; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyException02.java b/org.restlet/src/test/java/org/restlet/resource/MyException02.java index 5ff4e2055d..6a51f099f9 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyException02.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyException02.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; @Status(value = 400) diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource01.java b/org.restlet/src/test/java/org/restlet/resource/MyResource01.java index 6f1a74a078..366a4187ee 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource01.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource01.java @@ -1,17 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** * Sample annotated interface. - * + * * @author Jerome Louvel */ public interface MyResource01 { @@ -30,5 +29,4 @@ public interface MyResource01 { @Options("txt") String describe(); - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource02.java b/org.restlet/src/test/java/org/restlet/resource/MyResource02.java index 786b164cb4..921faa6271 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource02.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource02.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; import org.restlet.data.MediaType; @@ -19,5 +18,4 @@ public class MyResource02 extends ServerResource { public Representation represent() { return new StringRepresentation("", MediaType.TEXT_XML); } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource03.java b/org.restlet/src/test/java/org/restlet/resource/MyResource03.java index f62e953694..a58ccc22eb 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource03.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource03.java @@ -1,17 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** * Sample server resource that sets the "existing" flag to false. - * + * * @author Jerome Louvel */ public class MyResource03 extends ServerResource implements MyResource01 { @@ -45,5 +44,4 @@ public String store(MyBean bean) { myBean = bean; return "Done"; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource04.java b/org.restlet/src/test/java/org/restlet/resource/MyResource04.java index 2c0240b31f..e096da6383 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource04.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource04.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; public class MyResource04 extends ServerResource { @@ -25,5 +24,4 @@ public String toJson() { public String toHtml() { return "root"; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource05.java b/org.restlet/src/test/java/org/restlet/resource/MyResource05.java index a036ccef83..b085e3a0cf 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource05.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource05.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; public class MyResource05 extends ServerResource { @@ -20,5 +19,4 @@ public String storeXml(String entity) { public String storeJson(String entity) { return entity; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource06.java b/org.restlet/src/test/java/org/restlet/resource/MyResource06.java index 206681092a..92b1f49996 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource06.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource06.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; import java.io.IOException; @@ -22,5 +21,4 @@ public String storeXml(String entity) throws IOException { public String storeJson(String entity) throws IOException { return entity + "2"; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource07.java b/org.restlet/src/test/java/org/restlet/resource/MyResource07.java index f38a0f867a..a1fdf51f88 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource07.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource07.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; public class MyResource07 extends ServerResource { @@ -20,5 +19,4 @@ public String storeJson(String entity) { public String storeXml(String entity) { return entity + "2"; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource08.java b/org.restlet/src/test/java/org/restlet/resource/MyResource08.java index cb0eb69e41..9b7d3640d7 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource08.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource08.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; public class MyResource08 extends ServerResource { @@ -20,5 +19,4 @@ public String store1(String entity) { public String store2(String entity) { return entity + "2"; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource12.java b/org.restlet/src/test/java/org/restlet/resource/MyResource12.java index 9ab5705ec1..09aae24e14 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource12.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource12.java @@ -1,19 +1,18 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; import org.restlet.data.Form; /** * Sample annotated interface. - * + * * @author Jerome Louvel */ public interface MyResource12 { @@ -23,5 +22,4 @@ public interface MyResource12 { @Put void store(Form form); - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource17.java b/org.restlet/src/test/java/org/restlet/resource/MyResource17.java index 2294da3ab3..27e92ee1cd 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource17.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource17.java @@ -1,17 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; public interface MyResource17 { @Post R add(R rep); - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyResource20.java b/org.restlet/src/test/java/org/restlet/resource/MyResource20.java index 7284564db4..1da8bfc1d4 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyResource20.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyResource20.java @@ -1,17 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** * Sample annotated interface. - * + * * @author Jerome Louvel */ public interface MyResource20 { @@ -21,5 +20,4 @@ public interface MyResource20 { @Put MyBean representAndSerializeException() throws MyException02; - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyServerResource01.java b/org.restlet/src/test/java/org/restlet/resource/MyServerResource01.java index 031b099644..f690e0409e 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyServerResource01.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyServerResource01.java @@ -1,17 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** * Sample server resource. - * + * * @author Jerome Louvel */ public class MyServerResource01 extends ServerResource implements MyResource01 { @@ -39,5 +38,4 @@ public String store(MyBean bean) { myBean = bean; return "Done"; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyServerResource12.java b/org.restlet/src/test/java/org/restlet/resource/MyServerResource12.java index 84e17714e9..4619097aea 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyServerResource12.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyServerResource12.java @@ -1,19 +1,18 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; import org.restlet.data.Form; /** * Sample server resource. - * + * * @author Jerome Louvel */ public class MyServerResource12 extends ServerResource implements MyResource12 { @@ -27,5 +26,4 @@ public void store(Form form) { public Form represent() { return myForm; } - } diff --git a/org.restlet/src/test/java/org/restlet/resource/MyServerResource15.java b/org.restlet/src/test/java/org/restlet/resource/MyServerResource15.java index 236fbad80a..aea87d3203 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyServerResource15.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyServerResource15.java @@ -1,17 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** * Sample server resource implementing abstract generic class. - * + * * @author Jerome Louvel */ public class MyServerResource15 extends AbstractGenericAnnotatedServerResource { diff --git a/org.restlet/src/test/java/org/restlet/resource/MyServerResource15Ok.java b/org.restlet/src/test/java/org/restlet/resource/MyServerResource15Ok.java index e12a1ad20b..bd05c2c938 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyServerResource15Ok.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyServerResource15Ok.java @@ -1,17 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** * Sample server resource implementing abstract generic class. - * + * * @author Thierry Boileau */ public class MyServerResource15Ok extends AbstractGenericAnnotatedServerResource { diff --git a/org.restlet/src/test/java/org/restlet/resource/MyServerResource16.java b/org.restlet/src/test/java/org/restlet/resource/MyServerResource16.java index ab2119dd8b..efa9974772 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyServerResource16.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyServerResource16.java @@ -1,19 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** * Sample server resource implementing generic class. - * + * * @author Jerome Louvel */ -public class MyServerResource16 extends GenericServerResource16 { - -} +public class MyServerResource16 extends GenericServerResource16 {} diff --git a/org.restlet/src/test/java/org/restlet/resource/MyServerResource17.java b/org.restlet/src/test/java/org/restlet/resource/MyServerResource17.java index 30972d9504..892ed61a51 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyServerResource17.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyServerResource17.java @@ -1,19 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** * Sample server resource implementing generic class. - * + * * @author Jerome Louvel */ -public class MyServerResource17 extends GenericServerResource17 { - -} +public class MyServerResource17 extends GenericServerResource17 {} diff --git a/org.restlet/src/test/java/org/restlet/resource/MyServerResource18.java b/org.restlet/src/test/java/org/restlet/resource/MyServerResource18.java index 7ccb1286b0..59aec50486 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyServerResource18.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyServerResource18.java @@ -1,19 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; /** * Sample server resource implementing generic class. - * + * * @author Jerome Louvel */ -public class MyServerResource18 extends GenericAnnotatedServerResource { - -} +public class MyServerResource18 extends GenericAnnotatedServerResource {} diff --git a/org.restlet/src/test/java/org/restlet/resource/MyServerResource20.java b/org.restlet/src/test/java/org/restlet/resource/MyServerResource20.java index 4bc9c92df3..1e326b90da 100644 --- a/org.restlet/src/test/java/org/restlet/resource/MyServerResource20.java +++ b/org.restlet/src/test/java/org/restlet/resource/MyServerResource20.java @@ -1,22 +1,20 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.resource; import java.util.Date; - import org.restlet.Server; import org.restlet.data.Protocol; /** * Sample server resource. - * + * * @author Jerome Louvel */ public class MyServerResource20 extends ServerResource implements MyResource20 { @@ -35,5 +33,4 @@ public MyBean represent() throws MyException01 { public MyBean representAndSerializeException() throws MyException02 { throw new MyException02("my custom error"); } - } diff --git a/org.restlet/src/test/java/org/restlet/routing/AbstractFilterTestCase.java b/org.restlet/src/test/java/org/restlet/routing/AbstractFilterTestCase.java index e88b04ce5c..1e1740f532 100644 --- a/org.restlet/src/test/java/org/restlet/routing/AbstractFilterTestCase.java +++ b/org.restlet/src/test/java/org/restlet/routing/AbstractFilterTestCase.java @@ -1,62 +1,61 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import org.junit.jupiter.api.Test; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; -import static org.junit.jupiter.api.Assertions.*; - /** * Tests where every Filter should run through. - * + * * @author Lars Heuer */ public abstract class AbstractFilterTestCase { /** * Returns a Filter to be used for the tests. - * + * * @return Filter instance. */ protected abstract Filter getFilter(); /** * Returns a request. - * + * * @return Request instance. */ protected abstract Request getRequest(); /** * Returns a response. - * - * @param request - * The associated request. + * + * @param request The associated request. * @return Response instance. */ protected abstract Response getResponse(Request request); /** * Returns a restlet. - * + * * @return Restlet instance. */ protected abstract Restlet getRestlet(); - /** - * Test Restlet instance attaching/detaching. - */ + /** Test Restlet instance attaching/detaching. */ @Test - public void testAttachDetachInstance() throws Exception { + void testAttachDetachInstance() throws Exception { final Filter filter = getFilter(); assertFalse(filter.hasNext()); filter.setNext(getRestlet()); @@ -71,11 +70,9 @@ public void testAttachDetachInstance() throws Exception { assertFalse(filter.hasNext()); } - /** - * Test not started Filter. - */ + /** Test not started Filter. */ @Test - public void testIllegalStartedState() { + void testIllegalStartedState() { final Filter filter = getFilter(); filter.setNext(getRestlet()); assertTrue(filter.hasNext()); @@ -94,11 +91,9 @@ public void testIllegalStartedState() { } } - /** - * Test with null target. - */ + /** Test with null target. */ @Test - public void testIllegalTarget() throws Exception { + void testIllegalTarget() throws Exception { final Filter filter = getFilter(); filter.start(); assertTrue(filter.isStarted()); @@ -109,5 +104,4 @@ public void testIllegalTarget() throws Exception { assertThrows(Exception.class, () -> filter.handle(request, response)); } - } diff --git a/org.restlet/src/test/java/org/restlet/routing/FilterTestCase.java b/org.restlet/src/test/java/org/restlet/routing/FilterTestCase.java index da93437754..39e661bfc9 100644 --- a/org.restlet/src/test/java/org/restlet/routing/FilterTestCase.java +++ b/org.restlet/src/test/java/org/restlet/routing/FilterTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; import org.restlet.Request; @@ -15,9 +14,8 @@ /** * Test {@link org.restlet.routing.Filter}. - * - * @author Lars Heuer (heuer[at]semagia.com) Semagia + * + * @author Lars Heuer (heuer[at]semagia.com) Semagia */ public class FilterTestCase extends AbstractFilterTestCase { @Override @@ -39,5 +37,4 @@ protected Response getResponse(Request request) { protected Restlet getRestlet() { return new MockRestlet(null); } - } diff --git a/org.restlet/src/test/java/org/restlet/routing/MockFilter.java b/org.restlet/src/test/java/org/restlet/routing/MockFilter.java index 45181dad08..c0291f4856 100644 --- a/org.restlet/src/test/java/org/restlet/routing/MockFilter.java +++ b/org.restlet/src/test/java/org/restlet/routing/MockFilter.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; import org.restlet.Context; @@ -14,31 +13,29 @@ import org.restlet.Response; /** - * Thin layer around an AbstractFilter. Takes care about being started and - * having a target when it should handle a call. - * - * Concurrency note: instances of this class or its subclasses can be invoked by - * several threads at the same time and therefore must be thread-safe. You - * should be especially careful when storing state in member variables. - * - * @author Lars Heuer (heuer[at]semagia.com) - * Semagia + * Thin layer around an AbstractFilter. Takes care about being started and having a target when it + * should handle a call. + * + *

Concurrency note: instances of this class or its subclasses can be invoked by several threads + * at the same time and therefore must be thread-safe. You should be especially careful when storing + * state in member variables. + * + * @author Lars Heuer (heuer[at]semagia.com) Semagia */ public class MockFilter extends Filter { - public MockFilter(Context context) { - super(context); - } - - @Override - protected int beforeHandle(Request request, Response response) { - if (!super.isStarted()) { - throw new IllegalStateException("Filter is not started"); - } - if (!super.hasNext()) { - throw new IllegalStateException("Target is not set"); - } + public MockFilter(Context context) { + super(context); + } - return CONTINUE; - } + @Override + protected int beforeHandle(Request request, Response response) { + if (!super.isStarted()) { + throw new IllegalStateException("Filter is not started"); + } + if (!super.hasNext()) { + throw new IllegalStateException("Target is not set"); + } + return CONTINUE; + } } diff --git a/org.restlet/src/test/java/org/restlet/routing/MockRestlet.java b/org.restlet/src/test/java/org/restlet/routing/MockRestlet.java index 48556f7917..f15d4cc068 100644 --- a/org.restlet/src/test/java/org/restlet/routing/MockRestlet.java +++ b/org.restlet/src/test/java/org/restlet/routing/MockRestlet.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; import org.restlet.Context; @@ -15,11 +14,10 @@ import org.restlet.Restlet; /** - * Thin layer around an AbstractRestlet. Takes care about being started when it - * should handle a call. - * - * @author Lars Heuer (heuer[at]semagia.com) Semagia + * Thin layer around an AbstractRestlet. Takes care about being started when it should handle a + * call. + * + * @author Lars Heuer (heuer[at]semagia.com) Semagia */ public class MockRestlet extends Restlet { public MockRestlet(Context context) { diff --git a/org.restlet/src/test/java/org/restlet/routing/RedirectTestCase.java b/org.restlet/src/test/java/org/restlet/routing/RedirectTestCase.java index 528c3c04ee..3ccd668b33 100644 --- a/org.restlet/src/test/java/org/restlet/routing/RedirectTestCase.java +++ b/org.restlet/src/test/java/org/restlet/routing/RedirectTestCase.java @@ -1,40 +1,41 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.restlet.*; +import org.restlet.Component; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; import org.restlet.data.MediaType; import org.restlet.data.Method; import org.restlet.data.Protocol; import org.restlet.engine.Engine; import org.restlet.representation.StringRepresentation; -import static java.lang.String.format; -import static org.junit.jupiter.api.Assertions.assertNotNull; - /** * Unit tests for the RedirectRestlet. - * + * * @author Jerome Louvel */ -public class RedirectTestCase { +class RedirectTestCase { private static final int TEST_PORT = 1337; - private void testCall(Context context, Method method, String uri) - throws Exception { - final Response response = context.getClientDispatcher().handle( - new Request(method, uri)); + private void testCall(Context context, Method method, String uri) throws Exception { + final Response response = context.getClientDispatcher().handle(new Request(method, uri)); assertNotNull(response.getEntity()); response.getEntity().write(System.out); } @@ -51,11 +52,9 @@ public void tearDown() { Engine.register(); } - /** - * Tests the cookies parsing. - */ + /** Tests the cookies parsing. */ @Test - public void testRedirect() throws Exception { + void testRedirect() throws Exception { // Create components final Component clientComponent = new Component(); final Component proxyComponent = new Component(); @@ -67,22 +66,34 @@ public void testRedirect() throws Exception { // Create the proxy Restlet final String target = "http://localhost:" + (TEST_PORT + 1) + "{rr}"; - final Redirector proxy = new Redirector(proxyComponent.getContext() - .createChildContext(), target, Redirector.MODE_SERVER_OUTBOUND); + final Redirector proxy = + new Redirector( + proxyComponent.getContext().createChildContext(), + target, + Redirector.MODE_SERVER_OUTBOUND); // Create a new Restlet that will display some path information. - final Restlet trace = new Restlet(originComponent.getContext() - .createChildContext()) { - @Override - public void handle(Request request, Response response) { - // Print the requested URI path - final String message = "Resource URI: " + request.getResourceRef() + '\n' - + "Base URI: " + request.getResourceRef().getBaseRef() + '\n' - + "Remaining part: " + request.getResourceRef().getRemainingPart() + '\n' - + "Method name: " + request.getMethod() + '\n'; - response.setEntity(new StringRepresentation(message, MediaType.TEXT_PLAIN)); - } - }; + final Restlet trace = + new Restlet(originComponent.getContext().createChildContext()) { + @Override + public void handle(Request request, Response response) { + // Print the requested URI path + final String message = + "Resource URI: " + + request.getResourceRef() + + '\n' + + "Base URI: " + + request.getResourceRef().getBaseRef() + + '\n' + + "Remaining part: " + + request.getResourceRef().getRemainingPart() + + '\n' + + "Method name: " + + request.getMethod() + + '\n'; + response.setEntity(new StringRepresentation(message, MediaType.TEXT_PLAIN)); + } + }; // Set the component roots proxyComponent.getDefaultHost().attach("", proxy); @@ -107,9 +118,11 @@ public void handle(Request request, Response response) { testCall(context, Method.GET, uri); testCall(context, Method.DELETE, uri); - uri = "http://localhost:" + TEST_PORT - + "/v1/client/kwse/CnJlNUQV9%252BNNqbUf7Lhs2BYEK2Y%253D" - + "/user/johnm/uVGYTDK4kK4zsu96VHGeTCzfwso%253D/"; + uri = + "http://localhost:" + + TEST_PORT + + "/v1/client/kwse/CnJlNUQV9%252BNNqbUf7Lhs2BYEK2Y%253D" + + "/user/johnm/uVGYTDK4kK4zsu96VHGeTCzfwso%253D/"; testCall(context, Method.GET, uri); // Stop the components diff --git a/org.restlet/src/test/java/org/restlet/routing/RouteListTestCase.java b/org.restlet/src/test/java/org/restlet/routing/RouteListTestCase.java index 10514bf6ef..163a00446f 100644 --- a/org.restlet/src/test/java/org/restlet/routing/RouteListTestCase.java +++ b/org.restlet/src/test/java/org/restlet/routing/RouteListTestCase.java @@ -1,27 +1,29 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.junit.jupiter.api.Test; import org.restlet.Request; import org.restlet.Response; import org.restlet.util.RouteList; -import static org.junit.jupiter.api.Assertions.*; - /** * Test case for RouteList class. - * + * * @author Kevin Conaway */ -public class RouteListTestCase { +class RouteListTestCase { static class MockScoringRoute extends Route { int score; @@ -38,7 +40,7 @@ public float score(Request request, Response response) { } @Test - public void testGetLast() { + void testGetLast() { final RouteList list = new RouteList(); assertNull(list.getLast(null, null, 1f)); @@ -54,7 +56,7 @@ public void testGetLast() { } @Test - public void testGetNext() { + void testGetNext() { final RouteList list = new RouteList(); assertNull(list.getNext(null, null, 1f)); @@ -75,7 +77,7 @@ public void testGetNext() { } @Test - public void testGetRandom() { + void testGetRandom() { final RouteList list = new RouteList(); assertNull(list.getRandom(null, null, 1f)); @@ -90,13 +92,11 @@ public void testGetRandom() { list.add(new MockScoringRoute(7)); list.add(new MockScoringRoute(8)); - final MockScoringRoute r = (MockScoringRoute) list.getRandom(null, - null, 5f); + final MockScoringRoute r = (MockScoringRoute) list.getRandom(null, null, 5f); assertNotNull(r); assertTrue(r.score > 5); assertNull(list.getRandom(null, null, 9f)); } - } diff --git a/org.restlet/src/test/java/org/restlet/routing/TemplateTestCase.java b/org.restlet/src/test/java/org/restlet/routing/TemplateTestCase.java index c2fc229fb9..7e1f2e1ec1 100644 --- a/org.restlet/src/test/java/org/restlet/routing/TemplateTestCase.java +++ b/org.restlet/src/test/java/org/restlet/routing/TemplateTestCase.java @@ -1,38 +1,35 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; -import org.junit.jupiter.api.Test; -import org.restlet.engine.Engine; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; import java.util.List; import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.restlet.engine.Engine; /** * Test case for URI templates. - * + * * @author Jerome Louvel */ -public class TemplateTestCase { +class TemplateTestCase { @Test - public void testEncodedCharacters() { - Template template = new Template( - "http://localhost/{token}/bookstore/{bookid}"); - String encodedToken = "FtDF91VSX%2F7AN6C39k51ZV510SW%2Fot6SIGstq8XGCcHfOfHbZOZLUD4u%2BGUNK0bBawVZ4GR5TgV7PtRbF%2Bnm9abYJN6AWycdj9J6CLyU4D7Zou36KEjkel%2B0LtlGGhFPVrCvpBuqPy8V8o5IZ9tDys0Py6sXXAtEVbXBYeRYzOvIBzOZkIviIyceVCU%2BlYv%2Fh9k7Fhlb1JGtKUCj3ZDg%2FvJ1Co7dOC1Ho3%2Fe0Fup7k9qgTuCvZRSHcpizaEFPNLp"; - String targetUri = "http://localhost/" + encodedToken - + "/bookstore/1234"; + void testEncodedCharacters() { + Template template = new Template("http://localhost/{token}/bookstore/{bookid}"); + String encodedToken = + "FtDF91VSX%2F7AN6C39k51ZV510SW%2Fot6SIGstq8XGCcHfOfHbZOZLUD4u%2BGUNK0bBawVZ4GR5TgV7PtRbF%2Bnm9abYJN6AWycdj9J6CLyU4D7Zou36KEjkel%2B0LtlGGhFPVrCvpBuqPy8V8o5IZ9tDys0Py6sXXAtEVbXBYeRYzOvIBzOZkIviIyceVCU%2BlYv%2Fh9k7Fhlb1JGtKUCj3ZDg%2FvJ1Co7dOC1Ho3%2Fe0Fup7k9qgTuCvZRSHcpizaEFPNLp"; + String targetUri = "http://localhost/" + encodedToken + "/bookstore/1234"; Map variables1 = new HashMap<>(); int parsed1 = template.parse(targetUri, variables1); @@ -41,7 +38,7 @@ public void testEncodedCharacters() { } @Test - public void testPathMatching() { + void testPathMatching() { Template template = new Template("http://www.mydomain.com/abc/{v1}"); template.setMatchingMode(Template.MODE_STARTS_WITH); template.getDefaultVariable().setType(Variable.TYPE_URI_PATH); @@ -66,9 +63,8 @@ public void testPathMatching() { } @Test - public void testVariableNames() { - Template tpl = new Template( - "http://{userId}.restlet.com/invoices/{invoiceId}"); + void testVariableNames() { + Template tpl = new Template("http://{userId}.restlet.com/invoices/{invoiceId}"); tpl.setLogger(Engine.getAnonymousLogger()); List names = tpl.getVariableNames(); @@ -78,7 +74,7 @@ public void testVariableNames() { } @Test - public void testWithPercentChars() { + void testWithPercentChars() { Template template = new Template("abc/{v1}"); template.getDefaultVariable().setType(Variable.TYPE_URI_ALL); Map variables1 = new HashMap<>(); diff --git a/org.restlet/src/test/java/org/restlet/routing/TraceRestlet.java b/org.restlet/src/test/java/org/restlet/routing/TraceRestlet.java index ef380da58f..f27137b792 100644 --- a/org.restlet/src/test/java/org/restlet/routing/TraceRestlet.java +++ b/org.restlet/src/test/java/org/restlet/routing/TraceRestlet.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; import org.restlet.Context; @@ -17,7 +16,7 @@ /** * Trace Restlet. - * + * * @author Jerome Louvel */ public class TraceRestlet extends Restlet { @@ -27,21 +26,20 @@ public TraceRestlet(Context context) { /** * Handles a uniform call. - * - * @param request - * The request to handle. - * @param response - * The response to update. + * + * @param request The request to handle. + * @param response The response to update. */ @Override public void handle(Request request, Response response) { - final String message = "Hello World!" - + "\nYour IP address is " - + request.getClientInfo().getAddress() - + "\nYour request URI is: " - + ((request.getResourceRef() == null) ? "?" : request - .getResourceRef().toString()); + final String message = + "Hello World!" + + "\nYour IP address is " + + request.getClientInfo().getAddress() + + "\nYour request URI is: " + + ((request.getResourceRef() == null) + ? "?" + : request.getResourceRef().toString()); response.setEntity(message, MediaType.TEXT_PLAIN); } - } diff --git a/org.restlet/src/test/java/org/restlet/routing/ValidatorTestCase.java b/org.restlet/src/test/java/org/restlet/routing/ValidatorTestCase.java index dc26ee3bb2..b7590d78bc 100644 --- a/org.restlet/src/test/java/org/restlet/routing/ValidatorTestCase.java +++ b/org.restlet/src/test/java/org/restlet/routing/ValidatorTestCase.java @@ -1,30 +1,29 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.routing; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.Status; -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test {@link org.restlet.routing.Validator}. * * @author Jerome Louvel */ -public class ValidatorTestCase { +class ValidatorTestCase { @Test - public void testRequired() { + void testRequired() { // Create mock call Request rq = new Request(); Response rs = new Response(rq); @@ -46,7 +45,7 @@ public void testRequired() { } @Test - public void testFormat() { + void testFormat() { // Create mock call Request rq = new Request(); Response rs = new Response(rq); diff --git a/org.restlet/src/test/java/org/restlet/security/HelloWorldRestlet.java b/org.restlet/src/test/java/org/restlet/security/HelloWorldRestlet.java index eb18476f7d..bff810c43d 100644 --- a/org.restlet/src/test/java/org/restlet/security/HelloWorldRestlet.java +++ b/org.restlet/src/test/java/org/restlet/security/HelloWorldRestlet.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import org.restlet.Request; @@ -16,7 +15,7 @@ /** * Reusable hello world Restlet. - * + * * @author Jerome Louvel */ public class HelloWorldRestlet extends Restlet { @@ -25,5 +24,4 @@ public class HelloWorldRestlet extends Restlet { public void handle(Request request, Response response) { response.setEntity("hello, world", MediaType.TEXT_PLAIN); } - } diff --git a/org.restlet/src/test/java/org/restlet/security/HttpBasicTestCase.java b/org.restlet/src/test/java/org/restlet/security/HttpBasicTestCase.java index 328c2ab542..0ff9ddc764 100644 --- a/org.restlet/src/test/java/org/restlet/security/HttpBasicTestCase.java +++ b/org.restlet/src/test/java/org/restlet/security/HttpBasicTestCase.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -14,7 +13,6 @@ import java.util.Arrays; import java.util.stream.Stream; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -42,147 +40,138 @@ * @author Stian Soiland * @author Jerome Louvel */ -public class HttpBasicTestCase { - - public static class AuthenticatedRestlet extends Restlet { - @Override - public void handle(Request request, Response response) { - response.setEntity(AUTHENTICATED_MSG, MediaType.TEXT_PLAIN); - } - } - - public static class TestVerifier extends MapVerifier { - public TestVerifier() { - getLocalSecrets().put(SHORT_USERNAME, SHORT_PASSWORD.toCharArray()); - getLocalSecrets().put(LONG_USERNAME, LONG_PASSWORD.toCharArray()); - } - - @Override - public int verify(String identifier, char[] inputSecret) { - // NOTE: Allocating Strings are not really secure treatment of passwords - String almostSecret = new String(inputSecret); - - try { - return super.verify(identifier, inputSecret); - } finally { - // Clear secret from memory as soon as possible (This is better - // treatment, but useless due to our almostSecret copy) - Arrays.fill(inputSecret, '\000'); - } - } - } - - public static class BlockerVerifier implements Verifier { - @Override - public int verify(Request request, Response response) { - return RESULT_INVALID; - } - } - - public static final String AUTHENTICATED_MSG = "You are authenticated"; - - public static final String LONG_PASSWORD = "thisLongPasswordIsExtremelySecure"; - - public static final String LONG_USERNAME = "aVeryLongUsernameIsIndeedRequiredForThisTest"; - - public static final String SHORT_PASSWORD = "pw15"; - - public static final String SHORT_USERNAME = "user13"; - - public static final String WRONG_USERNAME = "wrongUser"; - - static Stream invalidCredentials() { - return Stream.of(arguments(LONG_USERNAME, SHORT_PASSWORD), arguments(SHORT_USERNAME, LONG_PASSWORD), - arguments(WRONG_USERNAME, SHORT_PASSWORD)); - } - - static Stream validCredentials() { - return Stream.of(arguments(LONG_USERNAME, LONG_PASSWORD), arguments(SHORT_USERNAME, SHORT_PASSWORD)); - } - - @Nested - class TestMapVerifier { - - private final MapVerifier verifier = new TestVerifier(); - - @ParameterizedTest - @MethodSource("org.restlet.security.HttpBasicTestCase#invalidCredentials") - void testInvalidCredentials(final String login, final String password) { - assertEquals(Verifier.RESULT_INVALID, this.verifier.verify(login, password.toCharArray())); - } - - @ParameterizedTest - @MethodSource("org.restlet.security.HttpBasicTestCase#validCredentials") - void testValidCredentials(final String login, final String password) { - assertEquals(Verifier.RESULT_VALID, this.verifier.verify(login, password.toCharArray())); - } - - } - - @Nested - class TestHttpBasicServer { - private Component component; - private Request request; - private Client client; - - @Test - public void HttpBasicNone() throws Exception { - final Response response = client.handle(request); - assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); - } - - @ParameterizedTest - @MethodSource("org.restlet.security.HttpBasicTestCase#invalidCredentials") - void testInvalidCredentials(final String login, final String password) throws Exception { - ChallengeResponse authentication = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, login, password); - request.setChallengeResponse(authentication); - - final Response response = client.handle(request); - assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); - } - - @ParameterizedTest - @MethodSource("org.restlet.security.HttpBasicTestCase#validCredentials") - void testValidCredentials(final String login, final String password) throws Exception { - ChallengeResponse authentication = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, login, password); - request.setChallengeResponse(authentication); - - final Response response = client.handle(request); - assertEquals(Status.SUCCESS_OK, response.getStatus()); - assertEquals(AUTHENTICATED_MSG, response.getEntity().getText()); - } - - @BeforeEach - public void makeServer() throws Exception { - final String REALM = HttpBasicTestCase.class.getSimpleName(); - this.component = new Component(); - final Server server = this.component.getServers().add(Protocol.HTTP, 0); - - final Application application = new Application() { - @Override - public Restlet createInboundRoot() { - ChallengeAuthenticator authenticator = new ChallengeAuthenticator(getContext(), - ChallengeScheme.HTTP_BASIC, REALM); - authenticator.setVerifier(new TestVerifier()); - authenticator.setNext(new AuthenticatedRestlet()); - return authenticator; - } - }; - - this.component.getDefaultHost().attach(application); - this.component.start(); - request = new Request(Method.GET, "http://localhost:" + server.getActualPort()); - client = new Client(Protocol.HTTP); - } - - @AfterEach - public void cleanup() throws Exception { - client.stop(); - if (this.component.isStarted()) { - this.component.stop(); - } - this.component = null; - } - } - +class HttpBasicTestCase { + + public static final String AUTHENTICATED_MSG = "You are authenticated"; + public static final String LONG_PASSWORD = "thisLongPasswordIsExtremelySecure"; + public static final String LONG_USERNAME = "aVeryLongUsernameIsIndeedRequiredForThisTest"; + public static final String SHORT_PASSWORD = "pw15"; + public static final String SHORT_USERNAME = "user13"; + public static final String WRONG_USERNAME = "wrongUser"; + + static Stream invalidCredentials() { + return Stream.of( + arguments(LONG_USERNAME, SHORT_PASSWORD), + arguments(SHORT_USERNAME, LONG_PASSWORD), + arguments(WRONG_USERNAME, SHORT_PASSWORD)); + } + + static Stream validCredentials() { + return Stream.of( + arguments(LONG_USERNAME, LONG_PASSWORD), arguments(SHORT_USERNAME, SHORT_PASSWORD)); + } + + public static class AuthenticatedRestlet extends Restlet { + @Override + public void handle(Request request, Response response) { + response.setEntity(AUTHENTICATED_MSG, MediaType.TEXT_PLAIN); + } + } + + public static class TestVerifier extends MapVerifier { + public TestVerifier() { + getLocalSecrets().put(SHORT_USERNAME, SHORT_PASSWORD.toCharArray()); + getLocalSecrets().put(LONG_USERNAME, LONG_PASSWORD.toCharArray()); + } + + @Override + public int verify(String identifier, char[] inputSecret) { + try { + return super.verify(identifier, inputSecret); + } finally { + // Clear secret from memory as soon as possible + Arrays.fill(inputSecret, '\000'); + } + } + } + + @Nested + class TestMapVerifier { + + private final MapVerifier verifier = new TestVerifier(); + + @ParameterizedTest + @MethodSource("org.restlet.security.HttpBasicTestCase#invalidCredentials") + void testInvalidCredentials(final String login, final String password) { + assertEquals( + Verifier.RESULT_INVALID, this.verifier.verify(login, password.toCharArray())); + } + + @ParameterizedTest + @MethodSource("org.restlet.security.HttpBasicTestCase#validCredentials") + void testValidCredentials(final String login, final String password) { + assertEquals( + Verifier.RESULT_VALID, this.verifier.verify(login, password.toCharArray())); + } + } + + @Nested + class TestHttpBasicServer { + private Component component; + private Request request; + private Client client; + + @Test + void HttpBasicNone() { + final Response response = client.handle(request); + assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); + } + + @ParameterizedTest + @MethodSource("org.restlet.security.HttpBasicTestCase#invalidCredentials") + void testInvalidCredentials(final String login, final String password) { + ChallengeResponse authentication = + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, login, password); + request.setChallengeResponse(authentication); + + final Response response = client.handle(request); + assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); + } + + @ParameterizedTest + @MethodSource("org.restlet.security.HttpBasicTestCase#validCredentials") + void testValidCredentials(final String login, final String password) throws Exception { + ChallengeResponse authentication = + new ChallengeResponse(ChallengeScheme.HTTP_BASIC, login, password); + request.setChallengeResponse(authentication); + + final Response response = client.handle(request); + assertEquals(Status.SUCCESS_OK, response.getStatus()); + assertEquals(AUTHENTICATED_MSG, response.getEntity().getText()); + } + + @BeforeEach + void makeServer() throws Exception { + final String REALM = HttpBasicTestCase.class.getSimpleName(); + this.component = new Component(); + final Server server = this.component.getServers().add(Protocol.HTTP, 0); + + final Application application = + new Application() { + @Override + public Restlet createInboundRoot() { + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator( + getContext(), ChallengeScheme.HTTP_BASIC, REALM); + authenticator.setVerifier(new TestVerifier()); + authenticator.setNext(new AuthenticatedRestlet()); + return authenticator; + } + }; + + this.component.getDefaultHost().attach(application); + this.component.start(); + request = new Request(Method.GET, "http://localhost:" + server.getActualPort()); + client = new Client(Protocol.HTTP); + } + + @AfterEach + void cleanup() throws Exception { + client.stop(); + if (this.component.isStarted()) { + this.component.stop(); + } + this.component = null; + } + } } diff --git a/org.restlet/src/test/java/org/restlet/security/MemoryRealmTest.java b/org.restlet/src/test/java/org/restlet/security/MemoryRealmTest.java index 7800d34e49..1a6053993d 100644 --- a/org.restlet/src/test/java/org/restlet/security/MemoryRealmTest.java +++ b/org.restlet/src/test/java/org/restlet/security/MemoryRealmTest.java @@ -1,72 +1,55 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file are subject to the terms of one of the following - * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can - * select the license that you prefer but you may not use this file except in - * compliance with one of these Licenses. - * - * You can obtain a copy of the Apache 2.0 license at - * http://www.opensource.org/licenses/apache-2.0 - * - * You can obtain a copy of the EPL 1.0 license at - * http://www.opensource.org/licenses/eclipse-1.0 - * - * See the Licenses for the specific language governing permissions and - * limitations under the Licenses. - * - * Alternatively, you can obtain a royalty free commercial license with less - * limitations, transferable or non-transferable, directly at - * https://restlet.talend.com/ - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -public class MemoryRealmTest { - - @Test - public void whenUnmappingAGroupAndRoleFromAMemoryRealmThenMappingIsDropped() { - // given a Memory Realm, a Group and a Role - MemoryRealm memoryRealm = new MemoryRealm(); - Group group = new Group(); - Role role = new Role(); - - // Given the group and role are mapped - memoryRealm.map(group, role); - // Then there is a mapping for this group - assertFalse(memoryRealm.findRoles(group).isEmpty()); - - // When I remove this mapping - memoryRealm.unmap(group, role); - - // Then the memory realm has no more mapping - assertTrue(memoryRealm.findRoles(group).isEmpty()); - } - - @Test - public void whenUnmappingAUserAndRoleFromAMemoryRealmThenMappingIsDropped() { - // given a Memory Realm, a Group and a Role - MemoryRealm memoryRealm = new MemoryRealm(); - User user = new User(); - Role role = new Role(); - - // Given the user and role are mapped - memoryRealm.map(user, role); - // Then there is a mapping for this user - assertFalse(memoryRealm.findRoles(user).isEmpty()); - - // When I remove this mapping - memoryRealm.unmap(user, role); - - // Then the memory realm has no more mapping - assertTrue(memoryRealm.findRoles(user).isEmpty()); - } +import org.junit.jupiter.api.Test; +class MemoryRealmTest { + + @Test + void whenUnmappingAGroupAndRoleFromAMemoryRealmThenMappingIsDropped() { + // given a Memory Realm, a Group and a Role + MemoryRealm memoryRealm = new MemoryRealm(); + Group group = new Group(); + Role role = new Role(); + + // Given the group and role are mapped + memoryRealm.map(group, role); + // Then there is a mapping for this group + assertFalse(memoryRealm.findRoles(group).isEmpty()); + + // When I remove this mapping + memoryRealm.unmap(group, role); + + // Then the memory realm has no more mapping + assertTrue(memoryRealm.findRoles(group).isEmpty()); + } + + @Test + void whenUnmappingAUserAndRoleFromAMemoryRealmThenMappingIsDropped() { + // given a Memory Realm, a Group and a Role + MemoryRealm memoryRealm = new MemoryRealm(); + User user = new User(); + Role role = new Role(); + + // Given the user and role are mapped + memoryRealm.map(user, role); + // Then there is a mapping for this user + assertFalse(memoryRealm.findRoles(user).isEmpty()); + + // When I remove this mapping + memoryRealm.unmap(user, role); + + // Then the memory realm has no more mapping + assertTrue(memoryRealm.findRoles(user).isEmpty()); + } } diff --git a/org.restlet/src/test/java/org/restlet/security/RoleTestCase.java b/org.restlet/src/test/java/org/restlet/security/RoleTestCase.java index 222f72ca41..17988cc013 100644 --- a/org.restlet/src/test/java/org/restlet/security/RoleTestCase.java +++ b/org.restlet/src/test/java/org/restlet/security/RoleTestCase.java @@ -1,30 +1,29 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; -import org.junit.jupiter.api.Test; -import org.restlet.Application; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import org.junit.jupiter.api.Test; +import org.restlet.Application; + /** * Suite of unit tests for the {@link Role} class. * * @author Thierry Boileau * @author Jerome Louvel */ -public class RoleTestCase { +class RoleTestCase { @Test - public void testRoleEquality() { + void testRoleEquality() { Application app1 = new Application(); Application app2 = new Application(); diff --git a/org.restlet/src/test/java/org/restlet/security/SaasApplication.java b/org.restlet/src/test/java/org/restlet/security/SaasApplication.java index 75c7fcd094..057f2974fd 100644 --- a/org.restlet/src/test/java/org/restlet/security/SaasApplication.java +++ b/org.restlet/src/test/java/org/restlet/security/SaasApplication.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import org.restlet.Application; @@ -16,9 +15,8 @@ import org.restlet.routing.Router; /** - * Sample SAAS application with a Basic authenticator guarding a hello world - * Restlet. - * + * Sample SAAS application with a Basic authenticator guarding a hello world Restlet. + * * @author Jerome Louvel */ public class SaasApplication extends Application { @@ -38,8 +36,8 @@ public Restlet createInboundRoot() { Router root = new Router(); // Attach test 1 - ChallengeAuthenticator authenticator = new ChallengeAuthenticator( - getContext(), ChallengeScheme.HTTP_BASIC, "saas"); + ChallengeAuthenticator authenticator = + new ChallengeAuthenticator(getContext(), ChallengeScheme.HTTP_BASIC, "saas"); authenticator.setNext(new HelloWorldRestlet()); root.attach("/httpBasicAuthenticator", authenticator); @@ -58,8 +56,8 @@ public Restlet createInboundRoot() { roleAuthorizer.getAuthorizedRoles().add(getRole("admin")); roleAuthorizer.setNext(new HelloWorldRestlet()); - authenticator = new ChallengeAuthenticator(getContext(), - ChallengeScheme.HTTP_BASIC, "saas"); + authenticator = + new ChallengeAuthenticator(getContext(), ChallengeScheme.HTTP_BASIC, "saas"); authenticator.setNext(roleAuthorizer); root.attach("/adminRoleAuthorizer", authenticator); @@ -68,8 +66,8 @@ public Restlet createInboundRoot() { roleAuthorizer.getForbiddenRoles().add(getRole("admin")); roleAuthorizer.setNext(new HelloWorldRestlet()); - authenticator = new ChallengeAuthenticator(getContext(), - ChallengeScheme.HTTP_BASIC, "saas"); + authenticator = + new ChallengeAuthenticator(getContext(), ChallengeScheme.HTTP_BASIC, "saas"); authenticator.setNext(roleAuthorizer); root.attach("/adminRoleForbiddenAuthorizer", authenticator); diff --git a/org.restlet/src/test/java/org/restlet/security/SaasComponent.java b/org.restlet/src/test/java/org/restlet/security/SaasComponent.java index 53ab1b98f2..a408c8f83b 100644 --- a/org.restlet/src/test/java/org/restlet/security/SaasComponent.java +++ b/org.restlet/src/test/java/org/restlet/security/SaasComponent.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; import org.restlet.Component; @@ -16,7 +15,7 @@ /** * Sample SAAS component with declared organizations. - * + * * @author Jerome Louvel */ public class SaasComponent extends Component { @@ -34,12 +33,10 @@ public SaasComponent() { context.setDefaultVerifier(realm.getVerifier()); // Add users - User stiger = new User("stiger", "pwd", "Scott", "Tiger", - "scott.tiger@foobar.com"); + User stiger = new User("stiger", "pwd", "Scott", "Tiger", "scott.tiger@foobar.com"); realm.getUsers().add(stiger); - User larmstrong = new User("larmstrong", "pwd", "Louis", "Armstrong", - "la@foobar.com"); + User larmstrong = new User("larmstrong", "pwd", "Louis", "Armstrong", "la@foobar.com"); realm.getUsers().add(larmstrong); // Add groups diff --git a/org.restlet/src/test/java/org/restlet/security/SecurityTestCase.java b/org.restlet/src/test/java/org/restlet/security/SecurityTestCase.java index ca3e83df39..531063c764 100644 --- a/org.restlet/src/test/java/org/restlet/security/SecurityTestCase.java +++ b/org.restlet/src/test/java/org/restlet/security/SecurityTestCase.java @@ -1,14 +1,16 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.security; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.restlet.data.ChallengeScheme.HTTP_BASIC; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -18,24 +20,23 @@ import org.restlet.resource.ClientResource; import org.restlet.resource.ResourceException; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.restlet.data.ChallengeScheme.HTTP_BASIC; - /** * Restlet unit tests for the security package. * * @author Jerome Louvel */ -public class SecurityTestCase { +class SecurityTestCase { - private final ChallengeResponse lambdaUserCR = new ChallengeResponse(HTTP_BASIC, "stiger", "pwd"); - private final ChallengeResponse adminUserCR = new ChallengeResponse(HTTP_BASIC, "larmstrong", "pwd"); + private final ChallengeResponse lambdaUserCR = + new ChallengeResponse(HTTP_BASIC, "stiger", "pwd"); + private final ChallengeResponse adminUserCR = + new ChallengeResponse(HTTP_BASIC, "larmstrong", "pwd"); private SaasComponent component; private int testPort; @BeforeEach - public void startComponent() throws Exception { + void startComponent() throws Exception { Engine.register(); Engine.clearThreadLocalVariables(); this.component = new SaasComponent(); @@ -44,7 +45,7 @@ public void startComponent() throws Exception { } @AfterEach - public void stopServer() throws Exception { + void stopServer() throws Exception { Engine.clearThreadLocalVariables(); if (this.component.isStarted()) { this.component.stop(); @@ -54,61 +55,74 @@ public void stopServer() throws Exception { } @Test - public void withoutAuthenticationHttpBasicAuthenticatorShouldReturnUnauthorizedResponse() { - ClientResource resource = new ClientResource("http://localhost:" + testPort + "/httpBasicAuthenticator"); + void withoutAuthenticationHttpBasicAuthenticatorShouldReturnUnauthorizedResponse() { + ClientResource resource = + new ClientResource("http://localhost:" + testPort + "/httpBasicAuthenticator"); runClientResource(resource); assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, resource.getStatus()); } @Test - public void withAuthenticationHttpBasicAuthenticatorShouldReturnOkResponse() { - ClientResource resource = new ClientResource("http://localhost:" + testPort + "/httpBasicAuthenticator"); + void withAuthenticationHttpBasicAuthenticatorShouldReturnOkResponse() { + ClientResource resource = + new ClientResource("http://localhost:" + testPort + "/httpBasicAuthenticator"); resource.setChallengeResponse(lambdaUserCR); runClientResource(resource); assertEquals(Status.SUCCESS_OK, resource.getStatus()); } @Test - public void withoutAuthenticationAlwaysAuthenticatorShouldReturnOkResponse() { - ClientResource resource = new ClientResource("http://localhost:" + testPort + "/alwaysAuthenticator"); + void withoutAuthenticationAlwaysAuthenticatorShouldReturnOkResponse() { + ClientResource resource = + new ClientResource("http://localhost:" + testPort + "/alwaysAuthenticator"); runClientResource(resource); assertEquals(Status.SUCCESS_OK, resource.getStatus()); } @Test - public void withAuthenticationNeverAuthenticatorShouldReturnForbiddenResponse() { - ClientResource resource = new ClientResource("http://localhost:" + testPort + "/neverAuthenticator"); + void withAuthenticationNeverAuthenticatorShouldReturnForbiddenResponse() { + ClientResource resource = + new ClientResource("http://localhost:" + testPort + "/neverAuthenticator"); runClientResource(resource); assertEquals(Status.CLIENT_ERROR_FORBIDDEN, resource.getStatus()); } @Test - public void withLambdaUserAuthenticationAdminRoleAuthorizerAuthenticatorShouldReturnForbiddenResponse() { - ClientResource resource = new ClientResource("http://localhost:" + testPort + "/adminRoleAuthorizer"); + void + withLambdaUserAuthenticationAdminRoleAuthorizerAuthenticatorShouldReturnForbiddenResponse() { + ClientResource resource = + new ClientResource("http://localhost:" + testPort + "/adminRoleAuthorizer"); resource.setChallengeResponse(lambdaUserCR); runClientResource(resource); assertEquals(Status.CLIENT_ERROR_FORBIDDEN, resource.getStatus()); } @Test - public void withAdminUserAuthenticationAdminRoleAuthorizerAuthenticatorShouldReturnOkResponse() { - ClientResource resource = new ClientResource("http://localhost:" + testPort + "/adminRoleAuthorizer"); + void withAdminUserAuthenticationAdminRoleAuthorizerAuthenticatorShouldReturnOkResponse() { + ClientResource resource = + new ClientResource("http://localhost:" + testPort + "/adminRoleAuthorizer"); resource.setChallengeResponse(adminUserCR); runClientResource(resource); assertEquals(Status.SUCCESS_OK, resource.getStatus()); } @Test - public void withAdminUserAuthenticationAdminRoleForbiddenAuthorizerAuthenticatorShouldReturnForbiddenResponse() { - ClientResource resource = new ClientResource("http://localhost:" + testPort + "/adminRoleForbiddenAuthorizer"); + void + withAdminUserAuthenticationAdminRoleForbiddenAuthorizerAuthenticatorShouldReturnForbiddenResponse() { + ClientResource resource = + new ClientResource( + "http://localhost:" + testPort + "/adminRoleForbiddenAuthorizer"); resource.setChallengeResponse(adminUserCR); runClientResource(resource); assertEquals(Status.CLIENT_ERROR_FORBIDDEN, resource.getStatus()); } @Test - public void withLambdaUserAuthenticationAdminRoleForbiddenAuthorizerAuthenticatorShouldReturnOkResponse() { - ClientResource resource = new ClientResource("http://localhost:" + testPort + "/adminRoleForbiddenAuthorizer"); + void + withLambdaUserAuthenticationAdminRoleForbiddenAuthorizerAuthenticatorShouldReturnOkResponse() { + ClientResource resource = + new ClientResource( + "http://localhost:" + testPort + "/adminRoleForbiddenAuthorizer"); resource.setChallengeResponse(lambdaUserCR); runClientResource(resource); assertEquals(Status.SUCCESS_OK, resource.getStatus()); @@ -117,8 +131,9 @@ public void withLambdaUserAuthenticationAdminRoleForbiddenAuthorizerAuthenticato private static void runClientResource(ClientResource resource) { try { resource.get(); - } catch (ResourceException e) {} + } catch (ResourceException ignored) { + // Ignored exceptions are expected + } resource.release(); } - } diff --git a/org.restlet/src/test/java/org/restlet/security/package-info.java b/org.restlet/src/test/java/org/restlet/security/package-info.java new file mode 100644 index 0000000000..6b9b889420 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/security/package-info.java @@ -0,0 +1,6 @@ +/** + * JUnit tests. + * + * @since 1.0 + */ +package org.restlet.security; diff --git a/org.restlet/src/test/java/org/restlet/security/package.html b/org.restlet/src/test/java/org/restlet/security/package.html deleted file mode 100644 index 04a9abdb4e..0000000000 --- a/org.restlet/src/test/java/org/restlet/security/package.html +++ /dev/null @@ -1,6 +0,0 @@ - - -JUnit tests. -@since 1.0 - - \ No newline at end of file diff --git a/org.restlet/src/test/java/org/restlet/service/ConnegServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/ConnegServiceTestCase.java index c5741f8048..87a67b41b5 100644 --- a/org.restlet/src/test/java/org/restlet/service/ConnegServiceTestCase.java +++ b/org.restlet/src/test/java/org/restlet/service/ConnegServiceTestCase.java @@ -1,64 +1,62 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.Test; import org.restlet.Request; import org.restlet.data.MediaType; import org.restlet.data.Preference; import org.restlet.representation.Variant; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - /** * Unit tests for the content negotiation service. - * + * * @author Jerome Louvel */ -public class ConnegServiceTestCase { +class ConnegServiceTestCase { @Test - public void testStrict() { + void testStrict() { List variants = new ArrayList<>(); Variant variant = new Variant(MediaType.APPLICATION_XML); variants.add(variant); Request request = new Request(); - request.getClientInfo().getAcceptedMediaTypes() + request.getClientInfo() + .getAcceptedMediaTypes() .add(new Preference<>(MediaType.APPLICATION_JSON)); MetadataService metadataService = new MetadataService(); ConnegService connegService = new ConnegService(); // Flexible algorithm - Variant preferedVariant = connegService.getPreferredVariant(variants, - request, metadataService); + Variant preferedVariant = + connegService.getPreferredVariant(variants, request, metadataService); assertNotNull(preferedVariant); assertEquals(MediaType.APPLICATION_XML, preferedVariant.getMediaType()); // Strict algorithm connegService.setStrict(true); - preferedVariant = connegService.getPreferredVariant(variants, request, - metadataService); + preferedVariant = connegService.getPreferredVariant(variants, request, metadataService); assertNull(preferedVariant); // Add a variant to match the strict preferences variant = new Variant(MediaType.APPLICATION_JSON); variants.add(variant); - preferedVariant = connegService.getPreferredVariant(variants, request, - metadataService); + preferedVariant = connegService.getPreferredVariant(variants, request, metadataService); assertNotNull(preferedVariant); assertEquals(MediaType.APPLICATION_JSON, preferedVariant.getMediaType()); - } } diff --git a/org.restlet/src/test/java/org/restlet/service/MetadataServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/MetadataServiceTestCase.java index 8200975f35..6be478b3f0 100644 --- a/org.restlet/src/test/java/org/restlet/service/MetadataServiceTestCase.java +++ b/org.restlet/src/test/java/org/restlet/service/MetadataServiceTestCase.java @@ -1,28 +1,27 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import static org.junit.jupiter.api.Assertions.assertNull; + import org.junit.jupiter.api.Test; import org.restlet.data.MediaType; -import static org.junit.jupiter.api.Assertions.assertNull; - /** * Unit tests for the metadata service. * * @author Jerome Louvel */ -public class MetadataServiceTestCase { +class MetadataServiceTestCase { @Test - public void testStrict() { + void testStrict() { MetadataService ms = new MetadataService(); MediaType ma = ms.getMediaType("ma"); assertNull(ma); diff --git a/org.restlet/src/test/java/org/restlet/service/StatusServiceTestCase.java b/org.restlet/src/test/java/org/restlet/service/StatusServiceTestCase.java index 96b79887d2..6290b899b6 100644 --- a/org.restlet/src/test/java/org/restlet/service/StatusServiceTestCase.java +++ b/org.restlet/src/test/java/org/restlet/service/StatusServiceTestCase.java @@ -1,14 +1,19 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; +import java.io.Serial; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,23 +27,18 @@ import org.restlet.representation.ObjectRepresentation; import org.restlet.representation.Representation; -import java.io.IOException; -import java.io.Serial; - -import static org.junit.jupiter.api.Assertions.*; - /** * Unit tests for the status service. - * + * * @author Jerome Louvel */ @SuppressWarnings("unchecked") -public class StatusServiceTestCase { +class StatusServiceTestCase { StatusService statusService = new StatusService(); @BeforeEach - void setUp() { + void setUp() { // Restore a clean engine Engine.clearThreadLocalVariables(); Engine.register(true); @@ -52,8 +52,9 @@ void cleanUp() { } @Test - public void shouldConvertToStatus() { - AnnotatedNotSerializableException statusException = new AnnotatedNotSerializableException("test message", 50); + void shouldConvertToStatus() { + AnnotatedNotSerializableException statusException = + new AnnotatedNotSerializableException("test message", 50); Status status = statusService.toStatus(statusException, null, null); @@ -62,8 +63,9 @@ public void shouldConvertToStatus() { } @Test - public void exceptionShouldNotBeSerialized() throws IOException { - AnnotatedNotSerializableException statusException = new AnnotatedNotSerializableException("test message", 50); + void exceptionShouldNotBeSerialized() throws IOException { + AnnotatedNotSerializableException statusException = + new AnnotatedNotSerializableException("test message", 50); Status status = new Status(400, statusException); Representation representation = statusServiceToRepresentation(status); @@ -81,26 +83,30 @@ public void exceptionShouldNotBeSerialized() throws IOException { } @Test - public void shouldSerializeAnnotatedException() throws IOException { - Status status = new Status(400, AnnotatedSerializableException.withoutCause("test message", 50)); + void shouldSerializeAnnotatedException() throws IOException { + Status status = + new Status(400, AnnotatedSerializableException.withoutCause("test message", 50)); Representation representation = statusServiceToRepresentation(status); assertEquals(MediaType.APPLICATION_JAVA_OBJECT, representation.getMediaType()); - AnnotatedSerializableException throwable = ((ObjectRepresentation) representation).getObject(); + AnnotatedSerializableException throwable = + ((ObjectRepresentation) representation).getObject(); assertEquals(50, throwable.value); assertEquals("test message", throwable.getMessage()); assertNull(throwable.getCause()); } @Test - public void shouldSerializeAnnotatedExceptionWithCause() throws IOException { - Status status = new Status(400, AnnotatedSerializableException.withCause("test message", 50)); + void shouldSerializeAnnotatedExceptionWithCause() throws IOException { + Status status = + new Status(400, AnnotatedSerializableException.withCause("test message", 50)); Representation representation = statusServiceToRepresentation(status); assertEquals(MediaType.APPLICATION_JAVA_OBJECT, representation.getMediaType()); - AnnotatedSerializableException throwable = ((ObjectRepresentation) representation).getObject(); + AnnotatedSerializableException throwable = + ((ObjectRepresentation) representation).getObject(); assertEquals(50, throwable.value); assertEquals("test message", throwable.getMessage()); assertEquals(0, throwable.getStackTrace().length); @@ -108,8 +114,9 @@ public void shouldSerializeAnnotatedExceptionWithCause() throws IOException { } @Test - public void shouldSerializeAnnotatedExceptionWithStackTrace() throws IOException { - Status status = new Status(400, AnnotatedSerializableException.withCause("test message", 50)); + void shouldSerializeAnnotatedExceptionWithStackTrace() throws IOException { + Status status = + new Status(400, AnnotatedSerializableException.withCause("test message", 50)); Request request = new Request(); Response response = new Response(request); @@ -121,7 +128,8 @@ public void shouldSerializeAnnotatedExceptionWithStackTrace() throws IOException Representation representation = statusService.toRepresentation(status, request, response); assertEquals(MediaType.APPLICATION_JAVA_OBJECT, representation.getMediaType()); - AnnotatedSerializableException throwable = ((ObjectRepresentation) representation).getObject(); + AnnotatedSerializableException throwable = + ((ObjectRepresentation) representation).getObject(); assertEquals(50, throwable.value); assertEquals("test message", throwable.getMessage()); assertEquals(1, throwable.getStackTrace().length); @@ -137,8 +145,7 @@ private Representation statusServiceToRepresentation(Status status) { @org.restlet.resource.Status(value = 400, serialize = false) private static class AnnotatedNotSerializableException extends Throwable { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private final int value; @@ -148,7 +155,7 @@ public AnnotatedNotSerializableException(String message, int value) { } @SuppressWarnings("unused") - public int getValue() { + public int getValue() { return value; } } @@ -156,36 +163,39 @@ public int getValue() { @org.restlet.resource.Status(value = 401) private static class AnnotatedSerializableException extends Throwable { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private int value; - public static AnnotatedSerializableException withoutCause(final String message, final int value) { + public static AnnotatedSerializableException withoutCause( + final String message, final int value) { StackTraceElement[] stackTrace = new StackTraceElement[1]; stackTrace[0] = new StackTraceElement("DeclaringClass", "MethodName", "FileName", 1); - AnnotatedSerializableException annotatedSerializableException = new AnnotatedSerializableException(message, value); + AnnotatedSerializableException annotatedSerializableException = + new AnnotatedSerializableException(message, value); annotatedSerializableException.setStackTrace(stackTrace); return annotatedSerializableException; } - public static AnnotatedSerializableException withCause(final String message, final int value) { + public static AnnotatedSerializableException withCause( + final String message, final int value) { StackTraceElement[] stackTrace = new StackTraceElement[1]; stackTrace[0] = new StackTraceElement("DeclaringClass", "MethodName", "FileName", 1); Throwable rootCause = new IOException("File '/toto.txt' is not readable"); rootCause.setStackTrace(stackTrace); - AnnotatedSerializableException annotatedSerializableException = new AnnotatedSerializableException(message, value, rootCause); + AnnotatedSerializableException annotatedSerializableException = + new AnnotatedSerializableException(message, value, rootCause); annotatedSerializableException.setStackTrace(stackTrace); return annotatedSerializableException; } @SuppressWarnings("unused") - public AnnotatedSerializableException() {} + public AnnotatedSerializableException() {} - public AnnotatedSerializableException(String message, int value) { + public AnnotatedSerializableException(String message, int value) { super(message); this.value = value; } @@ -199,5 +209,4 @@ public int getValue() { return value; } } - } diff --git a/org.restlet/src/test/java/org/restlet/service/UserAgentTestResource.java b/org.restlet/src/test/java/org/restlet/service/UserAgentTestResource.java index 44b9c21d16..0d8beb9087 100644 --- a/org.restlet/src/test/java/org/restlet/service/UserAgentTestResource.java +++ b/org.restlet/src/test/java/org/restlet/service/UserAgentTestResource.java @@ -1,12 +1,11 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; import org.restlet.data.MediaType; @@ -16,9 +15,7 @@ import org.restlet.resource.ResourceException; import org.restlet.resource.ServerResource; -/** - * Simple resource that returns at least text/html and text/xml representations. - */ +/** Simple resource that returns at least text/html and text/xml representations. */ public class UserAgentTestResource extends ServerResource { public UserAgentTestResource() { diff --git a/org.restlet/src/test/java/org/restlet/service/UserAgentTunnelFilterTestCase.java b/org.restlet/src/test/java/org/restlet/service/UserAgentTunnelFilterTestCase.java index ff3dad1a7f..803091c722 100644 --- a/org.restlet/src/test/java/org/restlet/service/UserAgentTunnelFilterTestCase.java +++ b/org.restlet/src/test/java/org/restlet/service/UserAgentTunnelFilterTestCase.java @@ -1,14 +1,15 @@ /** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

* Restlet is a registered trademark of QlikTech International AB. */ - package org.restlet.service; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.restlet.Application; @@ -21,12 +22,8 @@ import org.restlet.data.Status; import org.restlet.routing.Router; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Tests cases for the tunneling of preferences based on user agent. - */ -public class UserAgentTunnelFilterTestCase { +/** Tests cases for the tunneling of preferences based on user agent. */ +class UserAgentTunnelFilterTestCase { private Application application; @@ -48,18 +45,19 @@ private Request createRequest() { @BeforeEach public void setUpEach() { - this.application = new Application() { - @Override - public Restlet createInboundRoot() { - Router router = new Router(getContext()); - router.attachDefault(UserAgentTestResource.class); - return router; - } - }; + this.application = + new Application() { + @Override + public Restlet createInboundRoot() { + Router router = new Router(getContext()); + router.attachDefault(UserAgentTestResource.class); + return router; + } + }; } @Test - public void testTunnelOff() { + void testTunnelOff() { this.application.getTunnelService().setUserAgentTunnel(false); Request request = createRequest(); Response response = new Response(request); @@ -69,7 +67,7 @@ public void testTunnelOff() { } @Test - public void testTunnelOn() { + void testTunnelOn() { this.application.getTunnelService().setUserAgentTunnel(true); Request request = createRequest(); Response response = new Response(request); diff --git a/org.restlet/src/test/java/org/restlet/util/SeriesTestCase.java b/org.restlet/src/test/java/org/restlet/util/SeriesTestCase.java new file mode 100644 index 0000000000..13db334768 --- /dev/null +++ b/org.restlet/src/test/java/org/restlet/util/SeriesTestCase.java @@ -0,0 +1,172 @@ +/** + * Copyright 2005-2026 Qlik + *

+ * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. + */ +package org.restlet.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.restlet.data.Form; +import org.restlet.data.Parameter; + +class SeriesTestCase { + + /** A parameter whose name is not a key in the map is silently skipped. */ + @Test + void copyTo_unknownNameIsIgnored() { + Form form = form("foo", "bar"); + Map params = map("other"); + + form.copyTo(params); + + assertNull(params.get("other")); + } + + /** An empty Series leaves the map untouched. */ + @Test + void copyTo_emptySeriesLeavesMapUnchanged() { + Form form = new Form(); + Map params = map("key"); + + form.copyTo(params); + + assertNull(params.get("key")); + } + + /** A single parameter whose name matches a key stores its value directly (not in a list). */ + @Test + void copyTo_singleValueStoredDirectly() { + Form form = form("color", "red"); + Map params = map("color"); + + form.copyTo(params); + + assertEquals("red", params.get("color")); + } + + /** A parameter with a null value stores {@link Series#EMPTY_VALUE} as a sentinel. */ + @Test + void copyTo_nullValueStoredAsEmptyValue() { + Form form = new Form(); + form.add(new Parameter("flag", null)); + Map params = map("flag"); + + form.copyTo(params); + + assertSame(Series.EMPTY_VALUE, params.get("flag")); + } + + /** Two parameters with the same name produce a List containing both values. */ + @Test + void copyTo_duplicateNamesProduceList() { + Form form = form("accept", "text/html", "accept", "application/json"); + Map params = map("accept"); + + form.copyTo(params); + + Object value = params.get("accept"); + assertInstanceOf(List.class, value, "Expected a List for duplicate entries"); + @SuppressWarnings("unchecked") + List list = (List) value; + assertEquals(2, list.size()); + assertEquals("text/html", list.get(0)); + assertEquals("application/json", list.get(1)); + } + + /** Three parameters with the same name all end up in a single growing List. */ + @Test + void copyTo_triplicateNamesProduceListOfThree() { + Form form = form("x", "1", "x", "2", "x", "3"); + Map params = map("x"); + + form.copyTo(params); + + @SuppressWarnings("unchecked") + List list = (List) params.get("x"); + assertEquals(3, list.size()); + assertEquals("1", list.get(0)); + assertEquals("2", list.get(1)); + assertEquals("3", list.get(2)); + } + + /** Null values in a multi-value entry are stored as {@link Series#EMPTY_VALUE}. */ + @Test + void copyTo_nullValuesInListStoredAsEmptyValue() { + Form form = new Form(); + form.add(new Parameter("h", null)); + form.add(new Parameter("h", null)); + Map params = map("h"); + + form.copyTo(params); + + @SuppressWarnings("unchecked") + List list = (List) params.get("h"); + assertEquals(2, list.size()); + assertSame(Series.EMPTY_VALUE, list.get(0)); + assertSame(Series.EMPTY_VALUE, list.get(1)); + } + + /** Mixed null and non-null values in a multi-value entry are handled correctly. */ + @Test + void copyTo_mixedNullAndNonNullValuesInList() { + Form form = new Form(); + form.add(new Parameter("h", "value1")); + form.add(new Parameter("h", null)); + form.add(new Parameter("h", "value2")); + Map params = map("h"); + + form.copyTo(params); + + @SuppressWarnings("unchecked") + List list = (List) params.get("h"); + assertEquals(3, list.size()); + assertEquals("value1", list.get(0)); + assertSame(Series.EMPTY_VALUE, list.get(1)); + assertEquals("value2", list.get(2)); + } + + /** + * Only the keys initially present in the map receive values; unrelated entries are untouched. + */ + @Test + void copyTo_onlyMapKeysArePopulated() { + Form form = form("a", "1", "b", "2", "c", "3"); + Map params = map("a", "c"); + + form.copyTo(params); + + assertEquals("1", params.get("a")); + assertEquals("3", params.get("c")); + assertFalse(params.containsKey("b"), "Key 'b' should not have been inserted"); + } + + // -- helpers -- + + private static Form form(String... pairs) { + Form form = new Form(); + for (int i = 0; i < pairs.length; i += 2) { + form.add(pairs[i], pairs[i + 1]); + } + return form; + } + + private static Map map(String... keys) { + Map m = new HashMap<>(); + for (String key : keys) { + m.put(key, null); + } + return m; + } +} diff --git a/pom.xml b/pom.xml index 2e2df7d70f..c9503bd4ba 100644 --- a/pom.xml +++ b/pom.xml @@ -224,8 +224,7 @@ - + false ${release} @@ -313,6 +312,50 @@ SemVerVersionPolicy + + com.diffplug.spotless + spotless-maven-plugin + 3.3.0 + + + + + src/main/java/**/*.java + src/test/java/**/*.java + + + + + + true + + + + + + + + + * The content of this file is subject to the terms of the Apache 2.0 open + * source license available at https://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. + */]]> + + + + + + + apply + check + + compile + + +