Skip to content

Instantly share code, notes, and snippets.

@ieb
Last active January 31, 2020 12:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ieb/5f217e2c160afb7bb4098bca99896621 to your computer and use it in GitHub Desktop.
Save ieb/5f217e2c160afb7bb4098bca99896621 to your computer and use it in GitHub Desktop.
RedirectResolver proposal
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sling.TBD;
import org.osgi.annotation.versioning.ProviderType;
import javax.servlet.http.HttpServletRequest;
/**
* A RedirectResolver resolves a redirect in the context of a request. It resolves that redirect having first been bound
* to a Resource. This is typically achieved through the Adapt to mechanism eg resource.adaptTo(RedirectResolver.class).
*
* Implementations should implement an AdapterFactory to perform the adaption to an implementation of this class. That class
* should implement the resolve method such that the methods of the Redirect Response are called to define the RedirectResponse.
*
* The implementation of the resolve method indicates that it has successfully resolved a redirect by setting a setting a status code
* see the documentation of the RedirectResponse class for more information.
*/
@ProviderType
public interface RedirectResolver {
/**
* Resolve the redirect for the resource bound to this redirect resolver, in the context of the request.
* May decline to perform this operation, but not setting the status code on the redirectResponse.
* @param request the context of the resolution given by a request. This is required and must not be null. It will indicate
* where the request came from, including the exposed edge Host and other http request headers. Typcally
* and implementation will use these to determine aspects of the redirect response. For instance, requests through
* a CDN may redirect differently from request made direct from a service running behind the CDN.
* @param redirectResponse the redirect response class. This is implemented by the caller and will contain the methods
* defined in the base class at the time the implementation of the RedirectResolver was written.
* If methods are added to the RedirectResponse class, they will be added in a way to ensure existing
* implementations do not need to be updated to continue to work.
*/
void resolve(HttpServletRequest request, RedirectResponse redirectResponse);
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sling.TBD;
/**
* A Redirect response defines the base class used to hold information to build a redirect. It is extended
* by the caller to RedirectResolver.resolve, and called by the implementation of the RedirectResolver interface.
* On return from that call the caller will use the information in the RedirectResponse to perform whatever operation
* it needs to perform. Typically this will be returning a http redirect, but there may be other uses.
*
* A class is being used rather than an interface to allow empty setter methods to be added over time.
* Callers to RedirectResolver.resolve should override those methods that they need information for. New methods to this
* class should be added with empty implementations to ensure that pre-exiting classes extending this method to not
* have to be updated.
*/
public class RedirectResponse {
/**
* Value to be used in setSetatus to indicate no redirect available for context of the resolution.
*/
public static final int NO_REDIRECT = -1;
/**
* Set a header on the response. This will replace existing headers of the same name.
* If called twice, the header will be reset with the latest value.
* Multiple headers of the same name are not supported.
* @param name the name of the header.
* @param value the value of the header.
*/
public void setHeader(String name, String value) {
}
/**
* Set the redirect to be used. Will not be sent until the RedirectResponse is returned from the
* RedirectResolver.resolver and processed.
* @param url the url to set, should be set in the context of a request.
*/
public void setRedirect(String url) {
}
/**
* Set the status code used in the redirect response. Typically 301, but other status codes may be used.
* If the status code is not set, or is set to -1 then the redirect will not be acted on.
* @param i status code number.
*/
public void setStatus(int i) {
}
}
@ieb
Copy link
Author

ieb commented Jan 31, 2020

Typical usage

 /*
         * Check if this resource can be adapted to a redirect resolver and the that the
         * resource resolves to a redirect, if it does, set the headers, status code and send the redirect
         * otherwise do nothing.
        * 
         */
        RedirectResolver redirectResolver = resource.adaptTo(org.apache.sling.TBD.RedirectResolver.class);
        if ( redirectResolver != null ) {
            RedirectResponseImpl redirectResponse = new RedirectResponseImpl();
            redirectResolver.resolve(request, redirectResponse);
            if ( redirectResponse.hasResolved() /* implementation detail */ ) {
                for ( String[] header : redirectResponse.getHeaders() /* implementation detail */ ) {
                    response.setHeader(header[0], header[1]);
                }
                response.setStatus(redirectResponse.getStatus());
                response.sendRedirect(redirectResponse.getRedirect());
                return;
            }
        }

@raducotescu
Copy link

raducotescu commented Jan 31, 2020

What do you say about something like the following? Names are not important, I was focusing on getting rid of the adaptTo paradigm, which in this case doesn't make sense to me. A resource is at most an entity. I would not tie it conceptually to a response via adaptation.

interface ResponsePreProcessor {
    /**
     * Based on the resource associated to the passed {@code request} and potential headers already set to the passed {@code response},
     * this method will return a processed {@code Response}, wrapping the original {@code response} object, if such a processing was
     * possible. Otherwise, the method will return {@code null}.
     *
     * @param request the request to which this {@code RedirectProvider} might be able to reply with a {@code RedirectResponse}
     * @param response the response object
     * @return Response a processed response, if the preprocessor was able to create one, {@code null} otherwise
     */
    Response getResponse(SlingHttpServletRequest request, SlingHttpServletResponse response);
}

interface Response extends SlingHttpServletResponse {
    /**
     * Set a header on the response. This will replace existing headers of the same name.
     * If called twice, the header will be reset with the latest value.
     * Multiple headers of the same name are not supported.
     * @param name the name of the header.
     * @param value the value of the header.
     */
    void setHeader(String name, String value);
    /**
     * Set the redirect to be used. Will not be sent until the RedirectResponse is returned from the
     * RedirectResolver.resolver and processed.
     * @param url the url to set, should be set in the context of a request.
     */
    void setRedirect(String url);
    /**
     * Set the status code used in the redirect response. Typically 301, but other status codes may be used.
     * If the status code is not set, or is set to -1 then the redirect will not be acted on.
     * @param i status code number.
     */
    void setStatus(int i);
}

class MyServlet extends SlingAllMethodsServlet {
    @Reference
    private ResponsePreProcessor responsePreProcessor;
    @Override
    protected void doGet(SlingHttpServletRequest request,
                         SlingHttpServletResponse response)  {
        Response preProcessedResponse = responsePreProcessor.getResponse(request, response);
        if (preProcessedResponse != null) {
            // do magic
        }
    }
}

The concepts are similar, it's just that in this second proposal we're relying on services and explicit contracts.

@ieb
Copy link
Author

ieb commented Jan 31, 2020

Carsten advised against that pattern, will discuss on list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment