Skip to content

Commit

Permalink
apiUrl function #10628
Browse files Browse the repository at this point in the history
  • Loading branch information
anatol-sialitski committed Sep 19, 2024
1 parent 1dc67bd commit 0d4071a
Show file tree
Hide file tree
Showing 16 changed files with 628 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public MainWebHandler()
protected boolean canHandle( final WebRequest req )
{
final String path = req.getRawPath();
return path.equals( "" ) || path.equals( "/" ) || path.equals( "/admin" ) || path.equals( "/admin/" );
return path.isEmpty() || path.equals( "/" );
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected WebResponse doHandle( final WebRequest webRequest, final WebResponse w
final AdminToolHandlerWorker worker = new AdminToolHandlerWorker( portalRequest );
worker.controllerScriptFactory = this.controllerScriptFactory;
worker.adminToolDescriptorService = adminToolDescriptorService;
final DescriptorKey descriptorKey = AdminToolPortalHandler.getDescriptorKey( webRequest );
final DescriptorKey descriptorKey = AdminToolPortalHandler.getDescriptorKey( webRequest.getRawPath() );
worker.descriptorKey = descriptorKey == null ? AdminToolPortalHandler.DEFAULT_DESCRIPTOR_KEY : descriptorKey;

final Trace trace = Tracer.newTrace( "portalRequest" );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

import com.google.common.net.HttpHeaders;

import com.enonic.xp.app.ApplicationKey;
import com.enonic.xp.page.DescriptorKey;
import com.enonic.xp.portal.PortalRequest;
import com.enonic.xp.portal.RenderMode;
import com.enonic.xp.portal.handler.BasePortalHandler;
import com.enonic.xp.web.HttpStatus;
import com.enonic.xp.web.WebRequest;
import com.enonic.xp.web.WebResponse;
import com.enonic.xp.web.exception.ExceptionMapper;
import com.enonic.xp.web.exception.ExceptionRenderer;
import com.enonic.xp.web.handler.WebHandler;
import com.enonic.xp.web.handler.WebHandlerChain;
import com.enonic.xp.web.servlet.ServletRequestUrlHelper;

@Component(immediate = true, service = WebHandler.class)
public class AdminToolPortalHandler
Expand All @@ -35,13 +40,27 @@ protected boolean canHandle( final WebRequest webRequest )
return webRequest.getRawPath().equals( ADMIN_TOOL_BASE ) || webRequest.getRawPath().startsWith( ADMIN_TOOL_PREFIX );
}

@Override
protected WebResponse doHandle( final WebRequest webRequest, final WebResponse webResponse, final WebHandlerChain webHandlerChain )
{
if ( AdminToolPortalHandler.ADMIN_TOOL_PREFIX.equals( webRequest.getRawPath() ) )
{
final String uri = ServletRequestUrlHelper.createUri( webRequest.getRawRequest(), "/admin" );
return WebResponse.create().status( HttpStatus.TEMPORARY_REDIRECT ).header( HttpHeaders.LOCATION, uri ).build();

Check warning on line 49 in modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/portal/AdminToolPortalHandler.java

View check run for this annotation

Codecov / codecov/patch

modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/portal/AdminToolPortalHandler.java#L48-L49

Added lines #L48 - L49 were not covered by tests
}
else
{
return super.doHandle( webRequest, webResponse, webHandlerChain );

Check warning on line 53 in modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/portal/AdminToolPortalHandler.java

View check run for this annotation

Codecov / codecov/patch

modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/portal/AdminToolPortalHandler.java#L53

Added line #L53 was not covered by tests
}
}

@Override
protected PortalRequest createPortalRequest( final WebRequest webRequest, final WebResponse webResponse )
{
final PortalRequest portalRequest = new PortalRequest( webRequest );

final DescriptorKey descriptorKey = getDescriptorKey( webRequest );
if ( descriptorKey == null )
final DescriptorKey descriptorKey = getDescriptorKey( webRequest.getRawPath() );

Check warning on line 62 in modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/portal/AdminToolPortalHandler.java

View check run for this annotation

Codecov / codecov/patch

modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/portal/AdminToolPortalHandler.java#L62

Added line #L62 was not covered by tests
if ( descriptorKey == null || DEFAULT_DESCRIPTOR_KEY.equals( descriptorKey ) )
{
portalRequest.setBaseUri( ADMIN_TOOL_BASE );
portalRequest.setApplicationKey( DEFAULT_DESCRIPTOR_KEY.getApplicationKey() );
Expand All @@ -55,13 +74,11 @@ protected PortalRequest createPortalRequest( final WebRequest webRequest, final
return portalRequest;
}

public static DescriptorKey getDescriptorKey( final WebRequest webRequest )
public static DescriptorKey getDescriptorKey( final String path )
{
final String path = webRequest.getRawPath();

if ( path.equals( ADMIN_TOOL_BASE ) )
{
return null;
return DEFAULT_DESCRIPTOR_KEY;

Check warning on line 81 in modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/portal/AdminToolPortalHandler.java

View check run for this annotation

Codecov / codecov/patch

modules/admin/admin-impl/src/main/java/com/enonic/xp/admin/impl/portal/AdminToolPortalHandler.java#L81

Added line #L81 was not covered by tests
}
else if ( path.startsWith( ADMIN_TOOL_PREFIX ) )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void testRedirect()
assertRedirect( response1 );

request.setRawPath( "/admin" );
assertTrue( this.handler.canHandle( request ) );
assertFalse( this.handler.canHandle( request ) );

final WebResponse response2 = this.handler.doHandle( request, null, null );
assertRedirect( response2 );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.enonic.xp.lib.portal.url;

import java.util.Set;

import com.google.common.collect.Multimap;

import com.enonic.xp.portal.url.ApiUrlParams;

public final class ApiUrlHandler
extends AbstractUrlHandler
{
private static final Set<String> VALID_URL_PROPERTY_KEYS = Set.of( "application", "api", "type", "params" );

@Override
protected String buildUrl( final Multimap<String, String> map )
{
final ApiUrlParams params = new ApiUrlParams().portalRequest( this.request ).setAsMap( map );
return this.urlService.apiUrl( params );
}

@Override
protected boolean isValidParam( final String param )
{
return VALID_URL_PROPERTY_KEYS.contains( param );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const portalLib = require('/lib/xp/portal');
const assert = require('/lib/xp/testing');

// BEGIN
const url = portalLib.apiUrl({
application: 'com.enonic.app.myapp',
api: 'myapi',
params: {
a: 1,
b: 2
}
});

const unnamedApiUrl = portalLib.apiUrl({
application: 'com.enonic.app.myapp',
});

// END

assert.assertEquals('ApiUrlParams{type=server, params={a=[1], b=[2]}, api=myapi, application=com.enonic.app.myapp}', url);
assert.assertEquals('ApiUrlParams{type=server, params={}, application=com.enonic.app.myapp}', unnamedApiUrl);
29 changes: 29 additions & 0 deletions modules/lib/lib-portal/src/main/resources/lib/xp/portal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,3 +600,32 @@ export function imagePlaceholder(params: ImagePlaceholderParams): string {
bean.setHeight(params?.height ?? 0);
return bean.createImagePlaceholder();
}

export interface ApiUrlParams {
application: string;
api?: string;
type?: 'server' | 'absolute' | 'websocket';
params?: object;
}

interface ApiUrlHandler {
createUrl(value: object): string;
}

/**
* This function generates a URL pointing to a Universal API.
*
* @example-ref examples/portal/apiUrl.js
*
* @param {object} params Input parameters as JSON.
* @param {string} params.application Application to reference to the API.
* @param {string} [params.api] Name of the API
* @param {string} [params.type=server] URL type. Either `server` (server-relative URL) or `absolute` or `websocket`.
* @param {object} [params.params] Custom parameters to append to the URL.
*
* @returns {string} The generated URL.
*/
export function apiUrl(params: ApiUrlParams): string {
const bean = __.newBean<ApiUrlHandler>('com.enonic.xp.lib.portal.url.ApiUrlHandler');
return bean.createUrl(__.toScriptValue(params));
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,10 @@ public void testExample_imagePlaceholder()
{
runScript( "/lib/xp/examples/portal/imagePlaceholder.js" );
}

@Test
public void testExample_apiUrl()
{
runScript( "/lib/xp/examples/portal/apiUrl.js" );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.enonic.xp.portal.url;

import java.util.Objects;

import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Multimap;

import com.enonic.xp.annotation.PublicApi;

@PublicApi
public final class ApiUrlParams
extends AbstractUrlParams<ApiUrlParams>
{
private String application;

private String api;

public String getApplication()
{
return application;
}

public String getApi()
{
return api;
}

public ApiUrlParams application( final String value )
{
this.application = Objects.requireNonNull( value );
return this;
}

public ApiUrlParams api( final String value )
{
this.api = Strings.emptyToNull( value );
return this;
}

@Override
public ApiUrlParams setAsMap( final Multimap<String, String> map )
{
super.setAsMap( map );
api( singleValue( map, "_api" ) );
application( singleValue( map, "_application" ) );
getParams().putAll( map );
return this;
}

@Override
protected void buildToString( final MoreObjects.ToStringHelper helper )
{
super.buildToString( helper );
helper.add( "api", this.api );
helper.add( "application", this.application );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ public interface PortalUrlService
String generateUrl( GenerateUrlParams params );

String processHtml( ProcessHtmlParams params );

String apiUrl( ApiUrlParams params );
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@

import java.math.BigInteger;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Splitter;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.net.UrlEscapers;

import com.enonic.xp.content.ContentService;
import com.enonic.xp.exception.NotFoundException;
Expand All @@ -31,8 +26,6 @@
import com.enonic.xp.web.servlet.ServletRequestUrlHelper;
import com.enonic.xp.web.servlet.UriRewritingResult;

import static com.google.common.base.Strings.isNullOrEmpty;

abstract class PortalUrlBuilder<T extends AbstractUrlParams>
{
private static final Logger LOG = LoggerFactory.getLogger( PortalUrlBuilder.class );
Expand All @@ -55,72 +48,17 @@ public final void setParams( final T params )

protected final void appendPart( final StringBuilder str, final String urlPart )
{
if ( isNullOrEmpty( urlPart ) )
{
return;
}

final boolean endsWithSlash = ( str.length() > 0 ) && ( str.charAt( str.length() - 1 ) == '/' );
final String normalized = normalizePath( urlPart );

if ( !endsWithSlash )
{
str.append( "/" );
}

str.append( normalized );
UrlBuilderHelper.appendPart( str, urlPart );
}

private void appendParams( final StringBuilder str, final Collection<Map.Entry<String, String>> params )
{
if ( params.isEmpty() )
{
return;
}
str.append( "?" );
final Iterator<Map.Entry<String, String>> it = params.iterator();
appendParam( str, it.next() );
while ( it.hasNext() )
{
str.append( "&" );
appendParam( str, it.next() );
}
}

private void appendParam( final StringBuilder str, final Map.Entry<String, String> param )
{
final String value = param.getValue();
str.append( urlEncode( param.getKey() ) );
if ( value != null )
{
str.append( "=" ).append( urlEncode( value ) );
}
}

private String urlEncode( final String value )
{
return UrlEscapers.urlFormParameterEscaper().escape( value );
}

private String urlEncodePathSegment( final String value )
{
return UrlEscapers.urlPathSegmentEscaper().escape( value );
UrlBuilderHelper.appendParams( str, params );
}

String normalizePath( final String value )
{
if ( value == null )
{
return null;
}

if ( !value.contains( "/" ) )
{
return urlEncodePathSegment( value );
}

return StreamSupport.stream( Splitter.on( '/' ).trimResults().omitEmptyStrings().split( value ).spliterator(), false ).
map( this::urlEncodePathSegment ).collect( Collectors.joining( "/" ) );
return UrlBuilderHelper.normalizePath( value );
}

public final String build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.enonic.xp.portal.impl.PortalConfig;
import com.enonic.xp.portal.impl.RedirectChecksumService;
import com.enonic.xp.portal.url.AbstractUrlParams;
import com.enonic.xp.portal.url.ApiUrlParams;
import com.enonic.xp.portal.url.AssetUrlParams;
import com.enonic.xp.portal.url.AttachmentUrlParams;
import com.enonic.xp.portal.url.ComponentUrlParams;
Expand Down Expand Up @@ -139,6 +140,25 @@ public String processHtml( final ProcessHtmlParams params )
return new RichTextProcessor( styleDescriptorService, this, macroService ).process( params );
}

@Override
public String apiUrl( final ApiUrlParams params )
{
if ( params == null || params.getApplication() == null )
{
throw new IllegalArgumentException( "\"application\" is required" );
}

if ( params.getPortalRequest() != null )
{
return build( new UniversalApiUrlBuilder(), params );
}
else
{
// TODO resolve baseUrl
return new SlashApiUrlBuilder( params ).build();
}
}

private <B extends PortalUrlBuilder<P>, P extends AbstractUrlParams> String build( final B builder, final P params )
{
builder.setParams( params );
Expand Down
Loading

0 comments on commit 0d4071a

Please sign in to comment.