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 20, 2024
1 parent 1a48ac1 commit 385446d
Show file tree
Hide file tree
Showing 17 changed files with 748 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();
}
else
{
return super.doHandle( webRequest, webResponse, webHandlerChain );
}
}

@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() );
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;
}
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,107 @@
package com.enonic.xp.admin.impl.portal;

import javax.servlet.http.HttpServletRequest;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.google.common.net.HttpHeaders;

import com.enonic.xp.web.HttpMethod;
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.WebHandlerChain;

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 static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class AdminToolPortalHandlerTest
{
private WebRequest request;

private WebResponse response;

private AdminToolPortalHandler handler;

private WebHandlerChain chain;

@BeforeEach
public void setup()
throws Exception
{
final ExceptionMapper exceptionMapper = mock( ExceptionMapper.class );
final ExceptionRenderer exceptionRenderer = mock( ExceptionRenderer.class );

this.handler = new AdminToolPortalHandler();
this.handler.setWebExceptionMapper( exceptionMapper );
this.handler.setExceptionRenderer( exceptionRenderer );

final HttpServletRequest rawRequest = mock( HttpServletRequest.class );

this.request = new WebRequest();
this.request.setMethod( HttpMethod.GET );
this.request.setRawRequest( rawRequest );

this.response = WebResponse.create().build();

this.chain = mock( WebHandlerChain.class );
when( chain.handle( any( WebRequest.class ), any( WebResponse.class ) ) ).thenReturn( this.response );
}

@Test
void testCanHandle()
{
this.request.setRawPath( "/admin" );
assertTrue( this.handler.canHandle( request ) );

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

this.request.setRawPath( "/path" );
assertFalse( this.handler.canHandle( request ) );
}

@Test
void testDoHandleRedirect()
{
this.request.setRawPath( "/admin/" );

final WebResponse webResponse = this.handler.doHandle( this.request, response, null );
assertEquals( HttpStatus.TEMPORARY_REDIRECT, webResponse.getStatus() );
assertEquals( "/admin", webResponse.getHeaders().get( HttpHeaders.LOCATION ) );
}

@Test
void testDoHandleOnSlashAdmin()
{
this.request.setRawPath( "/admin" );

final WebResponse webResponse = this.handler.doHandle( this.request, response, chain );
assertEquals( this.response, webResponse );
}

@Test
void testDoHandle()
{
this.request.setRawPath( "/admin/app/toolname" );

final WebResponse webResponse = this.handler.doHandle( this.request, response, chain );
assertEquals( this.response, webResponse );
}

@Test
void testDoHandleDescriptorNotFoundFallbackToSlashAdmin()
{
this.request.setRawPath( "/admin/app" );

final WebResponse webResponse = this.handler.doHandle( this.request, response, chain );
assertEquals( this.response, webResponse );
}
}
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 );
}
Loading

0 comments on commit 385446d

Please sign in to comment.