Skip to content

Commit

Permalink
BL-190
Browse files Browse the repository at this point in the history
  • Loading branch information
bdw429s committed Jun 8, 2024
1 parent 137a59a commit 32978d5
Show file tree
Hide file tree
Showing 11 changed files with 554 additions and 20 deletions.
79 changes: 79 additions & 0 deletions src/main/java/ortus/boxlang/runtime/components/system/Exit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* [BoxLang]
*
* Copyright [2023] [Ortus Solutions, Corp]
*
* Licensed 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 ortus.boxlang.runtime.components.system;

import java.util.Set;

import ortus.boxlang.runtime.components.Attribute;
import ortus.boxlang.runtime.components.BoxComponent;
import ortus.boxlang.runtime.components.Component;
import ortus.boxlang.runtime.context.IBoxContext;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.types.IStruct;
import ortus.boxlang.runtime.types.exceptions.AbortException;
import ortus.boxlang.runtime.types.exceptions.BoxValidationException;
import ortus.boxlang.runtime.validation.Validator;

@BoxComponent
public class Exit extends Component {

/**
* --------------------------------------------------------------------------
* Constructor(s)
* --------------------------------------------------------------------------
*/

public Exit() {
super();
declaredAttributes = new Attribute[] {
new Attribute( Key.method, "string", "exitTag", Set.of( Validator.valueOneOf( "exitTag", "exitTemplate", "loop" ) ) ),
};
}

/**
* This component aborts processing of the currently executing custom tag, exits the page within the currently executing custom tag, or re-executes a section of code within the currently executing custom tag.
*
* @param context The context in which the Component is being invoked
* @param attributes The attributes to the Component
* @param body The body of the Component
* @param executionState The execution state of the Component
*
* @attribute.method The method to use for exiting (exitTag, exitTemplate, loop)
*
*/
public BodyResult _invoke( IBoxContext context, IStruct attributes, ComponentBody body, IStruct executionState ) {
String method = attributes.getAsString( Key.method ).toLowerCase();

String type;
switch ( method ) {
case "exittag" :
type = "exit-tag";
break;
case "exittemplate" :
type = "exit-template";
break;
case "loop" :
type = "exit-loop";
break;
default :
throw new BoxValidationException( "Invalid exit method: " + method );
}
context.flushBuffer( false );
throw new AbortException( type, null );
}
}
67 changes: 52 additions & 15 deletions src/main/java/ortus/boxlang/runtime/components/system/Module.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
import ortus.boxlang.runtime.types.Array;
import ortus.boxlang.runtime.types.IStruct;
import ortus.boxlang.runtime.types.Struct;
import ortus.boxlang.runtime.types.exceptions.AbortException;
import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException;
import ortus.boxlang.runtime.types.exceptions.BoxValidationException;
import ortus.boxlang.runtime.types.exceptions.CustomException;
import ortus.boxlang.runtime.util.ResolvedFilePath;

Expand Down Expand Up @@ -109,28 +111,63 @@ public BodyResult _invoke( IBoxContext context, IStruct attributes, ComponentBod
variables.put( Key.thisTag, thisTag );

try {
bTemplate.invoke( ctContext );
ctContext.flushBuffer( false );
try {
bTemplate.invoke( ctContext );
} catch ( AbortException e ) {
if ( e.isTag() ) {
return DEFAULT_RETURN;
} else if ( e.isTemplate() || e.isPage() ) {
// Do nothing, we'll contine with the body next like nothing happened
} else if ( e.isLoop() ) {
throw new BoxValidationException( "You cannot use the 'loop' method of the exit component in the start of a custom tag." );
} else {
// Any other type of abort just keeps going up the stack
throw e;
}
} finally {
ctContext.flushBuffer( false );
}

if ( body != null ) {

thisTag.put( Key.executionMode, "inactive" );

StringBuffer buffer = new StringBuffer();
BodyResult bodyResult = processBody( context, body, buffer );
// IF there was a return statement inside our body, we early exit now
if ( bodyResult.isEarlyExit() ) {
// Output thus far
context.writeToBuffer( buffer.toString() );
return bodyResult;
boolean keepLooping = true;
while ( keepLooping ) {
// Assume we will only exucute the body once
keepLooping = false;

StringBuffer buffer = new StringBuffer();
BodyResult bodyResult = processBody( context, body, buffer );
// IF there was a return statement inside our body, we early exit now
if ( bodyResult.isEarlyExit() ) {
// Output thus far
context.writeToBuffer( buffer.toString() );
return bodyResult;
}
thisTag.put( Key.generatedContent, buffer.toString() );

thisTag.put( Key.executionMode, "end" );

try {
bTemplate.invoke( ctContext );
} catch ( AbortException e ) {
if ( e.isTag() ) {
return DEFAULT_RETURN;
} else if ( e.isTemplate() || e.isPage() ) {
// Do nothing, we'll contine with the body next like nothing happened
} else if ( e.isLoop() ) {
// If the closing tag has exit method="loop", then we will run the tag body again!
keepLooping = true;
} else {
// Any other type of abort just keeps going up the stack
throw e;
}
} finally {
context.writeToBuffer( thisTag.getAsString( Key.generatedContent ) );
}
}
thisTag.put( Key.generatedContent, buffer.toString() );

thisTag.put( Key.executionMode, "end" );

bTemplate.invoke( ctContext );

context.writeToBuffer( thisTag.getAsString( Key.generatedContent ) );
}
} finally {
ctContext.flushBuffer( false );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private void run( IBoxContext context, String name, String priority, IStruct att
processBody( tContext, body, buffer );
} catch ( AbortException e ) {
// We log it so we can potentially find out why it was aborted
logger.error( "Thread [{}] aborted at stacktrace: {}", nameKey.getName(), e.getStackTrace() );
logger.debug( "Thread [{}] aborted at stacktrace: {}", nameKey.getName(), e.getStackTrace() );
} catch ( Throwable e ) {
exception = e;
logger.error( "Thread [{}] terminated with exception: {}", nameKey.getName(), e.getMessage() );
Expand Down
18 changes: 14 additions & 4 deletions src/main/java/ortus/boxlang/runtime/runnables/BoxTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
import ortus.boxlang.runtime.BoxRuntime;
import ortus.boxlang.runtime.context.IBoxContext;
import ortus.boxlang.runtime.loader.ImportDefinition;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.types.IStruct;
import ortus.boxlang.runtime.types.Struct;
import ortus.boxlang.runtime.types.exceptions.AbortException;
import ortus.boxlang.runtime.types.exceptions.BoxValidationException;
import ortus.boxlang.runtime.util.ResolvedFilePath;

public abstract class BoxTemplate implements ITemplateRunnable {
Expand All @@ -43,8 +45,9 @@ public abstract class BoxTemplate implements ITemplateRunnable {
*
*/
public void invoke( IBoxContext context ) {
BoxRuntime runtime = BoxRuntime.getInstance();

BoxRuntime runtime = BoxRuntime.getInstance();
boolean isInModule = context.getComponents().length > 0
&& context.getComponents()[ context.getComponents().length - 1 ].getAsKey( Key._NAME ).equals( Key.module );
context.pushTemplate( this );
try {
// Announcements
Expand All @@ -59,10 +62,17 @@ public void invoke( IBoxContext context ) {
// Announce
runtime.announce( "postTemplateInvoke", data );
} catch ( AbortException e ) {
context.flushBuffer( true );
// Swallowing aborts here if type="page"
// Module components have their own checks
if ( isInModule && ( e.isTemplate() || e.isLoop() || e.isTag() ) ) {
throw e;
}
if ( e.isLoop() ) {
throw new BoxValidationException( "You cannot use the 'loop' method of the exit component outside of a custom tag." );
}
// Swallowing aborts here if type="page" and exits of type template
// Ignoring showerror in case for now
if ( e.isRequest() ) {
context.flushBuffer( true );
throw e;
}
} catch ( Throwable e ) {
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/ortus/boxlang/runtime/types/Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
import ortus.boxlang.runtime.scopes.ArgumentsScope;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.services.InterceptorService;
import ortus.boxlang.runtime.types.exceptions.AbortException;
import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException;
import ortus.boxlang.runtime.types.exceptions.BoxValidationException;
import ortus.boxlang.runtime.types.meta.BoxMeta;
import ortus.boxlang.runtime.types.meta.FunctionMeta;
import ortus.boxlang.runtime.util.ArgumentUtil;
Expand Down Expand Up @@ -178,6 +180,16 @@ public Object invoke( FunctionBoxContext context ) {
BoxEvent.POST_FUNCTION_INVOKE,
data
);
} catch ( AbortException e ) {
if ( e.isLoop() ) {
throw new BoxValidationException( "You cannot use the 'loop' method of the exit component outside of a custom tag." );
} else if ( e.isTemplate() || e.isTag() ) {
// These function basically as a return from the method
return result;
} else if ( e.isRequest() ) {
context.flushBuffer( true );
}
throw e;
} catch ( Throwable e ) {
context.flushBuffer( true );
throw e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,32 @@ public Boolean isPage() {
return type.equals( "page" );
}

// other types: exit-tag, exit-template, exit-loop

/**
* Is this abort type tag? Use with exit component.
*
* @return Whether this abort affects the tag
*/
public Boolean isTag() {
return type.equals( "exit-tag" );
}

/**
* Is this abort type template? Use with exit component.
*
* @return Whether this abort affects the template
*/
public Boolean isTemplate() {
return type.equals( "exit-template" );
}

/**
* Is this abort type loop? Use with exit component.
*
* @return Whether this abort affects the loop
*/
public Boolean isLoop() {
return type.equals( "exit-loop" );
}
}
Loading

0 comments on commit 32978d5

Please sign in to comment.