Skip to content

Commit

Permalink
Extend API descriptor #10739
Browse files Browse the repository at this point in the history
  • Loading branch information
anatol-sialitski committed Oct 25, 2024
1 parent c9098ab commit 41e6ca7
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
import com.enonic.xp.web.WebResponse;
import com.enonic.xp.portal.universalapi.UniversalApiHandler;

@Component(immediate = true, service = UniversalApiHandler.class, property = {"applicationKey=admin", "apiKey=status"})
@Component(immediate = true, service = UniversalApiHandler.class, property = {"applicationKey=admin", "apiKey=status",
"displayName=Status API"})
public class StatusApiHandler
implements UniversalApiHandler
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

import com.google.common.collect.Multimap;

import com.enonic.xp.portal.universalapi.UniversalApiHandler;
import com.enonic.xp.web.HttpMethod;
import com.enonic.xp.web.WebException;
import com.enonic.xp.web.WebRequest;
import com.enonic.xp.web.WebResponse;
import com.enonic.xp.portal.universalapi.UniversalApiHandler;

@Component(immediate = true, service = UniversalApiHandler.class, property = {"applicationKey=admin", "apiKey=widget",
"allowedPrincipals=role:system.admin.login"})
"allowedPrincipals=role:system.admin.login", "displayName=Widget API"})
public class WidgetDispatcherApiHandler
implements UniversalApiHandler
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.enonic.xp.api;

import java.util.Objects;

import com.google.common.base.Preconditions;

import com.enonic.xp.annotation.PublicApi;
Expand All @@ -15,12 +17,24 @@ public final class ApiDescriptor

private final PrincipalKeys allowedPrincipals;

private final String displayName;

private final String description;

private final String documentationUrl;

private final boolean slashApi;

private ApiDescriptor( final Builder builder )
{
Preconditions.checkNotNull( builder.key, "key cannot be null" );

this.key = builder.key;
this.allowedPrincipals = builder.allowedPrincipals;
this.displayName = builder.displayName;
this.description = builder.description;
this.documentationUrl = builder.documentationUrl;
this.slashApi = Objects.requireNonNullElse( builder.slashApi, true );
}

public DescriptorKey getKey()
Expand All @@ -33,6 +47,26 @@ public PrincipalKeys getAllowedPrincipals()
return allowedPrincipals;
}

public String getDisplayName()
{
return displayName;
}

public String getDescription()
{
return description;
}

public String getDocumentationUrl()
{
return documentationUrl;
}

public Boolean isSlashApi()
{
return slashApi;
}

public boolean isAccessAllowed( final PrincipalKeys principalKeys )
{
return allowedPrincipals == null || principalKeys.contains( RoleKeys.ADMIN ) ||
Expand All @@ -55,6 +89,14 @@ public static final class Builder

private PrincipalKeys allowedPrincipals;

private String displayName;

private String description;

private String documentationUrl;

private Boolean slashApi;

private Builder()
{
}
Expand All @@ -71,6 +113,30 @@ public Builder allowedPrincipals( final PrincipalKeys allowedPrincipals )
return this;
}

public Builder displayName( final String displayName )
{
this.displayName = displayName;
return this;
}

public Builder description( final String description )
{
this.description = description;
return this;
}

public Builder documentationUrl( final String documentationUrl )
{
this.documentationUrl = documentationUrl;
return this;
}

public Builder slashApi( final Boolean useInSlashApi )
{
this.slashApi = useInSlashApi;
return this;
}

public ApiDescriptor build()
{
return new ApiDescriptor( this );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,10 @@

<xs:complexType name="apiDescriptor">
<xs:sequence>
<xs:element minOccurs="0" name="display-name" type="xs:string"/>
<xs:element minOccurs="0" name="description" type="xs:string"/>
<xs:element minOccurs="0" name="documentation-url" type="xs:string"/>
<xs:element minOccurs="0" name="slash-api" type="xs:boolean"/>
<xs:element minOccurs="0" name="allow">
<xs:complexType>
<xs:sequence>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,24 @@
import com.enonic.xp.web.WebResponse;

public final class DynamicUniversalApiHandler
implements UniversalApiHandler
{
private final UniversalApiHandler apiHandler;
final UniversalApiHandler apiHandler;

private final ApiDescriptor apiDescriptor;

public DynamicUniversalApiHandler( final UniversalApiHandler apiHandler, final ApiDescriptor apiDescriptor )
DynamicUniversalApiHandler( final UniversalApiHandler apiHandler, final ApiDescriptor apiDescriptor )
{
this.apiHandler = apiHandler;
this.apiDescriptor = apiDescriptor;
}

@Override
public WebResponse handle( WebRequest request )
{
return apiHandler.handle( request );
}

public UniversalApiHandler getApiHandler()
{
return apiHandler;
}

public ApiDescriptor getApiDescriptor()
{
return apiDescriptor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

import org.osgi.service.component.annotations.Activate;
Expand All @@ -24,7 +23,7 @@
@Component(service = DynamicUniversalApiHandlerRegistry.class)
public class DynamicUniversalApiHandlerRegistry
{
private final ConcurrentMap<DescriptorKey, DynamicUniversalApiHandler> dynamicApiHandlers = new ConcurrentHashMap<>();
private final CopyOnWriteArrayList<DynamicUniversalApiHandler> dynamicApiHandlers = new CopyOnWriteArrayList<>();

@Activate
public DynamicUniversalApiHandlerRegistry()
Expand All @@ -34,28 +33,27 @@ public DynamicUniversalApiHandlerRegistry()

public DynamicUniversalApiHandler getApiHandler( final DescriptorKey descriptorKey )
{
return dynamicApiHandlers.get( descriptorKey );
return dynamicApiHandlers.stream()
.filter( handler -> handler.getApiDescriptor().getKey().equals( descriptorKey ) )
.findFirst()
.orElse( null );
}

public List<ApiDescriptor> getAllApiDescriptors()
{
return dynamicApiHandlers.values().stream().map( DynamicUniversalApiHandler::getApiDescriptor ).collect( Collectors.toList() );
return dynamicApiHandlers.stream().map( DynamicUniversalApiHandler::getApiDescriptor ).collect( Collectors.toList() );
}

@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
public void addApiHandler( final UniversalApiHandler apiHandler, final Map<String, ?> properties )
{
final ApiDescriptor apiDescriptor = createDynamicApiDescriptor( properties );
this.dynamicApiHandlers.put( apiDescriptor.getKey(), new DynamicUniversalApiHandler( apiHandler, apiDescriptor ) );
this.dynamicApiHandlers.add( new DynamicUniversalApiHandler( apiHandler, apiDescriptor ) );
}

public void removeApiHandler( final UniversalApiHandler apiHandler )
{
dynamicApiHandlers.values()
.stream()
.filter( wrapper -> wrapper.getApiHandler().equals( apiHandler ) )
.findFirst()
.ifPresent( apiHandlerWrapper -> this.dynamicApiHandlers.remove( apiHandlerWrapper.getApiDescriptor().getKey() ) );
dynamicApiHandlers.removeIf( handler -> handler.apiHandler == apiHandler );
}

private ApiDescriptor createDynamicApiDescriptor( final Map<String, ?> properties )
Expand All @@ -64,7 +62,27 @@ private ApiDescriptor createDynamicApiDescriptor( final Map<String, ?> propertie
final String apiKey = Objects.requireNonNull( (String) properties.get( "apiKey" ) );
final PrincipalKeys allowedPrincipals = resolveDynamicPrincipalKeys( properties.get( "allowedPrincipals" ) );

return ApiDescriptor.create().key( DescriptorKey.from( applicationKey, apiKey ) ).allowedPrincipals( allowedPrincipals ).build();
final ApiDescriptor.Builder builder =
ApiDescriptor.create().key( DescriptorKey.from( applicationKey, apiKey ) ).allowedPrincipals( allowedPrincipals );

if ( properties.get( "description" ) != null )
{
builder.description( properties.get( "description" ).toString() );
}
if ( properties.get( "displayName" ) != null )
{
builder.displayName( properties.get( "displayName" ).toString() );
}
if ( properties.get( "documentationUrl" ) != null )
{
builder.documentationUrl( properties.get( "documentationUrl" ).toString() );
}
if ( properties.get( "slashApi" ) != null )
{
builder.slashApi( Boolean.valueOf( properties.get( "slashApi" ).toString() ) );
}

return builder.build();
}

private PrincipalKeys resolveDynamicPrincipalKeys( final Object allowedPrincipals )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ public final class XmlApiDescriptorParser

private static final String PRINCIPAL_TAG_NAME = "principal";

private static final String DISPLAY_NAME_TAG_NAME = "display-name";

private static final String DESCRIPTION_TAG_NAME = "description";

private static final String DOCUMENTATION_URL_TAG_NAME = "documentation-url";

private static final String SLASH_API_TAG_NAME = "slash-api";

private final ApiDescriptor.Builder builder;

public XmlApiDescriptorParser( final ApiDescriptor.Builder builder )
Expand All @@ -31,6 +39,16 @@ protected void doParse( final DomElement root )
{
assertTagName( root, ROOT_TAG_NAME );

this.builder.displayName( root.getChildValueTrimmed( DISPLAY_NAME_TAG_NAME ) );
this.builder.description( root.getChildValue( DESCRIPTION_TAG_NAME ) );
this.builder.documentationUrl( root.getChildValueTrimmed( DOCUMENTATION_URL_TAG_NAME ) );

final String useInSlashApi = root.getChildValueTrimmed( SLASH_API_TAG_NAME );
if ( useInSlashApi != null )
{
builder.slashApi( Boolean.valueOf( useInSlashApi ) );
}

DomElement allowedPrincipals = root.getChild( ALLOW_TAG_NAME );

if ( allowedPrincipals != null )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,13 @@ public WebResponse handle( final WebRequest webRequest )
final DynamicUniversalApiHandler dynamicApiHandler = universalApiHandlerRegistry.getApiHandler( descriptorKey );
final ApiDescriptor apiDescriptor = resolveApiDescriptor( dynamicApiHandler, descriptorKey );

if ( apiDescriptor != null )
if ( apiDescriptor == null )
{
verifyAccessToApi( apiDescriptor );
throw WebException.notFound( String.format( "API [%s] not found", descriptorKey ) );
}

if ( !verifyRequestMounted( descriptorKey, portalRequest ) )
verifyAccessToApi( apiDescriptor );
if ( !verifyRequestMounted( apiDescriptor, portalRequest ) )
{
throw WebException.notFound( String.format( "API [%s] is not mounted", descriptorKey ) );
}
Expand Down Expand Up @@ -176,13 +177,14 @@ private WebResponse execute( final PortalRequest portalRequest, final Descriptor
} );
}

private boolean verifyRequestMounted( final DescriptorKey descriptorKey, final PortalRequest portalRequest )
private boolean verifyRequestMounted( final ApiDescriptor apiDescriptor, final PortalRequest portalRequest )
{
final String rawPath = portalRequest.getRawPath();
final DescriptorKey descriptorKey = apiDescriptor.getKey();

if ( portalRequest.getEndpointPath() == null )
{
return rawPath.startsWith( "/api/" );
return rawPath.startsWith( "/api/" ) && apiDescriptor.isSlashApi();
}
else if ( rawPath.startsWith( "/site/" ) || rawPath.startsWith( "/admin/site/" ) )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ private Map<String, Object> map( final ApiDescriptor apiDescriptor )
.stream()
.map( PrincipalKey::toString )
.collect( Collectors.toList() ) );
result.put( "displayName", apiDescriptor.getDisplayName() );
result.put( "description", apiDescriptor.getDescription() );
result.put( "documentationUrl", apiDescriptor.getDocumentationUrl() );
result.put( "slashApi", apiDescriptor.isSlashApi() );

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ private void assertResult()
final ApiDescriptor result = this.builder.build();
assertEquals( "myapplication:myapi", result.getKey().toString() );

assertEquals( "My API", result.getDisplayName() );
assertEquals( "This is my API", result.getDescription() );
assertEquals( "https://apis.enonic.com", result.getDocumentationUrl() );
assertFalse( result.isSlashApi() );

final PrincipalKeys allowedPrincipals = result.getAllowedPrincipals();
assertNotNull( allowedPrincipals );
assertEquals( 1, allowedPrincipals.getSize() );
Expand Down
Loading

0 comments on commit 41e6ca7

Please sign in to comment.