From 9c63110f1d2040466b224d7c8606dd83d55eaac4 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 16 Nov 2016 23:49:01 -0600 Subject: [PATCH 01/87] Bump new version --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 0c172110e..fd81b5f17 100644 --- a/build/build.xml +++ b/build/build.xml @@ -15,7 +15,7 @@ External Dependencies: - + From 6cbaaa2c928937d0dcadc16720e54bfb3b41913e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 17 Nov 2016 00:44:46 -0600 Subject: [PATCH 02/87] COMMANDBOX-498 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index f9ead1a16..b0a0dc7d4 100644 --- a/build/build.properties +++ b/build/build.properties @@ -11,7 +11,7 @@ java.pack200=false #dependencies dependencies.dir=${basedir}/lib -cfml.version=4.5.3.020 +cfml.version=4.5.4.017 cfml.loader.version=1.4.1 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} From b283be17bff533eb160e17b3425ae80b22922a51 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 17 Nov 2016 21:50:18 -0600 Subject: [PATCH 03/87] CONTENTBOX-786 --- src/cfml/system/services/PackageService.cfc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index 66835e335..c229b980d 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -272,6 +272,10 @@ component accessors="true" singleton { // ContentBox Widget } else if( packageType == 'contentbox-widgets' ) { installDirectory = arguments.packagePathRequestingInstallation & '/modules/contentbox/widgets'; + // widgets just get dumped in + artifactDescriptor.createPackageDirectory = false; + // Don't trash the widgets folder with this + ignorePatterns.append( '/box.json' ); // ContentBox themes/layouts } else if( packageType == 'contentbox-themes' || packageType == 'contentbox-layouts' ) { installDirectory = arguments.packagePathRequestingInstallation & '/modules/contentbox/themes'; From 0453e426082e06d10ffbcef55dddc1cdfa67ab58 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 18 Nov 2016 11:29:30 -0600 Subject: [PATCH 04/87] COMMANDBOX-500 --- src/cfml/system/services/ServerService.cfc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index fdb490021..d00ada539 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -650,7 +650,7 @@ component accessors="true" singleton { // thread the execution var threadName = 'server#hash( serverInfo.webroot )##createUUID()#'; - thread name="#threadName#" serverInfo=serverInfo args=args { + thread name="#threadName#" serverInfo=serverInfo args=args startupTimeout=startupTimeout { try{ // execute the server command var executeResult = ''; @@ -659,7 +659,7 @@ component accessors="true" singleton { serverInfo.statusInfo = { command:variables.javaCommand, arguments:attributes.args, result:'' }; setServerInfo( serverInfo ); // Note this timeout is purposefully longer than the Runwar timeout so if the server takes too long, we get to capture the console info - execute name=variables.javaCommand arguments=attributes.args timeout="150" variable="executeResult" errorVariable="executeError"; + execute name=variables.javaCommand arguments=attributes.args timeout="#startupTimeout+20#" variable="executeResult" errorVariable="executeError"; serverInfo.status="running"; } catch (any e) { logger.error( "Error starting server: #e.message# #e.detail#", arguments ); From 0c6c59b6096bbe79e0fa3185f2d15c08fb9336a9 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 18 Nov 2016 20:50:09 -0600 Subject: [PATCH 05/87] COMMANDBOX-503 --- .../system-commands/commands/tail.cfc | 165 +++++++++++++++++- 1 file changed, 157 insertions(+), 8 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/tail.cfc b/src/cfml/system/modules_app/system-commands/commands/tail.cfc index 802918b0b..abd16239f 100644 --- a/src/cfml/system/modules_app/system-commands/commands/tail.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/tail.cfc @@ -15,10 +15,11 @@ component { /** - * @path.hint file or directory to tail - * @lines.hint number of lines to display. + * @path file or directory to tail + * @lines number of lines to display. + * @follow Keep outputting new lines to the file until Ctrl-C is pressed **/ - function run( required path, numeric lines = 15 ){ + function run( required path, numeric lines = 15, boolean follow = false ){ var filePath = fileSystemUtil.resolvePath( arguments.path ); @@ -26,13 +27,17 @@ component { return error( "The file does not exist: #arguments.path#" ); } + variables.file = createObject( "java", "java.io.File" ).init( filePath ); + var startPos = findStartPos(); + var startingLength = 0; + try { var lineCounter = 0; var buffer = []; - var file = createObject( "java", "java.io.File" ).init( filePath ); var randomAccessFile = createObject( "java", "java.io.RandomAccessFile" ).init( file, "r" ); - var position = file.length(); + var startingLength = file.length(); + variables.position = startingLength; // move to the end of the file randomAccessFile.seek( position ); @@ -41,9 +46,13 @@ component { var lastLF = false; while( true ){ - + // stop looping if we have met our line limit or if end of file - if ( position < 0 || lineCounter == arguments.lines ) { + if ( position < startPos-1 || lineCounter == arguments.lines ) { + if( buffer.len() ) { + // Strip any CR or LF from the last (first really) line to eliminate leading line breaks in console output + buffer[ buffer.len() ] = listChangeDelims( buffer[ buffer.len() ], '', chr(13) & chr( 10 ) ); + } break; } @@ -68,7 +77,9 @@ component { } // print our file to console - print.line( buffer.reverse().toList( "" ) ); + print + .text( buffer.reverse().toList( "" ) ) + .toConsole(); } finally { @@ -76,6 +87,144 @@ component { randomAccessFile.close(); } } + + // If we're not following the file, just bail here. + if( !follow ) { + if( buffer.len() ) { + print.line(); + } + return; + } + + position = startingLength; + // This lets the thread know we're still running + variables.tailRun = true; + + try { + // This thread will keep redrawing the screen while the main thread waits for user input + threadName = 'tail#createUUID()#'; + thread action="run" name=threadName priority="HIGH" { + try{ + // Only keep drawing as long as the main thread is active + while( variables.tailRun ) { + + var randomAccessFile = createObject( "java", "java.io.RandomAccessFile" ).init( file, "r" ); + randomAccessFile.seek( position ); + // As long as there is at least one more character in the file + while( ( var char = randomAccessFile.read() ) > -1 ){ + // output it + print + .text( chr( char ) ) + .toConsole(); + + randomAccessFile.seek( ++position ); + } + // Close the file every time so we don't keep it open and locked + randomAccessFile.close(); + + // Decrease this to speed up the Tail + sleep( 300 ); + } + } catch( any e ) { + logger.error( e.message & ' ' & e.detail, e.stacktrace ); + } finally { + // Clean up after ourselves if anything went wrong + if( isDefined( 'randomAccessFile' ) ) { + randomAccessFile.close(); + } + } + + } // End thread + + while( true ) { + // Wipe out prompt so it doesn't redraw if the user hits enter + shell.getReader().setPrompt( '' ); + + // Detect user pressing Ctrl-C + // Any other characters captured will be ignored + var line = shell.getReader().readLine(); + if( line == 'q' ) { + break; + } else { + print.boldRedLine( 'To exit press Ctrl-C or "q" followed the enter key.' ).toConsole(); + } + } + + + // user wants to exit, they've pressed Ctrl-C + } catch ( jline.console.UserInterruptException e ) { + // make sure the thread exits + variables.tailRun = false; + // Wait until the thread finishes its last draw + thread action="join" name=threadName; + shell.setPrompt(); + // Something horrible went wrong + } catch ( any e ) { + // make sure the thread exits + variables.tailRun = false; + // Wait until the thread finishes its last draw + thread action="join" name=threadName; + shell.setPrompt(); + rethrow; + } + + // We're done with the Tail, clean up. + variables.tailRun = false; + // Wait until the thread finishes its last draw + thread action="join" name=threadName; + shell.setPrompt(); + + } + + // Deal with BOM (Byte order mark) + // TODO: Actually pay attention to the BOM! + function findStartPos() { + var randomAccessFile = createObject( "java", "java.io.RandomAccessFile" ).init( file, "r" ); + randomAccessFile.seek( 0 ); + var length = randomAccessFile.length(); + var startPos = 0 + ; + // Will contain the first few bytes of the file represented by an integer + var peek = ''; + + // If the file has a least 2 bytes + if( length > 1 ) { + // read them + peek &= randomAccessFile.read(); + randomAccessFile.seek( 1 ); + peek &= randomAccessFile.read(); + // If we found one of the 3 char BOMs + if( listFindNoCase( '254255,255254', peek ) ) { + // Start after it + startPos=2; + } + } + + // If the file has a least 3 bytes + if( length > 2 && ! startPos ) { + // read them + randomAccessFile.seek( 2 ); + peek &= randomAccessFile.read(); + // If we found one of the 3 char BOMs + if( listFindNoCase( '239187191', peek ) ) { + // Start after it + startPos=3; + } + } + // If the file has at least 4 bytes and we didn't find a 3 byte BOM + if( length > 3 && ! startPos) { + // Read the fourth byte + randomAccessFile.seek( 3 ); + peek &= randomAccessFile.read(); + // If we found one of the 4 char BOMs + if( listFindNoCase( '00254255,25525400', peek ) ) { + // Start after it + startPos=4; + } + } + + randomAccessFile.close(); + return startPos; } } \ No newline at end of file From a5ea90d0a23a0893b266c334a4784615c36fa8b3 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 18 Nov 2016 22:26:27 -0600 Subject: [PATCH 06/87] COMMANDBOX-504 --- .../system-commands/commands/tail.cfc | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/tail.cfc b/src/cfml/system/modules_app/system-commands/commands/tail.cfc index abd16239f..0dee1a0c7 100644 --- a/src/cfml/system/modules_app/system-commands/commands/tail.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/tail.cfc @@ -15,16 +15,33 @@ component { /** - * @path file or directory to tail + * @path file or directory to tail or raw input to process * @lines number of lines to display. * @follow Keep outputting new lines to the file until Ctrl-C is pressed **/ function run( required path, numeric lines = 15, boolean follow = false ){ - + var rawText = false; + var inputAsArray = listToArray( arguments.path, chr(13) & chr(10) ); + + // If there is a line break in the input, then it's raw text + if( inputAsArray.len() > 1 ) { + var rawText = true; + } var filePath = fileSystemUtil.resolvePath( arguments.path ); if( !fileExists( filePath ) ){ - return error( "The file does not exist: #arguments.path#" ); + var rawText = true; + } + + // If we're piping raw text and not a file + if( rawText ) { + // Only show the last X lines + var startIndex = max( inputAsArray.len() - arguments.lines + 1, 1 ); + while( startIndex <= inputAsArray.len() ) { + print.line( inputAsArray[ startIndex++ ] ); + } + + return; } variables.file = createObject( "java", "java.io.File" ).init( filePath ); From 90e841b71b0ab1d6cf369112a4336a37e9c5bc98 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 18 Nov 2016 22:54:19 -0600 Subject: [PATCH 07/87] COMMANDBOX-505 --- src/java/cliloader/LoaderCLIMain.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/java/cliloader/LoaderCLIMain.java b/src/java/cliloader/LoaderCLIMain.java index a8971b001..e8678f127 100644 --- a/src/java/cliloader/LoaderCLIMain.java +++ b/src/java/cliloader/LoaderCLIMain.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import org.apache.commons.io.input.BOMInputStream; import net.minidev.json.JSONArray; @@ -295,11 +296,11 @@ private static File getCLI_HOME( ArrayList< String > cliArguments, } if( cliPropFile.isFile() ) { Properties userProps = new Properties(); - FileInputStream fi; + InputStream fi; try { log.debug( "checking for home in properties from " + cliPropFile.getCanonicalPath() ); - fi = new FileInputStream( cliPropFile ); + fi = new BOMInputStream( new FileInputStream( cliPropFile ), false ); userProps.load( fi ); fi.close(); if( mapGetNoCase( userProps, "cli.home" ) != null ) { @@ -476,7 +477,7 @@ public static void main( String[] arguments ) throws Throwable{ if( cliPropFile.isFile() ) { log.debug( "merging properties from " + cliPropFile.getCanonicalPath() ); - FileInputStream fi = new FileInputStream( cliPropFile ); + InputStream fi = new BOMInputStream( new FileInputStream( cliPropFile ), false ); userProps.load( fi ); fi.close(); props = mergeProperties( props, userProps ); @@ -497,8 +498,7 @@ public static void main( String[] arguments ) throws Throwable{ } if( new File( cli_home, "cli.properties" ).isFile() ) { - FileInputStream fi = new FileInputStream( new File( cli_home, - "cli.properties" ) ); + InputStream fi = new BOMInputStream( new FileInputStream( new File( cli_home, "cli.properties" ) ), false ); userProps.load( fi ); fi.close(); props = mergeProperties( props, userProps ); From ad9c9bc5e52c0d16e6530697aae1c2e996ccb1e8 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 18 Nov 2016 23:28:00 -0600 Subject: [PATCH 08/87] COMMANDBOX-505 --- src/java/cliloader/BOMInputStream.java | 341 +++++++++++++++++++++++ src/java/cliloader/ByteOrderMark.java | 169 +++++++++++ src/java/cliloader/LoaderCLIMain.java | 1 - src/java/cliloader/ProxyInputStream.java | 237 ++++++++++++++++ 4 files changed, 747 insertions(+), 1 deletion(-) create mode 100644 src/java/cliloader/BOMInputStream.java create mode 100644 src/java/cliloader/ByteOrderMark.java create mode 100644 src/java/cliloader/ProxyInputStream.java diff --git a/src/java/cliloader/BOMInputStream.java b/src/java/cliloader/BOMInputStream.java new file mode 100644 index 000000000..a6ad17a84 --- /dev/null +++ b/src/java/cliloader/BOMInputStream.java @@ -0,0 +1,341 @@ +/* + * 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 cliloader; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; + + +/** + * This class is used to wrap a stream that includes an encoded + * {@link ByteOrderMark} as its first bytes. + * + * This class detects these bytes and, if required, can automatically skip them + * and return the subsequent byte as the first byte in the stream. + * + * The {@link ByteOrderMark} implementation has the following pre-defined BOMs: + *
    + *
  • UTF-8 - {@link ByteOrderMark#UTF_8}
  • + *
  • UTF-16BE - {@link ByteOrderMark#UTF_16LE}
  • + *
  • UTF-16LE - {@link ByteOrderMark#UTF_16BE}
  • + *
+ * + * + *

Example 1 - Detect and exclude a UTF-8 BOM

+ *
+ *      BOMInputStream bomIn = new BOMInputStream(in);
+ *      if (bomIn.hasBOM()) {
+ *          // has a UTF-8 BOM
+ *      }
+ * 
+ * + *

Example 2 - Detect a UTF-8 BOM (but don't exclude it)

+ *
+ *      boolean include = true;
+ *      BOMInputStream bomIn = new BOMInputStream(in, include);
+ *      if (bomIn.hasBOM()) {
+ *          // has a UTF-8 BOM
+ *      }
+ * 
+ * + *

Example 3 - Detect Multiple BOMs

+ *
+ *      BOMInputStream bomIn = new BOMInputStream(in, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE);
+ *      if (bomIn.hasBOM() == false) {
+ *          // No BOM found
+ *      } else if (bomIn.hasBOM(ByteOrderMark.UTF_16LE)) {
+ *          // has a UTF-16LE BOM
+ *      } else if (bomIn.hasBOM(ByteOrderMark.UTF_16BE)) {
+ *          // has a UTF-16BE BOM
+ *      }
+ * 
+ * + * @see org.apache.commons.io.ByteOrderMark + * @see Wikipedia - Byte Order Mark + * @version $Revision: 1052095 $ $Date: 2010-12-22 23:03:20 +0000 (Wed, 22 Dec 2010) $ + * @since Commons IO 2.0 + */ +public class BOMInputStream extends ProxyInputStream { + private final boolean include; + private final List boms; + private ByteOrderMark byteOrderMark; + private int[] firstBytes; + private int fbLength; + private int fbIndex; + private int markFbIndex; + private boolean markedAtStart; + + /** + * Constructs a new BOM InputStream that excludes + * a {@link ByteOrderMark#UTF_8} BOM. + * @param delegate the InputStream to delegate to + */ + public BOMInputStream(InputStream delegate) { + this(delegate, false, ByteOrderMark.UTF_8); + } + + /** + * Constructs a new BOM InputStream that detects a + * a {@link ByteOrderMark#UTF_8} and optionally includes it. + * @param delegate the InputStream to delegate to + * @param include true to include the UTF-8 BOM or + * false to exclude it + */ + public BOMInputStream(InputStream delegate, boolean include) { + this(delegate, include, ByteOrderMark.UTF_8); + } + + /** + * Constructs a new BOM InputStream that excludes + * the specified BOMs. + * @param delegate the InputStream to delegate to + * @param boms The BOMs to detect and exclude + */ + public BOMInputStream(InputStream delegate, ByteOrderMark... boms) { + this(delegate, false, boms); + } + + /** + * Constructs a new BOM InputStream that detects the + * specified BOMs and optionally includes them. + * @param delegate the InputStream to delegate to + * @param include true to include the specified BOMs or + * false to exclude them + * @param boms The BOMs to detect and optionally exclude + */ + public BOMInputStream(InputStream delegate, boolean include, ByteOrderMark... boms) { + super(delegate); + if (boms == null || boms.length == 0) { + throw new IllegalArgumentException("No BOMs specified"); + } + this.include = include; + this.boms = Arrays.asList(boms); + } + + /** + * Indicates whether the stream contains one of the specified BOMs. + * + * @return true if the stream has one of the specified BOMs, otherwise false + * if it does not + * @throws IOException if an error reading the first bytes of the stream occurs + */ + public boolean hasBOM() throws IOException { + return (getBOM() != null); + } + + /** + * Indicates whether the stream contains the specified BOM. + * + * @param bom The BOM to check for + * @return true if the stream has the specified BOM, otherwise false + * if it does not + * @throws IllegalArgumentException if the BOM is not one the stream + * is configured to detect + * @throws IOException if an error reading the first bytes of the stream occurs + */ + public boolean hasBOM(ByteOrderMark bom) throws IOException { + if (!boms.contains(bom)) { + throw new IllegalArgumentException("Stream not configure to detect " + bom); + } + return (byteOrderMark != null && getBOM().equals(bom)); + } + + /** + * Return the BOM (Byte Order Mark). + * + * @return The BOM or null if none + * @throws IOException if an error reading the first bytes of the stream occurs + */ + public ByteOrderMark getBOM() throws IOException { + if (firstBytes == null) { + int max = 0; + for (ByteOrderMark bom : boms) { + max = Math.max(max, bom.length()); + } + firstBytes = new int[max]; + for (int i = 0; i < firstBytes.length; i++) { + firstBytes[i] = in.read(); + fbLength++; + if (firstBytes[i] < 0) { + break; + } + + byteOrderMark = find(); + if (byteOrderMark != null) { + if (!include) { + fbLength = 0; + } + break; + } + } + } + return byteOrderMark; + } + + /** + * Return the BOM charset Name - {@link ByteOrderMark#getCharsetName()}. + * + * @return The BOM charset Name or null if no BOM found + * @throws IOException if an error reading the first bytes of the stream occurs + * + */ + public String getBOMCharsetName() throws IOException { + getBOM(); + return (byteOrderMark == null ? null : byteOrderMark.getCharsetName()); + } + + /** + * This method reads and either preserves or skips the first bytes in the + * stream. It behaves like the single-byte read() method, + * either returning a valid byte or -1 to indicate that the initial bytes + * have been processed already. + * @return the byte read (excluding BOM) or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + private int readFirstBytes() throws IOException { + getBOM(); + return (fbIndex < fbLength) ? firstBytes[fbIndex++] : -1; + } + + /** + * Find a BOM with the specified bytes. + * + * @return The matched BOM or null if none matched + */ + private ByteOrderMark find() { + for (ByteOrderMark bom : boms) { + if (matches(bom)) { + return bom; + } + } + return null; + } + + /** + * Check if the bytes match a BOM. + * + * @param bom The BOM + * @return true if the bytes match the bom, otherwise false + */ + private boolean matches(ByteOrderMark bom) { + if (bom.length() != fbLength) { + return false; + } + for (int i = 0; i < bom.length(); i++) { + if (bom.get(i) != firstBytes[i]) { + return false; + } + } + return true; + } + + //---------------------------------------------------------------------------- + // Implementation of InputStream + //---------------------------------------------------------------------------- + + /** + * Invokes the delegate's read() method, detecting and + * optionally skipping BOM. + * @return the byte read (excluding BOM) or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read() throws IOException { + int b = readFirstBytes(); + return (b >= 0) ? b : in.read(); + } + + /** + * Invokes the delegate's read(byte[], int, int) method, detecting + * and optionally skipping BOM. + * @param buf the buffer to read the bytes into + * @param off The start offset + * @param len The number of bytes to read (excluding BOM) + * @return the number of bytes read or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read(byte[] buf, int off, int len) throws IOException { + int firstCount = 0; + int b = 0; + while ((len > 0) && (b >= 0)) { + b = readFirstBytes(); + if (b >= 0) { + buf[off++] = (byte) (b & 0xFF); + len--; + firstCount++; + } + } + int secondCount = in.read(buf, off, len); + return (secondCount < 0) ? (firstCount > 0 ? firstCount : -1) : firstCount + secondCount; + } + + /** + * Invokes the delegate's read(byte[]) method, detecting and + * optionally skipping BOM. + * @param buf the buffer to read the bytes into + * @return the number of bytes read (excluding BOM) + * or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read(byte[] buf) throws IOException { + return read(buf, 0, buf.length); + } + + /** + * Invokes the delegate's mark(int) method. + * @param readlimit read ahead limit + */ + @Override + public synchronized void mark(int readlimit) { + markFbIndex = fbIndex; + markedAtStart = (firstBytes == null); + in.mark(readlimit); + } + + /** + * Invokes the delegate's reset() method. + * @throws IOException if an I/O error occurs + */ + @Override + public synchronized void reset() throws IOException { + fbIndex = markFbIndex; + if (markedAtStart) { + firstBytes = null; + } + + in.reset(); + } + + /** + * Invokes the delegate's skip(long) method, detecting + * and optionallyskipping BOM. + * @param n the number of bytes to skip + * @return the number of bytes to skipped or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public long skip(long n) throws IOException { + while ((n > 0) && (readFirstBytes() >= 0)) { + n--; + } + return in.skip(n); + } +} diff --git a/src/java/cliloader/ByteOrderMark.java b/src/java/cliloader/ByteOrderMark.java new file mode 100644 index 000000000..5808610fb --- /dev/null +++ b/src/java/cliloader/ByteOrderMark.java @@ -0,0 +1,169 @@ +/* + * 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 cliloader; + +import java.io.Serializable; + +/** + * Byte Order Mark (BOM) representation - + * see {@link org.apache.commons.io.input.BOMInputStream}. + * + * @see org.apache.commons.io.input.BOMInputStream + * @see Wikipedia - Byte Order Mark + * @version $Id: ByteOrderMark.java 1005099 2010-10-06 16:13:01Z niallp $ + * @since Commons IO 2.0 + */ +public class ByteOrderMark implements Serializable { + + private static final long serialVersionUID = 1L; + + /** UTF-8 BOM */ + public static final ByteOrderMark UTF_8 = new ByteOrderMark("UTF-8", 0xEF, 0xBB, 0xBF); + /** UTF-16BE BOM (Big Endian) */ + public static final ByteOrderMark UTF_16BE = new ByteOrderMark("UTF-16BE", 0xFE, 0xFF); + /** UTF-16LE BOM (Little Endian) */ + public static final ByteOrderMark UTF_16LE = new ByteOrderMark("UTF-16LE", 0xFF, 0xFE); + + private final String charsetName; + private final int[] bytes; + + /** + * Construct a new BOM. + * + * @param charsetName The name of the charset the BOM represents + * @param bytes The BOM's bytes + * @throws IllegalArgumentException if the charsetName is null or + * zero length + * @throws IllegalArgumentException if the bytes are null or zero + * length + */ + public ByteOrderMark(String charsetName, int... bytes) { + if (charsetName == null || charsetName.length() == 0) { + throw new IllegalArgumentException("No charsetName specified"); + } + if (bytes == null || bytes.length == 0) { + throw new IllegalArgumentException("No bytes specified"); + } + this.charsetName = charsetName; + this.bytes = new int[bytes.length]; + System.arraycopy(bytes, 0, this.bytes, 0, bytes.length); + } + + /** + * Return the name of the {@link java.nio.charset.Charset} the BOM represents. + * + * @return the character set name + */ + public String getCharsetName() { + return charsetName; + } + + /** + * Return the length of the BOM's bytes. + * + * @return the length of the BOM's bytes + */ + public int length() { + return bytes.length; + } + + /** + * The byte at the specified position. + * + * @param pos The position + * @return The specified byte + */ + public int get(int pos) { + return bytes[pos]; + } + + /** + * Return a copy of the BOM's bytes. + * + * @return a copy of the BOM's bytes + */ + public byte[] getBytes() { + byte[] copy = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + copy[i] = (byte)bytes[i]; + } + return copy; + } + + /** + * Indicates if this BOM's bytes equals another. + * + * @param obj The object to compare to + * @return true if the bom's bytes are equal, otherwise + * false + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ByteOrderMark)) { + return false; + } + ByteOrderMark bom = (ByteOrderMark)obj; + if (bytes.length != bom.length()) { + return false; + } + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] != bom.get(i)) { + return false; + } + } + return true; + } + + /** + * Return the hashcode for this BOM. + * + * @return the hashcode for this BOM. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hashCode = getClass().hashCode(); + for (int b : bytes) { + hashCode += b; + } + return hashCode; + } + + /** + * Provide a String representation of the BOM. + * + * @return the length of the BOM's bytes + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(getClass().getSimpleName()); + builder.append('['); + builder.append(charsetName); + builder.append(": "); + for (int i = 0; i < bytes.length; i++) { + if (i > 0) { + builder.append(","); + } + builder.append("0x"); + builder.append(Integer.toHexString(0xFF & bytes[i]).toUpperCase()); + } + builder.append(']'); + return builder.toString(); + } + +} diff --git a/src/java/cliloader/LoaderCLIMain.java b/src/java/cliloader/LoaderCLIMain.java index e8678f127..17569f49d 100644 --- a/src/java/cliloader/LoaderCLIMain.java +++ b/src/java/cliloader/LoaderCLIMain.java @@ -39,7 +39,6 @@ import java.util.List; import java.util.Map; import java.util.Properties; -import org.apache.commons.io.input.BOMInputStream; import net.minidev.json.JSONArray; diff --git a/src/java/cliloader/ProxyInputStream.java b/src/java/cliloader/ProxyInputStream.java new file mode 100644 index 000000000..a2a9d7737 --- /dev/null +++ b/src/java/cliloader/ProxyInputStream.java @@ -0,0 +1,237 @@ +/* + * 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 cliloader; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A Proxy stream which acts as expected, that is it passes the method + * calls on to the proxied stream and doesn't change which methods are + * being called. + *

+ * It is an alternative base class to FilterInputStream + * to increase reusability, because FilterInputStream changes the + * methods being called, such as read(byte[]) to read(byte[], int, int). + *

+ * See the protected methods for ways in which a subclass can easily decorate + * a stream with custom pre-, post- or error processing functionality. + * + * @author Stephen Colebourne + * @version $Id: ProxyInputStream.java 934041 2010-04-14 17:37:24Z jukka $ + */ +public abstract class ProxyInputStream extends FilterInputStream { + + /** + * Constructs a new ProxyInputStream. + * + * @param proxy the InputStream to delegate to + */ + public ProxyInputStream(InputStream proxy) { + super(proxy); + // the proxy is stored in a protected superclass variable named 'in' + } + + /** + * Invokes the delegate's read() method. + * @return the byte read or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read() throws IOException { + try { + beforeRead(1); + int b = in.read(); + afterRead(b != -1 ? 1 : -1); + return b; + } catch (IOException e) { + handleIOException(e); + return -1; + } + } + + /** + * Invokes the delegate's read(byte[]) method. + * @param bts the buffer to read the bytes into + * @return the number of bytes read or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read(byte[] bts) throws IOException { + try { + beforeRead(bts != null ? bts.length : 0); + int n = in.read(bts); + afterRead(n); + return n; + } catch (IOException e) { + handleIOException(e); + return -1; + } + } + + /** + * Invokes the delegate's read(byte[], int, int) method. + * @param bts the buffer to read the bytes into + * @param off The start offset + * @param len The number of bytes to read + * @return the number of bytes read or -1 if the end of stream + * @throws IOException if an I/O error occurs + */ + @Override + public int read(byte[] bts, int off, int len) throws IOException { + try { + beforeRead(len); + int n = in.read(bts, off, len); + afterRead(n); + return n; + } catch (IOException e) { + handleIOException(e); + return -1; + } + } + + /** + * Invokes the delegate's skip(long) method. + * @param ln the number of bytes to skip + * @return the actual number of bytes skipped + * @throws IOException if an I/O error occurs + */ + @Override + public long skip(long ln) throws IOException { + try { + return in.skip(ln); + } catch (IOException e) { + handleIOException(e); + return 0; + } + } + + /** + * Invokes the delegate's available() method. + * @return the number of available bytes + * @throws IOException if an I/O error occurs + */ + @Override + public int available() throws IOException { + try { + return super.available(); + } catch (IOException e) { + handleIOException(e); + return 0; + } + } + + /** + * Invokes the delegate's close() method. + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + try { + in.close(); + } catch (IOException e) { + handleIOException(e); + } + } + + /** + * Invokes the delegate's mark(int) method. + * @param readlimit read ahead limit + */ + @Override + public synchronized void mark(int readlimit) { + in.mark(readlimit); + } + + /** + * Invokes the delegate's reset() method. + * @throws IOException if an I/O error occurs + */ + @Override + public synchronized void reset() throws IOException { + try { + in.reset(); + } catch (IOException e) { + handleIOException(e); + } + } + + /** + * Invokes the delegate's markSupported() method. + * @return true if mark is supported, otherwise false + */ + @Override + public boolean markSupported() { + return in.markSupported(); + } + + /** + * Invoked by the read methods before the call is proxied. The number + * of bytes that the caller wanted to read (1 for the {@link #read()} + * method, buffer length for {@link #read(byte[])}, etc.) is given as + * an argument. + *

+ * Subclasses can override this method to add common pre-processing + * functionality without having to override all the read methods. + * The default implementation does nothing. + *

+ * Note this method is not called from {@link #skip(long)} or + * {@link #reset()}. You need to explicitly override those methods if + * you want to add pre-processing steps also to them. + * + * @since Commons IO 2.0 + * @param n number of bytes that the caller asked to be read + * @throws IOException if the pre-processing fails + */ + protected void beforeRead(int n) throws IOException { + } + + /** + * Invoked by the read methods after the proxied call has returned + * successfully. The number of bytes returned to the caller (or -1 if + * the end of stream was reached) is given as an argument. + *

+ * Subclasses can override this method to add common post-processing + * functionality without having to override all the read methods. + * The default implementation does nothing. + *

+ * Note this method is not called from {@link #skip(long)} or + * {@link #reset()}. You need to explicitly override those methods if + * you want to add post-processing steps also to them. + * + * @since Commons IO 2.0 + * @param n number of bytes read, or -1 if the end of stream was reached + * @throws IOException if the post-processing fails + */ + protected void afterRead(int n) throws IOException { + } + + /** + * Handle any IOExceptions thrown. + *

+ * This method provides a point to implement custom exception + * handling. The default behaviour is to re-throw the exception. + * @param e The IOException thrown + * @throws IOException if an I/O error occurs + * @since Commons IO 2.0 + */ + protected void handleIOException(IOException e) throws IOException { + throw e; + } + +} From d06aac0abcafd504797604a6e01bae103446d280 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 18 Nov 2016 23:29:34 -0600 Subject: [PATCH 09/87] Turn off for faster builds --- build/build-auto.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build-auto.properties b/build/build-auto.properties index c12b1597a..72fa144c1 100644 --- a/build/build-auto.properties +++ b/build/build-auto.properties @@ -1,5 +1,5 @@ #java build -java.pack200=true +java.pack200=false local.build=false #build locations From 7e91a7f1a5f59336b1d8dc478f6fba8830ad0b34 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 18 Nov 2016 23:56:12 -0600 Subject: [PATCH 10/87] Skip JRE snapshot builds --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index fd81b5f17..0adaf20c1 100644 --- a/build/build.xml +++ b/build/build.xml @@ -603,7 +603,7 @@ External Dependencies: - + From 567c83c31bde4ac85ab15282ec6de872f9ed8190 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 18 Nov 2016 23:58:17 -0600 Subject: [PATCH 11/87] new runwar snapshot --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index b0a0dc7d4..b3ce61410 100644 --- a/build/build.properties +++ b/build/build.properties @@ -17,7 +17,7 @@ cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} jre.version=1.8.0_102 launch4j.version=3.4 -runwar.version=3.4.11 +runwar.version=3.5.0-SNAPSHOT #build locations build.type=localdev From 291c1bac37b8402216685806fc0d35cd3e192bb5 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 19 Nov 2016 00:18:55 -0600 Subject: [PATCH 12/87] Temp workaround --- src/cfml/system/services/ServerService.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index d00ada539..e668b2a08 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -598,7 +598,7 @@ component accessors="true" singleton { & ' --open-browser #serverInfo.openbrowser#' & ' --open-url ' & ( serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#' ) & ( len( CFEngineName ) ? ' --cfengine-name "#CFEngineName#"' : '' ) - & ' --server-name "#serverInfo.name#" #errorPages#' + & ' --server-name "#serverInfo.name#" #errorPages# --welcome-files "index.cfm,index.cfml,default.cfm,index.html,index.htm,default.html,default.htm"' & ' --tray-icon "#serverInfo.trayIcon#" --tray-config "#trayOptionsPath#"' & ' --directoryindex "#serverInfo.directoryBrowsing#" --cfml-web-config "#serverInfo.webConfigDir#"' & ( len( CLIAliases ) ? ' --dirs "#CLIAliases#"' : '' ) From acbbd2a20ee451d9aa5a210e3757775b9fa69099 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 19 Nov 2016 00:40:27 -0600 Subject: [PATCH 13/87] servlet rest mappings --- src/cfml/system/services/ServerService.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index e668b2a08..1acb1d731 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -599,7 +599,7 @@ component accessors="true" singleton { & ' --open-url ' & ( serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#' ) & ( len( CFEngineName ) ? ' --cfengine-name "#CFEngineName#"' : '' ) & ' --server-name "#serverInfo.name#" #errorPages# --welcome-files "index.cfm,index.cfml,default.cfm,index.html,index.htm,default.html,default.htm"' - & ' --tray-icon "#serverInfo.trayIcon#" --tray-config "#trayOptionsPath#"' + & ' --tray-icon "#serverInfo.trayIcon#" --tray-config "#trayOptionsPath#" --servlet-rest-mappings "/rest/*,/api/*"' & ' --directoryindex "#serverInfo.directoryBrowsing#" --cfml-web-config "#serverInfo.webConfigDir#"' & ( len( CLIAliases ) ? ' --dirs "#CLIAliases#"' : '' ) & ' --cfml-server-config "#serverInfo.serverConfigDir#" #serverInfo.runwarArgs# --timeout #startupTimeout#'; From e26b4ad3caeb1e0f66b6842ca26b59cf5e6118ba Mon Sep 17 00:00:00 2001 From: Gareth Arch Date: Sat, 19 Nov 2016 11:55:56 -0500 Subject: [PATCH 14/87] Corrected minor spelling mistake --- .../modules_app/testbox-commands/commands/testbox/help.cfc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/help.cfc b/src/cfml/system/modules_app/testbox-commands/commands/testbox/help.cfc index b342a3d55..f8ca61870 100644 --- a/src/cfml/system/modules_app/testbox-commands/commands/testbox/help.cfc +++ b/src/cfml/system/modules_app/testbox-commands/commands/testbox/help.cfc @@ -3,7 +3,7 @@ component excludeFromHelp=true { function run() { print.line(); - print.yellow( 'The ' ); print.boldYellow( 'testbox' ); print.yellowLine( ' namespace helps you do anything related to your TestBox isntallation. Use these commands' ); + print.yellow( 'The ' ); print.boldYellow( 'testbox' ); print.yellowLine( ' namespace helps you do anything related to your TestBox installation. Use these commands' ); print.yellowLine( 'to create tests, generate runners, and even run your tests for you from the command line.' ); print.yellowLine( 'Type help before any command name to get additional information on how to call that specific command.' ); @@ -13,4 +13,4 @@ component excludeFromHelp=true { } -} \ No newline at end of file +} From 52f83ca5d9670dfad23eea199c5c2aedfbd86385 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Sun, 20 Nov 2016 17:55:21 -0500 Subject: [PATCH 15/87] Implements the ability to pass a custom timeout for server startup (#112) * add the ability to pass a custom timeout for server startup * update help * add server.json support for timeout argument * change start timeout key nesting * normalize naming conventions in arguments --- .../server-commands/commands/server/start.cfc | 4 ++- src/cfml/system/services/ServerService.cfc | 26 ++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc index 6b82fed58..f26f2c102 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc @@ -77,6 +77,7 @@ component aliases="start" { * @cfengine.optionsUDF cfengineNameComplete * @WARPath sets the path to an existing war to use * @serverConfigFile The path to the server's JSON file. Created if it doesn't exist. + * @timeout A custom timeout value for the server startup. **/ function run( @@ -108,7 +109,8 @@ component aliases="start" { boolean saveSettings=true, String cfengine, String WARPath, - String serverConfigFile + String serverConfigFile, + Numeric timeout ){ // This is a common mis spelling diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 1acb1d731..63ffe80ea 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -117,6 +117,7 @@ component accessors="true" singleton { return { name : d.name ?: '', openBrowser : d.openBrowser ?: true, + startTimeout : 120, stopsocket : d.stopsocket ?: 0, debug : d.debug ?: false, trayicon : d.trayicon ?: '', @@ -347,8 +348,17 @@ component accessors="true" singleton { case "runwarArgs": serverJSON[ 'runwar' ][ 'args' ] = serverProps[ prop ]; break; + case "timeout": + case "startTimeout": + serverJSON[ 'startTimeout' ] = serverProps[ prop ]; + //normalize our name conventions if the alias is passed + if( prop == 'timeout' ){ + serverProps[ 'startTimeout' ] = serverProps[ prop ]; + } + + break; default: - serverJSON[ prop ] = serverProps[ prop ]; + serverJSON[ prop ] = serverProps[ prop ]; } // end switch } // for loop @@ -418,6 +428,9 @@ component accessors="true" singleton { // Global defauls are always added on top of whatever is specified by the user or server.json serverInfo.runwarArgs = ( serverProps.runwarArgs ?: serverJSON.runwar.args ?: '' ) & ' ' & defaults.runwar.args; + + // Server startup timeout + serverInfo.startTimeout = serverProps.startTimeout ?: serverJSON.startTimeout ?: defaults.startTimeout; // Global defauls are always added on top of whatever is specified by the user or server.json serverInfo.libDirs = ( serverProps.libDirs ?: serverJSON.app.libDirs ?: '' ).listAppend( defaults.app.libDirs ); @@ -581,13 +594,14 @@ component accessors="true" singleton { 'tooltip' : processName, 'items' : serverInfo.trayOptions }; + fileWrite( trayOptionsPath, serializeJSON( trayJSON ) ); - - - var startupTimeout = 120; + // Increase our startup allowance for Adobe engines, since a number of files are generated on the first request - if( CFEngineName == 'adobe' ){ - startupTimeout=240; + var startupTimeout = serverInfo.startTimeout; + + if( startupTimeout == defaults.startTimeout && findNoCase( 'adobe', CFEngineName ) ){ + var startupTimeout= ( defaults.startTimeout * 2 ); } // The java arguments to execute: Shared server, custom web configs From 28714dc417a0ab484cb7d5e9c13fee292df628c2 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sun, 20 Nov 2016 21:40:54 -0600 Subject: [PATCH 16/87] Cleanup for COMMANDBOX-506 --- .../server-commands/commands/server/start.cfc | 4 +-- src/cfml/system/services/ServerService.cfc | 26 ++++--------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc index f26f2c102..bc177a622 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc @@ -77,7 +77,7 @@ component aliases="start" { * @cfengine.optionsUDF cfengineNameComplete * @WARPath sets the path to an existing war to use * @serverConfigFile The path to the server's JSON file. Created if it doesn't exist. - * @timeout A custom timeout value for the server startup. + * @startTimeout The amount of time in seconds to wait for the server to start (in the background). **/ function run( @@ -110,7 +110,7 @@ component aliases="start" { String cfengine, String WARPath, String serverConfigFile, - Numeric timeout + Numeric startTimeout ){ // This is a common mis spelling diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 63ffe80ea..763b54d00 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -117,7 +117,7 @@ component accessors="true" singleton { return { name : d.name ?: '', openBrowser : d.openBrowser ?: true, - startTimeout : 120, + startTimeout : 240, stopsocket : d.stopsocket ?: 0, debug : d.debug ?: false, trayicon : d.trayicon ?: '', @@ -348,15 +348,6 @@ component accessors="true" singleton { case "runwarArgs": serverJSON[ 'runwar' ][ 'args' ] = serverProps[ prop ]; break; - case "timeout": - case "startTimeout": - serverJSON[ 'startTimeout' ] = serverProps[ prop ]; - //normalize our name conventions if the alias is passed - if( prop == 'timeout' ){ - serverProps[ 'startTimeout' ] = serverProps[ prop ]; - } - - break; default: serverJSON[ prop ] = serverProps[ prop ]; } // end switch @@ -596,14 +587,7 @@ component accessors="true" singleton { }; fileWrite( trayOptionsPath, serializeJSON( trayJSON ) ); - - // Increase our startup allowance for Adobe engines, since a number of files are generated on the first request - var startupTimeout = serverInfo.startTimeout; - - if( startupTimeout == defaults.startTimeout && findNoCase( 'adobe', CFEngineName ) ){ - var startupTimeout= ( defaults.startTimeout * 2 ); - } - + // The java arguments to execute: Shared server, custom web configs var args = ' #serverInfo.JVMargs# -Xmx#serverInfo.heapSize#m -Xms#serverInfo.heapSize#m' & ' #javaagent# -jar "#variables.jarPath#"' @@ -616,7 +600,7 @@ component accessors="true" singleton { & ' --tray-icon "#serverInfo.trayIcon#" --tray-config "#trayOptionsPath#" --servlet-rest-mappings "/rest/*,/api/*"' & ' --directoryindex "#serverInfo.directoryBrowsing#" --cfml-web-config "#serverInfo.webConfigDir#"' & ( len( CLIAliases ) ? ' --dirs "#CLIAliases#"' : '' ) - & ' --cfml-server-config "#serverInfo.serverConfigDir#" #serverInfo.runwarArgs# --timeout #startupTimeout#'; + & ' --cfml-server-config "#serverInfo.serverConfigDir#" #serverInfo.runwarArgs# --timeout #serverInfo.startTimeout#'; // Starting a WAR if (serverInfo.WARPath != "" ) { @@ -664,7 +648,7 @@ component accessors="true" singleton { // thread the execution var threadName = 'server#hash( serverInfo.webroot )##createUUID()#'; - thread name="#threadName#" serverInfo=serverInfo args=args startupTimeout=startupTimeout { + thread name="#threadName#" serverInfo=serverInfo args=args startTimeout=serverInfo.startTimeout { try{ // execute the server command var executeResult = ''; @@ -673,7 +657,7 @@ component accessors="true" singleton { serverInfo.statusInfo = { command:variables.javaCommand, arguments:attributes.args, result:'' }; setServerInfo( serverInfo ); // Note this timeout is purposefully longer than the Runwar timeout so if the server takes too long, we get to capture the console info - execute name=variables.javaCommand arguments=attributes.args timeout="#startupTimeout+20#" variable="executeResult" errorVariable="executeError"; + execute name=variables.javaCommand arguments=attributes.args timeout="#startTimeout+20#" variable="executeResult" errorVariable="executeError"; serverInfo.status="running"; } catch (any e) { logger.error( "Error starting server: #e.message# #e.detail#", arguments ); From 5169e42c7ed3857e73d7518f69404818bc8a3fba Mon Sep 17 00:00:00 2001 From: chrisschmitz Date: Mon, 21 Nov 2016 22:41:14 +0100 Subject: [PATCH 17/87] COMMANDBOX-479, make artifacts path customizable (#114) --- src/cfml/system/services/ArtifactService.cfc | 21 ++++++++++++++------ src/cfml/system/services/ConfigService.cfc | 4 +++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/cfml/system/services/ArtifactService.cfc b/src/cfml/system/services/ArtifactService.cfc index 7689f1872..fbf940d5b 100644 --- a/src/cfml/system/services/ArtifactService.cfc +++ b/src/cfml/system/services/ArtifactService.cfc @@ -21,6 +21,8 @@ component accessors="true" singleton { property name='packageService' inject='PackageService'; property name='logger' inject='logbox:logger:{this}'; property name="semanticVersion" inject="semanticVersion"; + // COMMANDBOX-479 + property name="configService" inject="ConfigService"; /** @@ -29,8 +31,9 @@ component accessors="true" singleton { function onDIComplete() { // Create the artifacts directory if it doesn't exist - if( !directoryExists( variables.artifactDir ) ) { - directoryCreate( variables.artifactDir ); + // COMMANDBOX-479 + if( !directoryExists( getArtifactsDirectory() ) ) { + directoryCreate( getArtifactsDirectory() ); } } @@ -42,7 +45,8 @@ component accessors="true" singleton { */ struct function listArtifacts( packageName='' ) { var result = {}; - var dirList = directoryList( path=variables.artifactDir, recurse=false, listInfo='query', sort='name asc' ); + // COMMANDBOX-479 + var dirList = directoryList( path=getArtifactsDirectory(), recurse=false, listInfo='query', sort='name asc' ); for( var dir in dirList ) { if( dir.type == 'dir' && ( !arguments.packageName.len() || arguments.packageName == dir.name ) ) { @@ -67,7 +71,8 @@ component accessors="true" singleton { * Removes all artifacts from the cache and returns the number of wiped out directories */ numeric function cleanArtifacts() { - var qryDir = directoryList( path=variables.artifactDir, recurse=false, listInfo='query' ); + // COMMANDBOX-479 + var qryDir = directoryList( path=getArtifactsDirectory(), recurse=false, listInfo='query' ); var numRemoved = 0; for( var path in qryDir ) { @@ -110,7 +115,8 @@ component accessors="true" singleton { */ function getPackagePath( required packageName, version="" ){ // This will likely change, so I'm only going to put the code here. - var path = variables.artifactDir & '/' & arguments.packageName; + // COMMANDBOX-479 + var path = getArtifactsDirectory() & '/' & arguments.packageName; // do we have a version? if( arguments.version.len() ){ path &= "/" & arguments.version; @@ -261,5 +267,8 @@ component accessors="true" singleton { } } - + // COMMANDBOX-479 + string function getArtifactsDirectory() { + return configService.getSetting( 'artifactsDirectory', variables.artifactDir ); + } } \ No newline at end of file diff --git a/src/cfml/system/services/ConfigService.cfc b/src/cfml/system/services/ConfigService.cfc index e62e06dc3..bc22e193d 100644 --- a/src/cfml/system/services/ConfigService.cfc +++ b/src/cfml/system/services/ConfigService.cfc @@ -50,7 +50,9 @@ component accessors="true" singleton { 'endpoints.forgebox.APIURL', // Servers 'server', - 'server.defaults' + 'server.defaults', + // used in Artifactsservice + 'artifactsDirectory' ]); setConfigFilePath( '/commandbox-home/CommandBox.json' ); From ff56baef38bc4fd6372fc9efce9f04158eae86f2 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 21 Nov 2016 21:59:15 -0600 Subject: [PATCH 18/87] COMMANDBOX-501 --- src/cfml/system/services/ServerService.cfc | 454 +++++++++++---------- 1 file changed, 241 insertions(+), 213 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 763b54d00..af58bb2f1 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -226,13 +226,13 @@ component accessors="true" singleton { // Backwards compat for default port in box.json. Remove this eventually... // * // * // Get package descriptor // * - var boxJSON = packageService.readPackageDescriptor( defaultwebroot ); // * + var boxJSON = packageService.readPackageDescriptorRaw( defaultwebroot ); // * // Get defaults // * var defaults = getDefaultServerJSON(); // * // * // Backwards compat with boxJSON default port. Remove in a future version // * // The property in box.json is deprecated. // * - if( boxJSON.defaultPort > 0 ) { // * + if( (boxJSON.defaultPort ?: 0) > 0 ) { // * // * // Remove defaultPort from box.json and pretend it was // * // manually typed which will cause server.json to save it. // * @@ -456,239 +456,267 @@ component accessors="true" singleton { // log directory location if( !directoryExists( serverInfo.logDir ) ){ directoryCreate( serverInfo.logDir ); } - - // Default java agent for embedded Lucee engine - var javaagent = serverinfo.cfengine contains 'lucee' ? '-javaagent:"#libdir#/lucee-inst.jar"' : ''; - - // Not sure what Runwar does with this, but it wants to know what CFEngine we're starting (if we know) - var CFEngineName = ''; - CFEngineName = serverinfo.cfengine contains 'lucee' ? 'lucee' : CFEngineName; - CFEngineName = serverinfo.cfengine contains 'railo' ? 'railo' : CFEngineName; - CFEngineName = serverinfo.cfengine contains 'adobe' ? 'adobe' : CFEngineName; - - var thisVersion = ''; - var processName = ( serverInfo.name is "" ? "CommandBox" : serverInfo.name ); - - // As long as there's no WAR Path, let's install the engine to use. - if( serverInfo.WARPath == '' ){ + + // Default java agent for embedded Lucee engine + var javaagent = serverinfo.cfengine contains 'lucee' ? '-javaagent:"#libdir#/lucee-inst.jar"' : ''; + + // Not sure what Runwar does with this, but it wants to know what CFEngine we're starting (if we know) + var CFEngineName = ''; + CFEngineName = serverinfo.cfengine contains 'lucee' ? 'lucee' : CFEngineName; + CFEngineName = serverinfo.cfengine contains 'railo' ? 'railo' : CFEngineName; + CFEngineName = serverinfo.cfengine contains 'adobe' ? 'adobe' : CFEngineName; + + var thisVersion = ''; + var processName = ( serverInfo.name is "" ? "CommandBox" : serverInfo.name ); + + // As long as there's no WAR Path, let's install the engine to use. + if( serverInfo.WARPath == '' ){ + + // This will install the engine war to start, possibly downloading it first + var installDetails = serverEngineService.install( cfengine=serverInfo.cfengine, basedirectory=serverInfo.webConfigDir ); + // This interception point can be used for additional configuration of the engine before it actually starts. + interceptorService.announceInterception( 'onServerInstall', { serverInfo=serverInfo, installDetails=installDetails } ); + thisVersion = ' ' & installDetails.version; + serverInfo.serverHome = installDetails.installDir; + serverInfo.logdir = installDetails.installDir & "/logs"; + + // If external Lucee server, set the java agent + if( !installDetails.internal && serverInfo.cfengine contains "lucee" ) { + javaagent = "-javaagent:""#installDetails.installDir#/WEB-INF/lib/lucee-inst.jar"""; + } + // If external Railo server, set the java agent + if( !installDetails.internal && serverInfo.cfengine contains "railo" ) { + javaagent = "-javaagent:""#installDetails.installDir#/WEB-INF/lib/railo-inst.jar"""; + } + // Using built in server that hasn't been started before + if( installDetails.internal && !directoryExists( serverInfo.webConfigDir & '/WEB-INF' ) ) { + serverInfo.webConfigDir = installDetails.installDir; + serverInfo.logdir = serverInfo.webConfigDir & "/logs"; + } - // This will install the engine war to start, possibly downloading it first - var installDetails = serverEngineService.install( cfengine=serverInfo.cfengine, basedirectory=serverInfo.webConfigDir ); - // This interception point can be used for additional configuration of the engine before it actually starts. - interceptorService.announceInterception( 'onServerInstall', { serverInfo=serverInfo, installDetails=installDetails } ); - thisVersion = ' ' & installDetails.version; - serverInfo.serverHome = installDetails.installDir; - serverInfo.logdir = installDetails.installDir & "/logs"; + // The process native name + var processName = ( serverInfo.name is "" ? "CommandBox" : serverInfo.name ) & ' [' & listFirst( serverinfo.cfengine, '@' ) & thisVersion & ']'; + + // Find the correct tray icon for this server + if( !len( serverInfo.trayIcon ) ) { + var iconSize = fileSystemUtil.isWindows() ? '-32px' : ''; + if( serverInfo.cfengine contains "lucee" ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-lucee#iconSize#.png'; + } else if( serverInfo.cfengine contains "railo" ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-railo#iconSize#.png'; + } else if( serverInfo.cfengine contains "adobe" ) { + + if( listFirst( installDetails.version, '.' ) == 9 ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf09#iconSize#.png'; + } else if( listFirst( installDetails.version, '.' ) == 10 ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf10#iconSize#.png'; + } else if( listFirst( installDetails.version, '.' ) == 11 ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf11#iconSize#.png'; + } else if( listFirst( installDetails.version, '.' ) == 2016 ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf2016#iconSize#.png'; + } else { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf2016#iconSize#.png'; + } + + } + } + + // This is a WAR + } else { + serverInfo.serverHome = getDirectoryFromPath( serverInfo.WARPath ); + } - // If external Lucee server, set the java agent - if( !installDetails.internal && serverInfo.cfengine contains "lucee" ) { - javaagent = "-javaagent:""#installDetails.installDir#/WEB-INF/lib/lucee-inst.jar"""; + // Default tray icon + serverInfo.trayIcon = ( len( serverInfo.trayIcon ) ? serverInfo.trayIcon : '#variables.libdir#/trayicon.png' ); + serverInfo.trayIcon = expandPath( serverInfo.trayIcon ); + + // Set default options for all servers + // TODO: Don't overwrite existing options with the same label. + + if( serverInfo.cfengine contains "lucee" ) { + serverInfo.trayOptions.prepend( { 'label':'Open Web Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/lucee/admin/web.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/web_settings.png' ) } ); + serverInfo.trayOptions.prepend( { 'label':'Open Server Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/lucee/admin/server.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/server_settings.png' ) } ); + } else if( serverInfo.cfengine contains "railo" ) { + serverInfo.trayOptions.prepend( { 'label':'Open Web Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/railo-context/admin/web.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/web_settings.png' ) } ); + serverInfo.trayOptions.prepend( { 'label':'Open Server Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/railo-context/admin/server.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/server_settings.png' ) } ); + } else if( serverInfo.cfengine contains "adobe" ) { + serverInfo.trayOptions.prepend( { 'label':'Open Server Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/CFIDE/administrator/enter.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/server_settings.png' ) } ); } - // If external Railo server, set the java agent - if( !installDetails.internal && serverInfo.cfengine contains "railo" ) { - javaagent = "-javaagent:""#installDetails.installDir#/WEB-INF/lib/railo-inst.jar"""; + + serverInfo.trayOptions.prepend( { 'label':'Open Browser', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/', 'image' : expandPath('/commandbox/system/config/server-icons/home.png' ) } ); + serverInfo.trayOptions.prepend( { 'label':'Stop Server', 'action':'stopserver', 'image' : expandPath('/commandbox/system/config/server-icons/stop.png' ) } ); + serverInfo.trayOptions.prepend( { 'label': processName, 'disabled':true, 'image' : expandPath('/commandbox/system/config/server-icons/info.png' ) } ); + + // This is due to a bug in RunWar not creating the right directory for the logs + directoryCreate( serverInfo.logDir, true, true ); + + interceptorService.announceInterception( 'onServerStart', { serverInfo=serverInfo } ); + + // Turn struct of aliases into a comma-delimited list, plus resolve relative paths. + // "/foo=C:\path,/bar=C:\another/path" + var CLIAliases = ''; + for( var thisAlias in serverInfo.aliases ) { + CLIAliases = CLIAliases.listAppend( thisAlias & '=' & fileSystemUtil.resolvePath( serverInfo.aliases[ thisAlias ], serverInfo.webroot ) ); } - // Using built in server that hasn't been started before - if( installDetails.internal && !directoryExists( serverInfo.webConfigDir & '/WEB-INF' ) ) { - serverInfo.webConfigDir = installDetails.installDir; - serverInfo.logdir = serverInfo.webConfigDir & "/logs"; + + // Turn struct of errorPages into a comma-delimited list. + // --error-pages="404=/path/to/404.html,500=/path/to/500.html,1=/path/to/default.html" + var errorPages = ''; + for( var thisErrorPage in serverInfo.errorPages ) { + // "default" turns into "1" + var tmp = thisErrorPage == 'default' ? 1 : thisErrorPage; + tmp &= '='; + // normalize slashes + var thisPath = replace( serverInfo.errorPages[ thisErrorPage ], '\', '/', 'all' ); + // Add leading slash if it doesn't exist. + tmp &= thisPath.startsWith( '/' ) ? thisPath : '/' & thisPath; + errorPages = errorPages.listAppend( tmp ); } - - // The process native name - var processName = ( serverInfo.name is "" ? "CommandBox" : serverInfo.name ) & ' [' & listFirst( serverinfo.cfengine, '@' ) & thisVersion & ']'; - - // Find the correct tray icon for this server - if( !len( serverInfo.trayIcon ) ) { - var iconSize = fileSystemUtil.isWindows() ? '-32px' : ''; - if( serverInfo.cfengine contains "lucee" ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-lucee#iconSize#.png'; - } else if( serverInfo.cfengine contains "railo" ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-railo#iconSize#.png'; - } else if( serverInfo.cfengine contains "adobe" ) { + // Bug in runwar requires me to completley omit this param unless it's populated + // https://github.com/cfmlprojects/runwar/issues/33 + if( len( errorPages ) ) { + errorPages = '--error-pages="#errorPages#"'; + } + + // Serialize tray options and write to temp file + var trayOptionsPath = serverInfo.serverHome & '/trayOptions.json'; + var trayJSON = { + 'title' : processName, + 'tooltip' : processName, + 'items' : serverInfo.trayOptions + }; + + fileWrite( trayOptionsPath, serializeJSON( trayJSON ) ); + + // The java arguments to execute: Shared server, custom web configs + var args = ' #serverInfo.JVMargs# -Xmx#serverInfo.heapSize#m -Xms#serverInfo.heapSize#m' + & ' #javaagent# -jar "#variables.jarPath#"' + & ' --background=true --port #serverInfo.port# --host #serverInfo.host# --debug=#serverInfo.debug#' + & ' --stop-port #serverInfo.stopsocket# --processname "#processName#" --log-dir "#serverInfo.logDir#"' + & ' --open-browser #serverInfo.openbrowser#' + & ' --open-url ' & ( serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#' ) + & ( len( CFEngineName ) ? ' --cfengine-name "#CFEngineName#"' : '' ) + & ' --server-name "#serverInfo.name#" #errorPages# --welcome-files "index.cfm,index.cfml,default.cfm,index.html,index.htm,default.html,default.htm"' + & ' --tray-icon "#serverInfo.trayIcon#" --tray-config "#trayOptionsPath#" --servlet-rest-mappings "/rest/*,/api/*"' + & ' --directoryindex "#serverInfo.directoryBrowsing#" --cfml-web-config "#serverInfo.webConfigDir#"' + & ( len( CLIAliases ) ? ' --dirs "#CLIAliases#"' : '' ) + & ' --cfml-server-config "#serverInfo.serverConfigDir#" #serverInfo.runwarArgs# --timeout #serverInfo.startTimeout#'; - if( listFirst( installDetails.version, '.' ) == 9 ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf09#iconSize#.png'; - } else if( listFirst( installDetails.version, '.' ) == 10 ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf10#iconSize#.png'; - } else if( listFirst( installDetails.version, '.' ) == 11 ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf11#iconSize#.png'; - } else if( listFirst( installDetails.version, '.' ) == 2016 ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf2016#iconSize#.png'; - } else { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf2016#iconSize#.png'; - } - - } - } - - // This is a WAR - } else { - serverInfo.serverHome = getDirectoryFromPath( serverInfo.WARPath ); - } + // Starting a WAR + if (serverInfo.WARPath != "" ) { + args &= " -war ""#serverInfo.WARPath#"""; + // Stand alone server + } else if( !installDetails.internal ){ + args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#installDetails.installDir#/WEB-INF/lib"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; + // internal server + } else { + // The internal server borrows the CommandBox lib directory + serverInfo.libDirs = serverInfo.libDirs.listAppend( variables.libDir ); + args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#serverInfo.libDirs#"""; + } + // Incorporate SSL to command + if( serverInfo.SSLEnable ){ + args &= " --http-enable #serverInfo.HTTPEnable# --ssl-enable #serverInfo.SSLEnable# --ssl-port #serverInfo.SSLPort#"; + } + if( serverInfo.SSLEnable && serverInfo.SSLCert != "") { + args &= " --ssl-cert ""#serverInfo.SSLCert#"" --ssl-key ""#serverInfo.SSLKey#"" --ssl-keypass ""#serverInfo.SSLKeyPass#"""; + } + // Incorporate web-xml to command + if ( Len( Trim( serverInfo.webXml ?: "" ) ) ) { + args &= " --web-xml-path ""#serverInfo.webXml#"""; + } + // Incorporate rewrites to command + args &= " --urlrewrite-enable #serverInfo.rewritesEnable#"; - // Default tray icon - serverInfo.trayIcon = ( len( serverInfo.trayIcon ) ? serverInfo.trayIcon : '#variables.libdir#/trayicon.png' ); - serverInfo.trayIcon = expandPath( serverInfo.trayIcon ); - - // Set default options for all servers - // TODO: Don't overwrite existing options with the same label. - - if( serverInfo.cfengine contains "lucee" ) { - serverInfo.trayOptions.prepend( { 'label':'Open Web Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/lucee/admin/web.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/web_settings.png' ) } ); - serverInfo.trayOptions.prepend( { 'label':'Open Server Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/lucee/admin/server.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/server_settings.png' ) } ); - } else if( serverInfo.cfengine contains "railo" ) { - serverInfo.trayOptions.prepend( { 'label':'Open Web Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/railo-context/admin/web.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/web_settings.png' ) } ); - serverInfo.trayOptions.prepend( { 'label':'Open Server Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/railo-context/admin/server.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/server_settings.png' ) } ); - } else if( serverInfo.cfengine contains "adobe" ) { - serverInfo.trayOptions.prepend( { 'label':'Open Server Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/CFIDE/administrator/enter.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/server_settings.png' ) } ); - } - - serverInfo.trayOptions.prepend( { 'label':'Open Browser', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/', 'image' : expandPath('/commandbox/system/config/server-icons/home.png' ) } ); - serverInfo.trayOptions.prepend( { 'label':'Stop Server', 'action':'stopserver', 'image' : expandPath('/commandbox/system/config/server-icons/stop.png' ) } ); - serverInfo.trayOptions.prepend( { 'label': processName, 'disabled':true, 'image' : expandPath('/commandbox/system/config/server-icons/info.png' ) } ); - - // This is due to a bug in RunWar not creating the right directory for the logs - directoryCreate( serverInfo.logDir, true, true ); - - interceptorService.announceInterception( 'onServerStart', { serverInfo=serverInfo } ); - - // Turn struct of aliases into a comma-delimited list, plus resolve relative paths. - // "/foo=C:\path,/bar=C:\another/path" - var CLIAliases = ''; - for( var thisAlias in serverInfo.aliases ) { - CLIAliases = CLIAliases.listAppend( thisAlias & '=' & fileSystemUtil.resolvePath( serverInfo.aliases[ thisAlias ], serverInfo.webroot ) ); - } - - // Turn struct of errorPages into a comma-delimited list. - // --error-pages="404=/path/to/404.html,500=/path/to/500.html,1=/path/to/default.html" - var errorPages = ''; - for( var thisErrorPage in serverInfo.errorPages ) { - // "default" turns into "1" - var tmp = thisErrorPage == 'default' ? 1 : thisErrorPage; - tmp &= '='; - // normalize slashes - var thisPath = replace( serverInfo.errorPages[ thisErrorPage ], '\', '/', 'all' ); - // Add leading slash if it doesn't exist. - tmp &= thisPath.startsWith( '/' ) ? thisPath : '/' & thisPath; - errorPages = errorPages.listAppend( tmp ); - } - // Bug in runwar requires me to completley omit this param unless it's populated - // https://github.com/cfmlprojects/runwar/issues/33 - if( len( errorPages ) ) { - errorPages = '--error-pages="#errorPages#"'; - } - - // Serialize tray options and write to temp file - var trayOptionsPath = serverInfo.serverHome & '/trayOptions.json'; - var trayJSON = { - 'title' : processName, - 'tooltip' : processName, - 'items' : serverInfo.trayOptions - }; - - fileWrite( trayOptionsPath, serializeJSON( trayJSON ) ); - - // The java arguments to execute: Shared server, custom web configs - var args = ' #serverInfo.JVMargs# -Xmx#serverInfo.heapSize#m -Xms#serverInfo.heapSize#m' - & ' #javaagent# -jar "#variables.jarPath#"' - & ' --background=true --port #serverInfo.port# --host #serverInfo.host# --debug=#serverInfo.debug#' - & ' --stop-port #serverInfo.stopsocket# --processname "#processName#" --log-dir "#serverInfo.logDir#"' - & ' --open-browser #serverInfo.openbrowser#' - & ' --open-url ' & ( serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#' ) - & ( len( CFEngineName ) ? ' --cfengine-name "#CFEngineName#"' : '' ) - & ' --server-name "#serverInfo.name#" #errorPages# --welcome-files "index.cfm,index.cfml,default.cfm,index.html,index.htm,default.html,default.htm"' - & ' --tray-icon "#serverInfo.trayIcon#" --tray-config "#trayOptionsPath#" --servlet-rest-mappings "/rest/*,/api/*"' - & ' --directoryindex "#serverInfo.directoryBrowsing#" --cfml-web-config "#serverInfo.webConfigDir#"' - & ( len( CLIAliases ) ? ' --dirs "#CLIAliases#"' : '' ) - & ' --cfml-server-config "#serverInfo.serverConfigDir#" #serverInfo.runwarArgs# --timeout #serverInfo.startTimeout#'; + if( serverInfo.rewritesEnable ){ + if( !fileExists(serverInfo.rewritesConfig) ){ + consoleLogger.error( '.' ); + consoleLogger.error( 'URL rewrite config not found [#serverInfo.rewritesConfig#]' ); + consoleLogger.error( '.' ); + return; + } + args &= " --urlrewrite-file ""#serverInfo.rewritesConfig#"""; + } + // change status to starting + persist + serverInfo.status = "starting"; + setServerInfo( serverInfo ); - // Starting a WAR - if (serverInfo.WARPath != "" ) { - args &= " -war ""#serverInfo.WARPath#"""; - // Stand alone server - } else if( !installDetails.internal ){ - args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#installDetails.installDir#/WEB-INF/lib"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; - // internal server - } else { - // The internal server borrows the CommandBox lib directory - serverInfo.libDirs = serverInfo.libDirs.listAppend( variables.libDir ); - args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#serverInfo.libDirs#"""; - } - // Incorporate SSL to command - if( serverInfo.SSLEnable ){ - args &= " --http-enable #serverInfo.HTTPEnable# --ssl-enable #serverInfo.SSLEnable# --ssl-port #serverInfo.SSLPort#"; - } - if( serverInfo.SSLEnable && serverInfo.SSLCert != "") { - args &= " --ssl-cert ""#serverInfo.SSLCert#"" --ssl-key ""#serverInfo.SSLKey#"" --ssl-keypass ""#serverInfo.SSLKeyPass#"""; - } - // Incorporate web-xml to command - if ( Len( Trim( serverInfo.webXml ?: "" ) ) ) { - args &= " --web-xml-path ""#serverInfo.webXml#"""; - } - // Incorporate rewrites to command - args &= " --urlrewrite-enable #serverInfo.rewritesEnable#"; + if( serverInfo.debug ) { + var cleanedArgs = cr & ' ' & trim( replaceNoCase( args, ' -', cr & ' -', 'all' ) ); + consoleLogger.debug("Server start command: #javaCommand# #cleanedArgs#"); + } + + // needs to be unique in each run to avoid errors + var threadName = 'server#hash( serverInfo.webroot )##createUUID()#'; + // Construct a new process object + var processBuilder = createObject( "java", "java.lang.ProcessBuilder" ); + // Pass array of tokens comprised of command plus arguments + var processTokens = [ variables.javaCommand ] + processTokens.append( args.listToArray( ' ' ), true ); + processBuilder.init( processTokens ); + // Conjoin standard error and output for convenience. + processBuilder.redirectErrorStream( true ); + // Kick off actual process + variables.process = processBuilder.start(); - if( serverInfo.rewritesEnable ){ - if( !fileExists(serverInfo.rewritesConfig) ){ - consoleLogger.error( '.' ); - consoleLogger.error( 'URL rewrite config not found [#serverInfo.rewritesConfig#]' ); - consoleLogger.error( '.' ); - return; - } - args &= " --urlrewrite-file ""#serverInfo.rewritesConfig#"""; - } - // change status to starting + persist - serverInfo.status = "starting"; - setServerInfo( serverInfo ); + // She'll be coming 'round the mountain when she comes... + consoleLogger.warn( "The server for #serverInfo.webroot# is starting on #serverInfo.host#:#serverInfo.port#..." ); + + // If the user is running a one-off command to start a server or specified the debug flag, stream the output and wait until it's finished starting. + var interactiveStart = ( shell.getShellType() == 'command' || serverInfo.debug ); - if( serverInfo.debug ) { - var cleanedArgs = cr & ' ' & trim( replaceNoCase( args, ' -', cr & ' -', 'all' ) ); - consoleLogger.debug("Server start command: #javaCommand# #cleanedArgs#"); - } - - // thread the execution - var threadName = 'server#hash( serverInfo.webroot )##createUUID()#'; - thread name="#threadName#" serverInfo=serverInfo args=args startTimeout=serverInfo.startTimeout { + // Spin up a thread to capture the standard out and error from the server + thread name="#threadName#" interactiveStart=interactiveStart serverInfo=serverInfo args=args startTimeout=serverInfo.startTimeout { try{ - // execute the server command - var executeResult = ''; - var executeError = ''; + // save server info and persist serverInfo.statusInfo = { command:variables.javaCommand, arguments:attributes.args, result:'' }; + serverInfo.status="starting"; setServerInfo( serverInfo ); - // Note this timeout is purposefully longer than the Runwar timeout so if the server takes too long, we get to capture the console info - execute name=variables.javaCommand arguments=attributes.args timeout="#startTimeout+20#" variable="executeResult" errorVariable="executeError"; - serverInfo.status="running"; - } catch (any e) { - logger.error( "Error starting server: #e.message# #e.detail#", arguments ); - serverInfo.statusInfo.result &= e.message & ' ' & e.detail; + + var startOutput = createObject( 'java', 'java.lang.StringBuilder' ).init(); + var inputStream = process.getInputStream(); + var print = wirebox.getInstance( "PrintBuffer" ); + + while( ( var char = inputStream.read() ) > -1 ){ + // Build up our output + startOutput.append( chr( char ) ); + + // output it if we're being interactive + if( attributes.interactiveStart ) { + print + .text( chr( char ) ) + .toConsole(); + } + } // End of inputStream + + // When we require Java 8 for CommandBox, we can pass a timeout to waitFor(). + var exitCode = process.waitFor(); + + if( exitCode == 0 ) { + serverInfo.status="running"; + } else { + serverInfo.status="unknown"; + } + + } catch( any e ) { + logger.error( e.message & ' ' & e.detail, e.stacktrace ); serverInfo.status="unknown"; } finally { - // make sure these don't come back as nulls - param name='local.executeResult' default=''; - param name='local.executeError' default=''; - serverInfo.statusInfo.result = serverInfo.statusInfo.result & executeResult & ' ' & executeError; - setServerInfo( serverInfo ); + // Make sure we always close the file or the process will never quit! + if( isDefined( 'inputStream' ) ) { + inputStream.close(); + } + serverInfo.statusInfo.result = startOutput.toString(); + setServerInfo( serverInfo ); } } - - // She'll be coming 'round the mountain when she comes... - consoleLogger.warn( "The server for #serverInfo.webroot# is starting on #serverInfo.host#:#serverInfo.port#... type 'server status' to see result" ); - - // If this is a one off command, wait for the thread to finish, otherwise the JVM will shutdown before - // the server is started and the json files get updated. - if( shell.getShellType() == 'command' || serverInfo.debug ) { + // Block until the process ends and the streaming output thread above is done. + if( interactiveStart ) { thread action="join" name="#threadName#"; - - // Pull latest info that was saved in the thread and output it. Since we made the - // user wait for the thread to finish, we might as well tell them what happened. - wirebox.getinstance( name='CommandDSL', initArguments={ name : 'server status' } ) - .params( name = serverInfo.name ) - .run(); } - } /** From d4a58e83f58f9f127127e523876cc620aebfb0a0 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 21 Nov 2016 22:37:55 -0600 Subject: [PATCH 19/87] COMMANDBOX-508 --- .../server-commands/commands/server/start.cfc | 8 ++-- src/cfml/system/services/ServerService.cfc | 37 +++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc index bc177a622..1d6180098 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc @@ -78,6 +78,7 @@ component aliases="start" { * @WARPath sets the path to an existing war to use * @serverConfigFile The path to the server's JSON file. Created if it doesn't exist. * @startTimeout The amount of time in seconds to wait for the server to start (in the background). + * @console Start this server in the forground console process and wait until Ctrl-C is pressed to stop it. **/ function run( @@ -103,14 +104,15 @@ component aliases="start" { Boolean rewritesEnable, String rewritesConfig, Numeric heapSize, - boolean directoryBrowsing, + Boolean directoryBrowsing, String JVMArgs, String runwarArgs, - boolean saveSettings=true, + Boolean saveSettings=true, String cfengine, String WARPath, String serverConfigFile, - Numeric startTimeout + Numeric startTimeout, + Boolean console ){ // This is a common mis spelling diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index af58bb2f1..388f14be3 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -587,11 +587,11 @@ component accessors="true" singleton { }; fileWrite( trayOptionsPath, serializeJSON( trayJSON ) ); - + var background = !(serverProps.console ?: true); // The java arguments to execute: Shared server, custom web configs var args = ' #serverInfo.JVMargs# -Xmx#serverInfo.heapSize#m -Xms#serverInfo.heapSize#m' & ' #javaagent# -jar "#variables.jarPath#"' - & ' --background=true --port #serverInfo.port# --host #serverInfo.host# --debug=#serverInfo.debug#' + & ' --background=#background# --port #serverInfo.port# --host #serverInfo.host# --debug=#serverInfo.debug#' & ' --stop-port #serverInfo.stopsocket# --processname "#processName#" --log-dir "#serverInfo.logDir#"' & ' --open-browser #serverInfo.openbrowser#' & ' --open-url ' & ( serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#' ) @@ -663,7 +663,7 @@ component accessors="true" singleton { consoleLogger.warn( "The server for #serverInfo.webroot# is starting on #serverInfo.host#:#serverInfo.port#..." ); // If the user is running a one-off command to start a server or specified the debug flag, stream the output and wait until it's finished starting. - var interactiveStart = ( shell.getShellType() == 'command' || serverInfo.debug ); + var interactiveStart = ( shell.getShellType() == 'command' || serverInfo.debug || !background ); // Spin up a thread to capture the standard out and error from the server thread name="#threadName#" interactiveStart=interactiveStart serverInfo=serverInfo args=args startTimeout=serverInfo.startTimeout { @@ -714,6 +714,37 @@ component accessors="true" singleton { // Block until the process ends and the streaming output thread above is done. if( interactiveStart ) { + + if( !background ) { + try { + + while( true ) { + // Wipe out prompt so it doesn't redraw if the user hits enter + shell.getReader().setPrompt( '' ); + + // Detect user pressing Ctrl-C + // Any other characters captured will be ignored + var line = shell.getReader().readLine(); + if( line == 'q' ) { + break; + } else { + consoleLogger.error( 'To exit press Ctrl-C or "q" followed the enter key.' ); + } + } + + // user wants to exit, they've pressed Ctrl-C + } catch ( jline.console.UserInterruptException e ) { + // Something bad happened + } catch ( Any e ) { + logger.error( '#e.message# #e.detail#' , e.stackTrace ); + consoleLogger.error( '#e.message##chr(10)##e.detail#' ); + // Either way, this server is done like dinner + } finally { + shell.setPrompt(); + process.destroy(); + } + } + thread action="join" name="#threadName#"; } From 18fd8618eee0659362b199651dae814a81dea2b7 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 22 Nov 2016 10:17:40 -0600 Subject: [PATCH 20/87] Had wrong defauit --- src/cfml/system/services/ServerService.cfc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 388f14be3..c21be922a 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -587,7 +587,7 @@ component accessors="true" singleton { }; fileWrite( trayOptionsPath, serializeJSON( trayJSON ) ); - var background = !(serverProps.console ?: true); + var background = !(serverProps.console ?: false); // The java arguments to execute: Shared server, custom web configs var args = ' #serverInfo.JVMargs# -Xmx#serverInfo.heapSize#m -Xms#serverInfo.heapSize#m' & ' #javaagent# -jar "#variables.jarPath#"' @@ -740,6 +740,7 @@ component accessors="true" singleton { consoleLogger.error( '#e.message##chr(10)##e.detail#' ); // Either way, this server is done like dinner } finally { + consoleLogger.error( 'Stopping server...' ); shell.setPrompt(); process.destroy(); } From e94a574b68c91ac0106feb44df0204a525b3854b Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 22 Nov 2016 10:30:05 -0600 Subject: [PATCH 21/87] Bump loader with new runwar version --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index b3ce61410..1136b55d8 100644 --- a/build/build.properties +++ b/build/build.properties @@ -12,7 +12,7 @@ java.pack200=false #dependencies dependencies.dir=${basedir}/lib cfml.version=4.5.4.017 -cfml.loader.version=1.4.1 +cfml.loader.version=1.4.2 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} jre.version=1.8.0_102 From 8bb557a8505bd24e685e7cda82f23354ef71c2de Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 22 Nov 2016 11:26:19 -0600 Subject: [PATCH 22/87] Removing quotes for processbuilder on *unix --- src/cfml/system/services/ServerService.cfc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index c21be922a..f940d4335 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -458,7 +458,7 @@ component accessors="true" singleton { // Default java agent for embedded Lucee engine - var javaagent = serverinfo.cfengine contains 'lucee' ? '-javaagent:"#libdir#/lucee-inst.jar"' : ''; + var javaagent = serverinfo.cfengine contains 'lucee' ? '-javaagent:#libdir#/lucee-inst.jar' : ''; // Not sure what Runwar does with this, but it wants to know what CFEngine we're starting (if we know) var CFEngineName = ''; @@ -482,11 +482,11 @@ component accessors="true" singleton { // If external Lucee server, set the java agent if( !installDetails.internal && serverInfo.cfengine contains "lucee" ) { - javaagent = "-javaagent:""#installDetails.installDir#/WEB-INF/lib/lucee-inst.jar"""; + javaagent = "-javaagent:#installDetails.installDir#/WEB-INF/lib/lucee-inst.jar"; } // If external Railo server, set the java agent if( !installDetails.internal && serverInfo.cfengine contains "railo" ) { - javaagent = "-javaagent:""#installDetails.installDir#/WEB-INF/lib/railo-inst.jar"""; + javaagent = "-javaagent:#installDetails.installDir#/WEB-INF/lib/railo-inst.jar"; } // Using built in server that hasn't been started before if( installDetails.internal && !directoryExists( serverInfo.webConfigDir & '/WEB-INF' ) ) { @@ -590,7 +590,7 @@ component accessors="true" singleton { var background = !(serverProps.console ?: false); // The java arguments to execute: Shared server, custom web configs var args = ' #serverInfo.JVMargs# -Xmx#serverInfo.heapSize#m -Xms#serverInfo.heapSize#m' - & ' #javaagent# -jar "#variables.jarPath#"' + & ' #javaagent# -jar #variables.jarPath#' & ' --background=#background# --port #serverInfo.port# --host #serverInfo.host# --debug=#serverInfo.debug#' & ' --stop-port #serverInfo.stopsocket# --processname "#processName#" --log-dir "#serverInfo.logDir#"' & ' --open-browser #serverInfo.openbrowser#' From c0708c27f0432dd7bb6586a9381ea64407a897cb Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 22 Nov 2016 11:50:40 -0600 Subject: [PATCH 23/87] Add debugging --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 0adaf20c1..c7e53ffe1 100644 --- a/build/build.xml +++ b/build/build.xml @@ -487,7 +487,7 @@ External Dependencies: - + From eb013bc1a6b2a8264771c2c00a591ebdda5b79b9 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 22 Nov 2016 13:53:48 -0600 Subject: [PATCH 24/87] COMMANDBOX-509 --- src/cfml/system/util/Parser.cfc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cfml/system/util/Parser.cfc b/src/cfml/system/util/Parser.cfc index 79e5c2792..db7e3fa3b 100644 --- a/src/cfml/system/util/Parser.cfc +++ b/src/cfml/system/util/Parser.cfc @@ -64,6 +64,11 @@ component { // If we're in the middle of a quoted string, just keep appending if( inQuotes ) { + // Auto-escape = in a quoted string so it doesn't screw up named-parmeter detection. + // It will be unescaped later when we parse the params. + if( char == '=' && !isEscaped ) { + token &= '\'; + } token &= char; // We just reached the end of our quoted string if( char == quoteChar && !isEscaped ) { From 2c977bf0b67aa3d6dfb12ab9b4569787fe01d617 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 22 Nov 2016 15:43:23 -0600 Subject: [PATCH 25/87] COMMANDBOX-510 --- .../modules_app/system-commands/commands/cfml.cfc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/cfml.cfc b/src/cfml/system/modules_app/system-commands/commands/cfml.cfc index abb47f7f7..d5888057e 100644 --- a/src/cfml/system/modules_app/system-commands/commands/cfml.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/cfml.cfc @@ -135,9 +135,17 @@ component{ // double quotes are \" instead of "". Any escaped double quotes must be converted // to the CFML version to work as an object literal. private function convertJSONEscapesToCFML( required string arg ) { - arguments.arg = replaceNoCase( arguments.arg, '\\', '__DOUBLE_ESCAPE__', 'all' ); + arguments.arg = replaceNoCase( arguments.arg, '\\', '__double_backslash_', 'all' ); + arguments.arg = replaceNoCase( arguments.arg, '\/', '/', 'all' ); arguments.arg = replaceNoCase( arguments.arg, '\"', '""', 'all' ); - arguments.arg = replaceNoCase( arguments.arg, '__DOUBLE_ESCAPE__', '\\', 'all' ); + arguments.arg = replaceNoCase( arguments.arg, '\t', ' ', 'all' ); + arguments.arg = replaceNoCase( arguments.arg, '\n', chr(13)&chr(10), 'all' ); + arguments.arg = replaceNoCase( arguments.arg, '\r', chr(13), 'all' ); + arguments.arg = replaceNoCase( arguments.arg, '\f', chr(12), 'all' ); + arguments.arg = replaceNoCase( arguments.arg, '\b', chr(8), 'all' ); + // This doesn't work-- I'd need to do it in a loop and replace each one individually. Meh... + // arguments.arg = reReplaceNoCase( arguments.arg, '\\u([0-9a-f]{4})', chr( inputBaseN( '\1', 16 ) ), 'all' ); + arguments.arg = replaceNoCase( arguments.arg, '__double_backslash_', '\', 'all' ); return arguments.arg; } From a1b2553e1dd1aea6bc0e6b664bd3c6f0236da3e5 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 22 Nov 2016 23:36:53 -0600 Subject: [PATCH 26/87] improve api docs --- build/build.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index c7e53ffe1..14b69e69a 100644 --- a/build/build.xml +++ b/build/build.xml @@ -485,6 +485,10 @@ External Dependencies: + + + + @@ -512,8 +516,15 @@ External Dependencies: + retries="5" + ignoreerrors="true" /> + + + + + + From 2341c4854bedfab38f6934a9ef7a66308d8a2cab Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 23 Nov 2016 00:36:14 -0600 Subject: [PATCH 27/87] Bump timeouts for api docs to generate. --- apidocs/index.cfm | 1 + apidocs/internal.cfm | 1 + 2 files changed, 2 insertions(+) diff --git a/apidocs/index.cfm b/apidocs/index.cfm index 242044038..291877fe4 100644 --- a/apidocs/index.cfm +++ b/apidocs/index.cfm @@ -1,3 +1,4 @@ + diff --git a/apidocs/internal.cfm b/apidocs/internal.cfm index 664b712bc..034daa879 100644 --- a/apidocs/internal.cfm +++ b/apidocs/internal.cfm @@ -1,6 +1,7 @@ + From 8ec0f6e69148a8483568e143ba79a6bfc25c3806 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 29 Nov 2016 00:20:34 -0600 Subject: [PATCH 28/87] COMMANDBOX-514 --- src/cfml/system/config/urlrewrite.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cfml/system/config/urlrewrite.xml b/src/cfml/system/config/urlrewrite.xml index c724f6943..42380274c 100644 --- a/src/cfml/system/config/urlrewrite.xml +++ b/src/cfml/system/config/urlrewrite.xml @@ -5,6 +5,9 @@ Generic Front-Controller URLs /(flex2gateway|flashservices/gateway|messagebroker|lucee|rest|cfide|CFIDE|cfformgateway|jrunscripts)/.* + + /.*\.cf(m|ml)/.* From 85836983ff3dfc124b6a87e30cdd65549ff066e4 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 29 Nov 2016 00:22:06 -0600 Subject: [PATCH 29/87] Temp workaround for default files --- src/cfml/system/services/ServerService.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index f940d4335..450c7e05c 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -596,7 +596,7 @@ component accessors="true" singleton { & ' --open-browser #serverInfo.openbrowser#' & ' --open-url ' & ( serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#' ) & ( len( CFEngineName ) ? ' --cfengine-name "#CFEngineName#"' : '' ) - & ' --server-name "#serverInfo.name#" #errorPages# --welcome-files "index.cfm,index.cfml,default.cfm,index.html,index.htm,default.html,default.htm"' + & ' --server-name "#serverInfo.name#" #errorPages#' // --welcome-files "index.cfm,index.cfml,default.cfm,index.html,index.htm,default.html,default.htm" & ' --tray-icon "#serverInfo.trayIcon#" --tray-config "#trayOptionsPath#" --servlet-rest-mappings "/rest/*,/api/*"' & ' --directoryindex "#serverInfo.directoryBrowsing#" --cfml-web-config "#serverInfo.webConfigDir#"' & ( len( CLIAliases ) ? ' --dirs "#CLIAliases#"' : '' ) From 9d1b804bfd42819d3224ef971f58a782453be131 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 30 Nov 2016 23:18:16 -0600 Subject: [PATCH 30/87] COMMANDBOX-520 --- .../server-commands/commands/server/list.cfc | 26 ++++++++++++++----- .../commands/server/status.cfc | 8 +++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/list.cfc b/src/cfml/system/modules_app/server-commands/commands/server/list.cfc index a17b38c34..06321d322 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/list.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/list.cfc @@ -86,12 +86,19 @@ component { .bold( status, statusColors.keyExists( status ) ? statusColors[ status ] : 'yellow' ) .bold( ')' ) .line(); - + if( arguments.verbose ) { - print.indentedLine( "host: " & thisServerInfo.host ) - .indentedLine( "webroot: " & thisServerInfo.webroot ) - .indentedLine( "HTTPEnable: " & thisServerInfo.HTTPEnable ) + print.indentedLine( "host: " & thisServerInfo.host ); + if( len( thisServerInfo.cfengine ) ) { + print.indentedLine( "CF Engine: " & thisServerInfo.cfengine ); + } + if( len( thisServerInfo.WARPath ) ) { + print.indentedLine( "WARPath: " & thisServerInfo.WARPath ); + } else { + print.indentedLine( "webroot: " & thisServerInfo.webroot ); + } + print.indentedLine( "HTTPEnable: " & thisServerInfo.HTTPEnable ) .indentedLine( "port: " & thisServerInfo.port ) .indentedLine( "SSLEnable: " & thisServerInfo.SSLEnable ) .indentedLine( "SSLport: " & thisServerInfo.SSLport ) @@ -101,8 +108,6 @@ component { .indentedLine( "debug: " & thisServerInfo.debug ) .indentedLine( "ID: " & thisServerInfo.id ); - if( len( thisServerInfo.cfengine ) ) { print.indentedLine( "cfengine: " & thisServerInfo.cfengine ); } - if( len( thisServerInfo.WARPath ) ) { print.indentedLine( "WARPath: " & thisServerInfo.WARPath ); } if( len( thisServerInfo.libDirs ) ) { print.indentedLine( "libDirs: " & thisServerInfo.libDirs ); } if( len( thisServerInfo.webConfigDir ) ) { print.indentedLine( "webConfigDir: " & thisServerInfo.webConfigDir ); } if( len( thisServerInfo.serverConfigDir ) ) { print.indentedLine( "serverConfigDir: " & thisServerInfo.serverConfigDir ); } @@ -118,7 +123,14 @@ component { if( thisServerInfo.SSLEnable ) { print.indentedLine( 'https://' & thisServerInfo.host & ':' & thisServerInfo.SSLport ); } - print.indentedLine( thisServerInfo.webroot ); + if( len( thisServerInfo.cfengine ) ) { + print.indentedLine( 'CF Engine: ' & thisServerInfo.cfengine ); + } + if( len( thisServerInfo.warPath ) ) { + print.indentedLine( 'WAR Path: ' & thisServerInfo.warPath ); + } else { + print.indentedLine( 'Webroot: ' & thisServerInfo.webroot ); + } }// end verbose } // End "filter" if diff --git a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc index a03836c2d..3c54fd866 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc @@ -106,8 +106,14 @@ component aliases='status,server info' { .bold( ')' ); print.indentedLine( thisServerInfo.host & ':' & thisServerInfo.port & ' --> ' & thisServerInfo.webroot ); - + if( len( serverInfo.cfengine ) ) { + print.indentedLine( 'CF Engine: ' & serverInfo.cfengine ); + } + if( len( serverInfo.warPath ) ) { + print.indentedLine( 'WAR Path: ' & serverInfo.warPath ); + } print.line(); + print.indentedLine( 'Last status message: ' ); print.indentedLine( thisServerInfo.statusInfo.result ); if( arguments.verbose ) { From 545745b70ab47c47458232fd8662394ab3b8597f Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 30 Nov 2016 23:28:44 -0600 Subject: [PATCH 31/87] COMMANDBOX-521 --- src/cfml/system/services/ServerService.cfc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 450c7e05c..7442514cb 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -482,7 +482,13 @@ component accessors="true" singleton { // If external Lucee server, set the java agent if( !installDetails.internal && serverInfo.cfengine contains "lucee" ) { - javaagent = "-javaagent:#installDetails.installDir#/WEB-INF/lib/lucee-inst.jar"; + // Detect Lucee 4.x + if( installDetails.version.listFirst( '.' ) < 5 ) { + javaagent = "-javaagent:#installDetails.installDir#/WEB-INF/lib/lucee-inst.jar"; + } else { + // Lucee 5+ doesn't need the Java agent + javaagent = ""; + } } // If external Railo server, set the java agent if( !installDetails.internal && serverInfo.cfengine contains "railo" ) { @@ -607,7 +613,9 @@ component accessors="true" singleton { args &= " -war ""#serverInfo.WARPath#"""; // Stand alone server } else if( !installDetails.internal ){ - args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#installDetails.installDir#/WEB-INF/lib"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; + // Add the server WAR's lib folder in + serverInfo.libDirs = serverInfo.libDirs.listAppend( installDetails.installDir& '/WEB-INF/lib' ); + args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#serverInfo.libDirs.listChangeDelims( ',', ',' )#"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; // internal server } else { // The internal server borrows the CommandBox lib directory From c67ca0111d06d8fc309666769a2e2a4b82a830ab Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 30 Nov 2016 23:49:09 -0600 Subject: [PATCH 32/87] COMMANDBOX-522 --- src/cfml/system/services/PackageService.cfc | 14 ++++++++------ src/cfml/system/services/ServerEngineService.cfc | 4 +++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index c229b980d..fee46092d 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -61,8 +61,10 @@ component accessors="true" singleton { * @verbose If set, it will produce much more verbose information about the package installation * @force When set to true, it will force dependencies to be installed whether they already exist or not * @packagePathRequestingInstallation If installing smart dependencies packages (like ColdBox modules) that are capable of being nested, this is our current level + * + * @returns True if no errors encountered, false if things went boom. **/ - function installPackage( + boolean function installPackage( required string ID, string directory, boolean save=false, @@ -91,7 +93,7 @@ component accessors="true" singleton { var endpointData = endpointService.resolveEndpoint( arguments.ID, arguments.currentWorkingDirectory ); } catch( EndpointNotFound var e ) { consoleLogger.error( e.message ); - return; + return false; } consoleLogger.info( '.'); @@ -104,7 +106,7 @@ component accessors="true" singleton { // but I don't want to "blow up" the console with a full error. } catch( endpointException var e ) { consoleLogger.error( e.message & ' ' & e.detail ); - return; + return false; } // Support box.json in the root OR in a subfolder (NPM-style!) @@ -190,7 +192,7 @@ component accessors="true" singleton { // Does the package that we found satisfy what we need? if( semanticVersion.satisfies( candidateBoxJSON.version, version ) ) { consoleLogger.warn( '#packageName# (#version#) is already satisfied by #candidateInstallPath# (#candidateBoxJSON.version#). Skipping installation.' ); - return; + return true; } } } @@ -354,7 +356,7 @@ component accessors="true" singleton { directoryDelete( tmpPath, true ); } consoleLogger.warn( "The package #packageName# is already installed at #installDirectory#. Skipping installation. Use --force option to force install." ); - return; + return true; } } @@ -488,7 +490,7 @@ component accessors="true" singleton { } interceptorService.announceInterception( 'postInstall', { installArgs=arguments } ); - + return true; } // DRY diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index f8d930133..e14e0a0bb 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -189,7 +189,9 @@ component accessors="true" singleton="true" { // Install the engine via our standard package service installDetails.initialInstall = true; - packageService.installPackage( ID=arguments.ID, directory=thisTempDir, save=false ); + if( !packageService.installPackage( ID=arguments.ID, directory=thisTempDir, save=false ) ) { + throw( message='Server not installed.', type="commandException"); + } // Look for a war or zip archive inside the package var theArchive = ''; From 231081891b003b2e7dbd86d2c4c6891733858bb8 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 1 Dec 2016 01:16:03 -0600 Subject: [PATCH 33/87] COMMANDBOX-518 --- .../server-commands/commands/server/start.cfc | 4 +- .../system/services/ServerEngineService.cfc | 2 +- src/cfml/system/services/ServerService.cfc | 59 +++++++++++++++---- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc index 1d6180098..93021d768 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc @@ -54,10 +54,10 @@ component aliases="start" { * @directory web root for this server * @stopPort stop socket listener port number * @force force start if status is not stopped - * @debug sets debug log level + * @debug Turns on debug output while starting and streams server output to console. * @webConfigDir custom location for web context configuration * @serverConfigDir custom location for server configuration - * @libDirs comma-separated list of extra lib directories for the server + * @libDirs comma-separated list of extra lib directories for the server to load * @trayIconFile path to .png file for tray icon * @webXML path to web.xml file used to configure the server * @HTTPEnable enable HTTP diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index e14e0a0bb..dc216bb59 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -175,7 +175,7 @@ component accessors="true" singleton="true" { } - // If we're starting a Lucee server whose version matches the CLI engine, then don't download anyting, we're using internal jars. + // If we're starting a Lucee server whose version matches the CLI engine, then don't download anything, we're using internal jars. if( listFirst( arguments.ID, '@' ) == 'lucee' && server.lucee.version == replace( installDetails.version, '+', '.', 'all' ) ) { installDetails.internal = true; return installDetails; diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 7442514cb..c1b68e8fc 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -192,6 +192,13 @@ component accessors="true" singleton { if( !isNull( serverProps.rewritesConfig ) ) { serverProps.rewritesConfig = fileSystemUtil.resolvePath( serverProps.rewritesConfig ); } + if( !isNull( serverProps.libDirs ) ) { + // Comma-delimited list needs each item resolved + serverProps.libDirs = serverProps.libDirs + .map( function( thisLibDir ){ + return fileSystemUtil.resolvePath( thisLibDir ); + } ); + } // Look up the server that we're starting var serverDetails = resolveServerDetails( arguments.serverProps ); @@ -252,6 +259,7 @@ component accessors="true" singleton { if( isNull( serverProps[ prop ] ) || listFindNoCase( 'saveSettings,serverConfigFile,debug,force', prop ) ) { continue; } + var configPath = replace( fileSystemUtil.resolvePath( defaultServerConfigFileDirectory ), '\', '/', 'all' ) & '/'; // Only need switch cases for properties that are nested or use different name switch(prop) { case "port": @@ -261,9 +269,8 @@ component accessors="true" singleton { serverJSON[ 'web' ][ 'host' ] = serverProps[ prop ]; break; case "directory": - // Both of these are canonical already. + // This path is canonical already. var thisDirectory = replace( serverProps[ 'directory' ], '\', '/', 'all' ) & '/'; - var configPath = replace( fileSystemUtil.resolvePath( defaultServerConfigFileDirectory ), '\', '/', 'all' ) & '/'; // If the web root is south of the server's JSON, make it relative for better portability. if( thisDirectory contains configPath ) { thisDirectory = replaceNoCase( thisDirectory, configPath, '' ); @@ -271,9 +278,8 @@ component accessors="true" singleton { serverJSON[ 'web' ][ 'webroot' ] = thisDirectory; break; case "trayIcon": - // Both of these are canonical already. + // This path is canonical already. var thisFile = replace( serverProps[ 'trayIcon' ], '\', '/', 'all' ); - var configPath = replace( fileSystemUtil.resolvePath( defaultServerConfigFileDirectory ), '\', '/', 'all' ) & '/'; // If the trayIcon is south of the server's JSON, make it relative for better portability. if( thisFile contains configPath ) { thisFile = replaceNoCase( thisFile, configPath, '' ); @@ -290,7 +296,18 @@ component accessors="true" singleton { serverJSON[ 'app' ][ 'serverConfigDir' ] = serverProps[ prop ]; break; case "libDirs": - serverJSON[ 'app' ][ 'libDirs' ] = serverProps[ prop ]; + serverJSON[ 'app' ][ 'libDirs' ] = serverProps[ 'libDirs' ] + .listMap( function( thisLibDir ) { + // This path is canonical already. + var thisLibDir = replace( thisLibDir, '\', '/', 'all' ); + // If the libDir is south of the server's JSON, make it relative for better portability. + if( thisLibDir contains configPath ) { + return replaceNoCase( thisLibDir, configPath, '' ); + } else { + return thisLibDir; + } + } ); + break; case "webXML": serverJSON[ 'app' ][ 'webXML' ] = serverProps[ prop ]; @@ -299,9 +316,8 @@ component accessors="true" singleton { serverJSON[ 'app' ][ 'cfengine' ] = serverProps[ prop ]; break; case "WARPath": - // Both of these are canonical already. + // This path is canonical already. var thisFile = replace( serverProps[ 'WARPath' ], '\', '/', 'all' ); - var configPath = replace( fileSystemUtil.resolvePath( defaultServerConfigFileDirectory ), '\', '/', 'all' ) & '/'; // If the trayIcon is south of the server's JSON, make it relative for better portability. if( thisFile contains configPath ) { thisFile = replaceNoCase( thisFile, configPath, '' ); @@ -330,9 +346,8 @@ component accessors="true" singleton { serverJSON[ 'web' ][ 'rewrites' ][ 'enable' ] = serverProps[ prop ]; break; case "rewritesConfig": - // Both of these are canonical already. + // This path is canonical already. var thisFile = replace( serverProps[ 'rewritesConfig' ], '\', '/', 'all' ); - var configPath = replace( fileSystemUtil.resolvePath( defaultServerConfigFileDirectory ), '\', '/', 'all' ) & '/'; // If the trayIcon is south of the server's JSON, make it relative for better portability. if( thisFile contains configPath ) { thisFile = replaceNoCase( thisFile, configPath, '' ); @@ -380,10 +395,13 @@ component accessors="true" singleton { serverInfo.webConfigDir = serverProps.webConfigDir ?: serverJSON.app.webConfigDir ?: defaults.app.webConfigDir; if( !len( serverInfo.webConfigDir ) ) { serverInfo.webConfigDir = getCustomServerFolder( serverInfo ); } serverInfo.serverConfigDir = serverProps.serverConfigDir ?: serverJSON.app.serverConfigDir ?: defaults.app.serverConfigDir; - serverInfo.libDirs = serverProps.libDirs ?: serverJSON.app.libDirs ?: defaults.app.libDirs; + + // relative trayIcon in server.json is resolved relative to the server.json if( serverJSON.keyExists( 'trayIcon' ) ) { serverJSON.trayIcon = fileSystemUtil.resolvePath( serverJSON.trayIcon, defaultServerConfigFileDirectory ); } + // relative trayIcon in config setting server defaults is resolved relative to the web root if( defaults.keyExists( 'trayIcon' ) && len( defaults.trayIcon ) ) { defaults.trayIcon = fileSystemUtil.resolvePath( defaults.trayIcon, defaultwebroot ); } serverInfo.trayIcon = serverProps.trayIcon ?: serverJSON.trayIcon ?: defaults.trayIcon; + serverInfo.webXML = serverProps.webXML ?: serverJSON.app.webXML ?: defaults.app.webXML; serverInfo.SSLEnable = serverProps.SSLEnable ?: serverJSON.web.SSL.enable ?: defaults.web.SSL.enable; serverInfo.HTTPEnable = serverProps.HTTPEnable ?: serverJSON.web.HTTP.enable ?: defaults.web.HTTP.enable; @@ -392,9 +410,14 @@ component accessors="true" singleton { serverInfo.SSLKey = serverProps.SSLKey ?: serverJSON.web.SSL.key ?: defaults.web.SSL.key; serverInfo.SSLKeyPass = serverProps.SSLKeyPass ?: serverJSON.web.SSL.keyPass ?: defaults.web.SSL.keyPass; serverInfo.rewritesEnable = serverProps.rewritesEnable ?: serverJSON.web.rewrites.enable ?: defaults.web.rewrites.enable; + + + // relative rewrite config path in server.json is resolved relative to the server.json if( isDefined( 'serverJSON.web.rewrites.config' ) ) { serverJSON.web.rewrites.config = fileSystemUtil.resolvePath( serverJSON.web.rewrites.config, defaultServerConfigFileDirectory ); } + // relative rewrite config path in config setting server defaults is resolved relative to the web root if( isDefined( 'defaults.web.rewrites.config' ) ) { defaults.web.rewrites.config = fileSystemUtil.resolvePath( defaults.web.rewrites.config, defaultwebroot ); } serverInfo.rewritesConfig = serverProps.rewritesConfig ?: serverJSON.web.rewrites.config ?: defaults.web.rewrites.config; + serverInfo.heapSize = serverProps.heapSize ?: serverJSON.JVM.heapSize ?: defaults.JVM.heapSize; serverInfo.directoryBrowsing = serverProps.directoryBrowsing ?: serverJSON.web.directoryBrowsing ?: defaults.web.directoryBrowsing; @@ -423,6 +446,18 @@ component accessors="true" singleton { // Server startup timeout serverInfo.startTimeout = serverProps.startTimeout ?: serverJSON.startTimeout ?: defaults.startTimeout; + // relative lib dirs in server.json are resolved relative to the server.json + if( serverJSON.keyExists( 'app' ) && serverJSON.app.keyExists( 'libDirs' ) ) { + serverJSON.app.libDirs = serverJSON.app.libDirs.listMap( function( thisLibDir ){ + return fileSystemUtil.resolvePath( thisLibDir, defaultServerConfigFileDirectory ); + }); + } + // relative lib dirs in config setting server defaults are resolved relative to the web root + if( defaults.keyExists( 'app' ) && defaults.app.keyExists( 'libDirs' ) && len( defaults.app.libDirs ) ) { + defaults.app.libDirs = defaults.app.libDirs.listMap( function( thisLibDir ){ + return fileSystemUtil.resolvePath( thisLibDir, defaultwebroot ); + }); + } // Global defauls are always added on top of whatever is specified by the user or server.json serverInfo.libDirs = ( serverProps.libDirs ?: serverJSON.app.libDirs ?: '' ).listAppend( defaults.app.libDirs ); @@ -615,12 +650,14 @@ component accessors="true" singleton { } else if( !installDetails.internal ){ // Add the server WAR's lib folder in serverInfo.libDirs = serverInfo.libDirs.listAppend( installDetails.installDir& '/WEB-INF/lib' ); + // Have to get rid of empty list elements args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#serverInfo.libDirs.listChangeDelims( ',', ',' )#"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; // internal server } else { // The internal server borrows the CommandBox lib directory serverInfo.libDirs = serverInfo.libDirs.listAppend( variables.libDir ); - args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#serverInfo.libDirs#"""; + // Have to get rid of empty list elements + args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#serverInfo.libDirs.listChangeDelims( ',', ',' )#"""; } // Incorporate SSL to command if( serverInfo.SSLEnable ){ From c07142aea9a1e8bab8039a027981f78c8903af11 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 1 Dec 2016 09:22:12 -0600 Subject: [PATCH 34/87] COMMANDBOX-523 --- src/cfml/system/services/InterceptorService.cfc | 2 +- src/cfml/system/services/ServerService.cfc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/services/InterceptorService.cfc b/src/cfml/system/services/InterceptorService.cfc index 69d811999..ea83b8a27 100644 --- a/src/cfml/system/services/InterceptorService.cfc +++ b/src/cfml/system/services/InterceptorService.cfc @@ -30,7 +30,7 @@ component accessors=true singleton { // Module lifecycle 'preModuleLoad','postModuleLoad','preModuleUnLoad','postModuleUnload', // Server lifecycle - 'onServerStart','onServerInstall','onServerStop', + 'preServerStart','onServerStart','onServerInstall','onServerStop', // Error handling 'onException', // Package lifecycle diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index c1b68e8fc..25363ea9c 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -202,7 +202,9 @@ component accessors="true" singleton { // Look up the server that we're starting var serverDetails = resolveServerDetails( arguments.serverProps ); - + + interceptorService.announceInterception( 'preServerStart', { serverDetails=serverDetails, serverProps=serverProps } ); + var defaultName = serverDetails.defaultName; var defaultwebroot = serverDetails.defaultwebroot; var defaultServerConfigFile = serverDetails.defaultServerConfigFile; @@ -648,8 +650,6 @@ component accessors="true" singleton { args &= " -war ""#serverInfo.WARPath#"""; // Stand alone server } else if( !installDetails.internal ){ - // Add the server WAR's lib folder in - serverInfo.libDirs = serverInfo.libDirs.listAppend( installDetails.installDir& '/WEB-INF/lib' ); // Have to get rid of empty list elements args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#serverInfo.libDirs.listChangeDelims( ',', ',' )#"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; // internal server From 0ad53040a3781225f1aa255d8aaea81c19fa0b42 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 1 Dec 2016 13:15:10 -0600 Subject: [PATCH 35/87] COMMANDBOX-518 --- src/cfml/system/services/ServerService.cfc | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 25363ea9c..1ac71d57d 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -456,9 +456,18 @@ component accessors="true" singleton { } // relative lib dirs in config setting server defaults are resolved relative to the web root if( defaults.keyExists( 'app' ) && defaults.app.keyExists( 'libDirs' ) && len( defaults.app.libDirs ) ) { - defaults.app.libDirs = defaults.app.libDirs.listMap( function( thisLibDir ){ - return fileSystemUtil.resolvePath( thisLibDir, defaultwebroot ); - }); + // For each lib dir in the list, resolve the path, but only keep it if the folder actually exists. + // This allows for "optional" global lib dirs. + // listReduce starts with an initial value of "" and aggregates the new list, onluy appending the items it wants to keep + defaults.app.libDirs = defaults.app.libDirs.listReduce( function( thisLibDirs, thisLibDir ){ + thisLibDir = fileSystemUtil.resolvePath( thisLibDir, defaultwebroot ); + if( directoryExists( thisLibDir ) ) { + thisLibDirs.listAppend( thisLibDir ); + } else if( serverInfo.debug ) { + consoleLogger.info( "Ignoring non-existant global lib dir: " & thisLibDir ); + } + return thisLibDirs; + }, '' ); } // Global defauls are always added on top of whatever is specified by the user or server.json serverInfo.libDirs = ( serverProps.libDirs ?: serverJSON.app.libDirs ?: '' ).listAppend( defaults.app.libDirs ); @@ -687,7 +696,7 @@ component accessors="true" singleton { setServerInfo( serverInfo ); if( serverInfo.debug ) { - var cleanedArgs = cr & ' ' & trim( replaceNoCase( args, ' -', cr & ' -', 'all' ) ); + var cleanedArgs = cr & ' ' & trim( replaceNoCase( args, ' -', cr & ' -', 'all' ) ); consoleLogger.debug("Server start command: #javaCommand# #cleanedArgs#"); } From 0d9914c1a94f9ad5423a35ed680589f17bd25394 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 1 Dec 2016 23:14:18 -0600 Subject: [PATCH 36/87] COMMANDBOX-473 --- .../server-commands/commands/server/start.cfc | 4 +++- src/cfml/system/services/ServerService.cfc | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc index 93021d768..a74f75cb1 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc @@ -79,6 +79,7 @@ component aliases="start" { * @serverConfigFile The path to the server's JSON file. Created if it doesn't exist. * @startTimeout The amount of time in seconds to wait for the server to start (in the background). * @console Start this server in the forground console process and wait until Ctrl-C is pressed to stop it. + * @welcomeFiles A comma-delimited list of default files to load when visiting a directory (index.cfm,index.htm,etc) **/ function run( @@ -112,7 +113,8 @@ component aliases="start" { String WARPath, String serverConfigFile, Numeric startTimeout, - Boolean console + Boolean console, + String welcomeFiles ){ // This is a common mis spelling diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 1ac71d57d..463014afa 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -135,6 +135,7 @@ component accessors="true" singleton { aliases : duplicate( d.web.aliases ?: {} ), // Duplicate so onServerStart interceptors don't actually change config settings via refernce. errorPages : duplicate( d.web.errorPages ?: {} ), + welcomeFiles : d.web.welcomeFiles ?: '', http : { port : d.web.http.port ?: 0, enable : d.web.http.enable ?: true @@ -258,7 +259,7 @@ component accessors="true" singleton { // Save hand-entered properties in our server.json for next time for( var prop in serverProps ) { // Ignore null props or ones that shouldn't be saved - if( isNull( serverProps[ prop ] ) || listFindNoCase( 'saveSettings,serverConfigFile,debug,force', prop ) ) { + if( isNull( serverProps[ prop ] ) || listFindNoCase( 'saveSettings,serverConfigFile,debug,force,console', prop ) ) { continue; } var configPath = replace( fileSystemUtil.resolvePath( defaultServerConfigFileDirectory ), '\', '/', 'all' ) & '/'; @@ -344,6 +345,9 @@ component accessors="true" singleton { case "SSLKeyPass": serverJSON[ 'web' ][ 'SSL' ][ 'keyPass' ] = serverProps[ prop ]; break; + case "welcomeFiles": + serverJSON[ 'web' ][ 'welcomeFiles' ] = serverProps[ prop ]; + break; case "rewritesEnable": serverJSON[ 'web' ][ 'rewrites' ][ 'enable' ] = serverProps[ prop ]; break; @@ -412,6 +416,9 @@ component accessors="true" singleton { serverInfo.SSLKey = serverProps.SSLKey ?: serverJSON.web.SSL.key ?: defaults.web.SSL.key; serverInfo.SSLKeyPass = serverProps.SSLKeyPass ?: serverJSON.web.SSL.keyPass ?: defaults.web.SSL.keyPass; serverInfo.rewritesEnable = serverProps.rewritesEnable ?: serverJSON.web.rewrites.enable ?: defaults.web.rewrites.enable; + serverInfo.welcomeFiles = serverProps.welcomeFiles ?: serverJSON.web.welcomeFiles ?: defaults.web.welcomeFiles; + // Clean up spaces in welcome file list + serverInfo.welcomeFiles = serverInfo.welcomeFiles.listMap( function( i ){ return trim( i ); } ); // relative rewrite config path in server.json is resolved relative to the server.json @@ -648,7 +655,8 @@ component accessors="true" singleton { & ' --open-browser #serverInfo.openbrowser#' & ' --open-url ' & ( serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#' ) & ( len( CFEngineName ) ? ' --cfengine-name "#CFEngineName#"' : '' ) - & ' --server-name "#serverInfo.name#" #errorPages#' // --welcome-files "index.cfm,index.cfml,default.cfm,index.html,index.htm,default.html,default.htm" + & ' --server-name "#serverInfo.name#" #errorPages#' + & ( len( serverInfo.welcomeFiles ) ? ' --welcome-files "#serverInfo.welcomeFiles#" ' : '' ) & ' --tray-icon "#serverInfo.trayIcon#" --tray-config "#trayOptionsPath#" --servlet-rest-mappings "/rest/*,/api/*"' & ' --directoryindex "#serverInfo.directoryBrowsing#" --cfml-web-config "#serverInfo.webConfigDir#"' & ( len( CLIAliases ) ? ' --dirs "#CLIAliases#"' : '' ) From f532af3f264b1563daf3b2d720eda0e17709843e Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 2 Dec 2016 10:53:49 -0700 Subject: [PATCH 37/87] Stop running servers when the force flag is passed. (#115) * Typo fix in server forget command * Stop running servers with the force flag * Don't try to stop stopped servers --- .../commands/server/forget.cfc | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/forget.cfc b/src/cfml/system/modules_app/server-commands/commands/server/forget.cfc index 2a4891e49..b54e19711 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/forget.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/forget.cfc @@ -38,16 +38,24 @@ component { } var serverInfo = serverService.resolveServerDetails( arguments ).serverinfo; - if( arguments.all ) { - var servers = serverService.getServers(); - servers.each( function( ID ){ runningServerCheck( servers[ arguments.ID ] ); } ); + var servers = arguments.all ? serverService.getServers() : { "#serverInfo.id#": serverInfo }; + if( arguments.force ) { + var runningServers = getRunningServers( servers ); + if ( ! runningServers.isEmpty() ) { + var stopMessage = arguments.all ? + "Stopping all running servers (#getServerNames( runningServers ).toList()#) first...." : + "Stopping server #serverInfo.name# first...."; + print.line( stopMessage ); + runningServers.each( function( ID ){ serverService.stop( runningServers[ arguments.ID ] ); } ); + } } else { - runningServerCheck( serverInfo ); + servers.each( function( ID ){ runningServerCheck( servers[ arguments.ID ] ); } ); } // Confirm deletion - var askMessage = arguments.all ? "Really forget & delete all servers (#arrayToList( serverService.getServerNames() )#) forever [y/n]?" : - "Really forget & delete server '#serverinfo.name#' forever [y/n]?"; + var askMessage = arguments.all ? + "Really forget & delete all servers (#arrayToList( serverService.getServerNames() )#) forever [y/n]?" : + "Really forget & delete server '#serverinfo.name#' forever [y/n]?"; if( arguments.force || confirm( askMessage ) ){ print.line( serverService.forget( serverInfo, arguments.all ) ); @@ -60,10 +68,22 @@ component { private function runningServerCheck( required struct serverInfo ) { if( serverService.isServerRunning( serverInfo ) ) { print.redBoldLine( 'Server "#serverInfo.name#" (#serverInfo.webroot#) appears to still be running!' ) - .yellowLine( 'Forgetting it now may leave the server in a currupt state. Please stop it first.' ) + .yellowLine( 'Forgetting it now may leave the server in a corrupt state. Please stop it first.' ) .line(); } } + + private function getRunningServers( required struct servers ) { + return servers.filter( function( ID ){ + return serverService.isServerRunning( servers[ arguments.ID ] ); + } ) + } + + private function getServerNames( required struct servers ){ + return servers.keyArray().map( function( ID ){ + return servers[ arguments.ID ].name; + } ); + } function serverNameComplete() { return serverService.getServerNames(); From 149a89e8fae8169d666bdf8d9c14b195ee7b2156 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 2 Dec 2016 17:21:25 -0600 Subject: [PATCH 38/87] Update for libdir arg --- src/cfml/system/services/ServerService.cfc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 463014afa..317242413 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -667,15 +667,19 @@ component accessors="true" singleton { args &= " -war ""#serverInfo.WARPath#"""; // Stand alone server } else if( !installDetails.internal ){ - // Have to get rid of empty list elements - args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#serverInfo.libDirs.listChangeDelims( ',', ',' )#"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; + args &= " -war ""#serverInfo.webroot#"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; // internal server } else { // The internal server borrows the CommandBox lib directory serverInfo.libDirs = serverInfo.libDirs.listAppend( variables.libDir ); + args &= " -war ""#serverInfo.webroot#"""; + } + + if( len( serverInfo.libDirs ) ) { // Have to get rid of empty list elements - args &= " -war ""#serverInfo.webroot#"" --lib-dirs ""#serverInfo.libDirs.listChangeDelims( ',', ',' )#"""; + args &= " --lib-dirs ""#serverInfo.libDirs.listChangeDelims( ',', ',' )#"""; } + // Incorporate SSL to command if( serverInfo.SSLEnable ){ args &= " --http-enable #serverInfo.HTTPEnable# --ssl-enable #serverInfo.SSLEnable# --ssl-port #serverInfo.SSLPort#"; From 74c330447fce1447ec829702d4206b64414d75cd Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 3 Dec 2016 00:05:51 -0600 Subject: [PATCH 39/87] Temproary until Runwar pull is merged --- src/cfml/system/services/ServerService.cfc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 317242413..dfa855bfd 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -667,6 +667,8 @@ component accessors="true" singleton { args &= " -war ""#serverInfo.WARPath#"""; // Stand alone server } else if( !installDetails.internal ){ + // Add the server WAR's lib folder in + serverInfo.libDirs = serverInfo.libDirs.listAppend( installDetails.installDir & '/WEB-INF/lib' ); args &= " -war ""#serverInfo.webroot#"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; // internal server } else { From 2abe6089a4d479ef903b28fad3b5fdc65a266776 Mon Sep 17 00:00:00 2001 From: benknox Date: Mon, 5 Dec 2016 08:51:19 -0600 Subject: [PATCH 40/87] Update reinit.cfc (#116) Use serverInfo.host so that 'coldbox reinit' will work on servers that were started with an alternative host defined. --- .../modules_app/coldbox-commands/commands/coldbox/reinit.cfc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/reinit.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/reinit.cfc index b1f2100ab..1f9ad00b8 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/reinit.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/reinit.cfc @@ -25,7 +25,7 @@ component aliases="fwreinit" { if( structCount( serverInfo ) eq 0 ){ print.boldRedLine( "No server configurations found for '#getCWD()#', so have no clue what to reinit buddy!" ); } else { - var thisURL = "localhost:#serverInfo.port#/?fwreinit=#arguments.password#"; + var thisURL = "#serverInfo.host#:#serverInfo.port#/?fwreinit=#arguments.password#"; print.greenLine( "Hitting...#thisURL#" ); http result="local.results" url="#thisURL#"; @@ -41,4 +41,4 @@ component aliases="fwreinit" { } } -} \ No newline at end of file +} From 84e2aa7494aa75cdd6155327c060ad7f9243a0b7 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 5 Dec 2016 09:26:03 -0600 Subject: [PATCH 41/87] Don't need this cmpat shim here any longer. Runwar does this. --- src/cfml/system/services/ServerService.cfc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index dfa855bfd..317242413 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -667,8 +667,6 @@ component accessors="true" singleton { args &= " -war ""#serverInfo.WARPath#"""; // Stand alone server } else if( !installDetails.internal ){ - // Add the server WAR's lib folder in - serverInfo.libDirs = serverInfo.libDirs.listAppend( installDetails.installDir & '/WEB-INF/lib' ); args &= " -war ""#serverInfo.webroot#"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; // internal server } else { From 84137ce9125268bd24e939ea908bd337a4690543 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 5 Dec 2016 15:08:28 -0600 Subject: [PATCH 42/87] Bump loader version to pick up new jars. --- build/build.properties | 2 +- src/cfml/system/modules/semver/models/SemanticVersion.cfc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.properties b/build/build.properties index 1136b55d8..c29e45ec2 100644 --- a/build/build.properties +++ b/build/build.properties @@ -12,7 +12,7 @@ java.pack200=false #dependencies dependencies.dir=${basedir}/lib cfml.version=4.5.4.017 -cfml.loader.version=1.4.2 +cfml.loader.version=1.4.3 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} jre.version=1.8.0_102 diff --git a/src/cfml/system/modules/semver/models/SemanticVersion.cfc b/src/cfml/system/modules/semver/models/SemanticVersion.cfc index 0e03075c4..aaf6778d0 100644 --- a/src/cfml/system/modules/semver/models/SemanticVersion.cfc +++ b/src/cfml/system/modules/semver/models/SemanticVersion.cfc @@ -482,7 +482,7 @@ component singleton{ sComparator.sVersion.minor=val( sComparator.sVersion.minor )+1; sComparator.sVersion.revision = 0; } else { - sComparator.sVersion.revision=val( sComparator.sVersion.revision )+1; + sComparator.sVersion.revision=val( sComparator.sVersion.revision )+1; } sComparator.version = getVersionAsString( sComparator.sVersion ); From 2ca7a4f32d102643903af235f28e50eaf4fb5fc5 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 5 Dec 2016 23:06:07 -0600 Subject: [PATCH 43/87] COMMANDBOX-524 --- src/cfml/system/services/ServerEngineService.cfc | 8 +++++--- src/cfml/system/services/ServerService.cfc | 3 ++- src/cfml/system/util/SemanticVersion.cfc | 15 +++++++++++---- tests/cfml/system/util/TestSemanticVersion.cfc | 2 ++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index dc216bb59..a599f4fe8 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -137,13 +137,15 @@ component accessors="true" singleton="true" { var version = endpoint.parseVersion( arguments.ID ); // If the user gave us an exact version, just use it! - if( semanticVersion.isExactVersion( version ) ) { - var satisfyingVersion = version; + // Require buildID like 5.1.0+34 + if( semanticVersion.isExactVersion( version=version, includeBuildID=true ) ) { + var satisfyingVersion = version; } else { - consoleLogger.info( "Contacting ForgeBox to determine the best version match for [#version#]. Use an exact 'cfengine' version to skip this check."); + consoleLogger.warn( "Contacting ForgeBox to determine the latest & greatest version of [#engineName##( len( version ) ? ' ' : '' )##version#]. Use an exact 'cfengine' version to skip this check."); // If ForgeBox is down, don't rain on people's parade. try { var satisfyingVersion = endpoint.findSatisfyingVersion( endpoint.parseSlug( arguments.ID ), version ).version; + consoleLogger.info( "OK, [#engineName# #satisfyingVersion#] it is!"); } catch( any var e ) { consoleLogger.error( "."); diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 317242413..228eb61c0 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -497,7 +497,8 @@ component accessors="true" singleton { } if( !len( serverInfo.WARPath ) && !len( serverInfo.cfengine ) ) { - serverInfo.cfengine = 'lucee@' & server.lucee.version; + // Turn 1.2.3.4 into 1.2.3+4 + serverInfo.cfengine = 'lucee@' & reReplace( server.lucee.version, '([0-9]*.[0-9]*.[0-9]*)(.)([0-9]*)', '\1+\3' ); } if( serverInfo.cfengine.endsWith( '@' ) ) { diff --git a/src/cfml/system/util/SemanticVersion.cfc b/src/cfml/system/util/SemanticVersion.cfc index 0e03075c4..28e624dd0 100644 --- a/src/cfml/system/util/SemanticVersion.cfc +++ b/src/cfml/system/util/SemanticVersion.cfc @@ -578,9 +578,7 @@ component singleton{ * True if a specific version, false if a range that could match multiple versions * version.hint A string that contains a version or a range */ - boolean function isExactVersion( required string version ) { - // Default any missing pieces to "x" so "3" becomes "3.x.x". - arguments.version = getVersionAsString (parseVersion( clean( arguments.version ), 'x' ) ); + boolean function isExactVersion( required string version, boolean includeBuildID=false ) { if( version contains '*' ) return false; if( version contains 'x.' ) return false; @@ -593,7 +591,16 @@ component singleton{ if( version contains '~' ) return false; if( version contains '^' ) return false; if( version contains ' || ' ) return false; - return len( trim( version ) ) > 0; + + // Default any missing pieces to "x" so "3" becomes "3.x.x". + arguments.version = parseVersion( clean( arguments.version ), 'x' ); + + if( version.major == 'x' ) { return false; } + if( version.minor == 'x' ) { return false; } + if( version.revision == 'x' ) { return false; } + if( includeBuildID && val( version.buildID ) == 0 ) { return false; } + + return true; } } \ No newline at end of file diff --git a/tests/cfml/system/util/TestSemanticVersion.cfc b/tests/cfml/system/util/TestSemanticVersion.cfc index f7f5fadbf..5591fc34d 100644 --- a/tests/cfml/system/util/TestSemanticVersion.cfc +++ b/tests/cfml/system/util/TestSemanticVersion.cfc @@ -54,6 +54,8 @@ component name="TestPrint" extends="mxunit.framework.TestCase" { assertFalse( semver.isExactVersion( '>1.0.0-alpha' ) ); assertFalse( semver.isExactVersion( '>=1.0.0-rc.0 <1.0.1' ) ); assertFalse( semver.isExactVersion( '^2 <2.2 || > 2.3' ) ); + assertTrue( semver.isExactVersion( '1.2.3+123', true ) ); + assertFalse( semver.isExactVersion( '1.2.3', true ) ); } public void function testIsNew() { From 3b742e49c8c070a04a821a5e7554cf09135e5400 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 8 Dec 2016 11:59:57 -0600 Subject: [PATCH 44/87] Improve code comments --- src/cfml/system/util/SemanticVersion.cfc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cfml/system/util/SemanticVersion.cfc b/src/cfml/system/util/SemanticVersion.cfc index 28e624dd0..27f275c4a 100644 --- a/src/cfml/system/util/SemanticVersion.cfc +++ b/src/cfml/system/util/SemanticVersion.cfc @@ -580,6 +580,7 @@ component singleton{ */ boolean function isExactVersion( required string version, boolean includeBuildID=false ) { + // First test for some sort of range if( version contains '*' ) return false; if( version contains 'x.' ) return false; if( version contains '.x' ) return false; @@ -592,9 +593,11 @@ component singleton{ if( version contains '^' ) return false; if( version contains ' || ' ) return false; + // Ok, looks like it might be a simple version format, so let's fire up the parser. // Default any missing pieces to "x" so "3" becomes "3.x.x". arguments.version = parseVersion( clean( arguments.version ), 'x' ); + // If any of these bits are "x" it means they weren't specified. if( version.major == 'x' ) { return false; } if( version.minor == 'x' ) { return false; } if( version.revision == 'x' ) { return false; } From 7151a9f3bcbcc692c36d9b03df8de0144bd59562 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 8 Dec 2016 12:00:32 -0600 Subject: [PATCH 45/87] COMMANDBOX-528 --- src/cfml/system/services/ServerService.cfc | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 228eb61c0..2966cd036 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -528,11 +528,18 @@ component accessors="true" singleton { // This will install the engine war to start, possibly downloading it first var installDetails = serverEngineService.install( cfengine=serverInfo.cfengine, basedirectory=serverInfo.webConfigDir ); - // This interception point can be used for additional configuration of the engine before it actually starts. - interceptorService.announceInterception( 'onServerInstall', { serverInfo=serverInfo, installDetails=installDetails } ); thisVersion = ' ' & installDetails.version; serverInfo.serverHome = installDetails.installDir; serverInfo.logdir = installDetails.installDir & "/logs"; + + // This is for one-time setup tasks on first install + if( installDetails.initialInstall ) { + // Make current settings available to package scripts + setServerInfo( serverInfo ); + + // This interception point can be used for additional configuration of the engine before it actually starts. + interceptorService.announceInterception( 'onServerInstall', { serverInfo=serverInfo, installDetails=installDetails } ); + } // If external Lucee server, set the java agent if( !installDetails.internal && serverInfo.cfengine contains "lucee" ) { @@ -541,7 +548,7 @@ component accessors="true" singleton { javaagent = "-javaagent:#installDetails.installDir#/WEB-INF/lib/lucee-inst.jar"; } else { // Lucee 5+ doesn't need the Java agent - javaagent = ""; + javaagent = ""; } } // If external Railo server, set the java agent @@ -610,6 +617,8 @@ component accessors="true" singleton { // This is due to a bug in RunWar not creating the right directory for the logs directoryCreate( serverInfo.logDir, true, true ); + // Make current settings available to package scripts + setServerInfo( serverInfo ); interceptorService.announceInterception( 'onServerStart', { serverInfo=serverInfo } ); // Turn struct of aliases into a comma-delimited list, plus resolve relative paths. From ab3df2505b131b5d09aa4d9f3af6e94bbf3f8577 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 8 Dec 2016 12:01:23 -0600 Subject: [PATCH 46/87] COMMANDBOX-529 --- src/cfml/system/config/web.xml | 132 ++++++++++++++++++ .../interceptors/packageScripts.cfc | 2 + .../system/services/ServerEngineService.cfc | 51 +++++-- 3 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 src/cfml/system/config/web.xml diff --git a/src/cfml/system/config/web.xml b/src/cfml/system/config/web.xml new file mode 100644 index 000000000..72eb965a1 --- /dev/null +++ b/src/cfml/system/config/web.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + Lucee Engine + CFMLServlet + lucee.loader.servlet.CFMLServlet + + + + + + + + + + + + + + 1 + + + + CFMLServlet + *.cfc + *.cfm + *.cfml + /index.cfc/* + /index.cfm/* + /index.cfml/* + + + + + + + + + + + + + + Lucee Servlet for RESTful services + RESTServlet + lucee.loader.servlet.RestServlet + 2 + + + + RESTServlet + /rest/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + index.cfm + index.cfml + index.html + index.htm + index.jsp + + + diff --git a/src/cfml/system/modules_app/package-commands/interceptors/packageScripts.cfc b/src/cfml/system/modules_app/package-commands/interceptors/packageScripts.cfc index c0e949277..2c885ed30 100644 --- a/src/cfml/system/modules_app/package-commands/interceptors/packageScripts.cfc +++ b/src/cfml/system/modules_app/package-commands/interceptors/packageScripts.cfc @@ -21,6 +21,8 @@ component { function postModuleLoad() { processScripts( 'postModuleLoad' ); } function preModuleUnLoad() { processScripts( 'preModuleUnLoad' ); } function postModuleUnload() { processScripts( 'postModuleUnload' ); } + function preServerStart() { processScripts( 'preServerStart' ); } + function onServerInstall() { processScripts( 'onServerInstall', interceptData.serverinfo.webroot ); } function onServerStart() { processScripts( 'onServerStart', interceptData.serverinfo.webroot ); } function onServerStop() { processScripts( 'onServerStop', interceptData.serverinfo.webroot ); } function onException() { processScripts( 'onException' ); } diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index a599f4fe8..f508c9d9d 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -59,7 +59,7 @@ component accessors="true" singleton="true" { if (fileExists(installDetails.installDir & "/WEB-INF/cfform/flex-config.xml")) { var flexConfig = fileRead(installDetails.installDir & "/WEB-INF/cfform/flex-config.xml"); - if(!installDetails.internal && installDetails.initialInstall ) { + if( installDetails.initialInstall ) { flexConfig = replace(flexConfig, "/WEB-INF/", installDetails.installDir & "/WEB-INF/","all"); - fileWrite(installDetails.installDir & "/WEB-INF/cfform/flex-config.xml", flexConfig); } else { @@ -141,7 +141,7 @@ component accessors="true" singleton="true" { if( semanticVersion.isExactVersion( version=version, includeBuildID=true ) ) { var satisfyingVersion = version; } else { - consoleLogger.warn( "Contacting ForgeBox to determine the latest & greatest version of [#engineName##( len( version ) ? ' ' : '' )##version#]. Use an exact 'cfengine' version to skip this check."); + consoleLogger.warn( "Contacting ForgeBox to determine the latest & greatest version of [#engineName##( len( version ) ? ' ' : '' )##version#]... Use an exact 'cfengine' version to skip this check."); // If ForgeBox is down, don't rain on people's parade. try { var satisfyingVersion = endpoint.findSatisfyingVersion( endpoint.parseSlug( arguments.ID ), version ).version; @@ -177,20 +177,35 @@ component accessors="true" singleton="true" { } - // If we're starting a Lucee server whose version matches the CLI engine, then don't download anything, we're using internal jars. - if( listFirst( arguments.ID, '@' ) == 'lucee' && server.lucee.version == replace( installDetails.version, '+', '.', 'all' ) ) { - installDetails.internal = true; - return installDetails; - } // Check to see if this WAR has already been exploded if( fileExists( installDetails.installDir & '/WEB-INF/web.xml' ) ) { consoleLogger.info( "WAR/zip archive already installed."); return installDetails; } - + // Install the engine via our standard package service installDetails.initialInstall = true; + + // If we're starting a Lucee server whose version matches the CLI engine, then don't download anything, we're using internal jars. + if( listFirst( arguments.ID, '@' ) == 'lucee' && server.lucee.version == replace( installDetails.version, '+', '.', 'all' ) ) { + // installDetails.internal = true; + + consoleLogger.info( "Building a WAR from local jars."); + + // Spoof a WAR file. + var thisWebinf = installDetails.installDir & '/WEB-INF'; + var thislib = thisWebinf & '/lib'; + var thiServerContext = thisWebinf & '/server-context'; + var thiWebContext = thisWebinf & '/web-context'; + + directoryCreate( installDetails.installDir & '/WEB-INF', true, true ); + directoryCopy( '/commandbox-home/lib', thislib, false, '*.jar' ); + fileCopy( '/commandbox/system/config/web.xml', thisWebinf & '/web.xml'); + + return installDetails; + } + if( !packageService.installPackage( ID=arguments.ID, directory=thisTempDir, save=false ) ) { throw( message='Server not installed.', type="commandException"); } @@ -238,7 +253,15 @@ component accessors="true" singleton="true" { initParam.XmlChildren[1] = xmlElemnew(webXML,"param-name"); initParam.XmlChildren[1].XmlText = "#lcase( cfengine )#-web-directory"; initParam.XmlChildren[2] = xmlElemnew(webXML,"param-value"); - initParam.XmlChildren[2].XmlText = "/WEB-INF/#lcase( cfengine )#/{web-context-label}"; + initParam.XmlChildren[2].XmlText = "/WEB-INF/#lcase( cfengine )#-web"; + arrayInsertAt(servlets[1].XmlParent.XmlChildren,4,initParam); + + var servlets = xmlSearch(webXML,"//:servlet-class[text()='#lcase( cfengine )#.loader.servlet.CFMLServlet']"); + var initParam = xmlElemnew(webXML,"http://java.sun.com/xml/ns/javaee","init-param"); + initParam.XmlChildren[1] = xmlElemnew(webXML,"param-name"); + initParam.XmlChildren[1].XmlText = "#lcase( cfengine )#-server-directory"; + initParam.XmlChildren[2] = xmlElemnew(webXML,"param-value"); + initParam.XmlChildren[2].XmlText = "/WEB-INF"; arrayInsertAt(servlets[1].XmlParent.XmlChildren,4,initParam); // Lucee 5+ has a LuceeServlet as well as will create the WEB-INF by default in your web root @@ -248,7 +271,15 @@ component accessors="true" singleton="true" { initParam.XmlChildren[1] = xmlElemnew(webXML,"param-name"); initParam.XmlChildren[1].XmlText = "#lcase( cfengine )#-web-directory"; initParam.XmlChildren[2] = xmlElemnew(webXML,"param-value"); - initParam.XmlChildren[2].XmlText = "/WEB-INF/#lcase( cfengine )#/{web-context-label}"; + initParam.XmlChildren[2].XmlText = "/WEB-INF/#lcase( cfengine )#-web"; + arrayInsertAt(servlets[1].XmlParent.XmlChildren,4,initParam); + + var servlets = xmlSearch(webXML,"//:servlet-class[text()='#lcase( cfengine )#.loader.servlet.LuceeServlet']"); + var initParam = xmlElemnew(webXML,"http://java.sun.com/xml/ns/javaee","init-param"); + initParam.XmlChildren[1] = xmlElemnew(webXML,"param-name"); + initParam.XmlChildren[1].XmlText = "#lcase( cfengine )#-server-directory"; + initParam.XmlChildren[2] = xmlElemnew(webXML,"param-value"); + initParam.XmlChildren[2].XmlText = "/WEB-INF"; arrayInsertAt(servlets[1].XmlParent.XmlChildren,4,initParam); } From 6a6ae7d9d7eba30f89e523e111d473c91fb4061d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 8 Dec 2016 12:33:45 -0600 Subject: [PATCH 47/87] COMMANDBOX-530 --- build/build.properties | 2 +- build/build.xml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build/build.properties b/build/build.properties index c29e45ec2..5d43ec69a 100644 --- a/build/build.properties +++ b/build/build.properties @@ -12,7 +12,7 @@ java.pack200=false #dependencies dependencies.dir=${basedir}/lib cfml.version=4.5.4.017 -cfml.loader.version=1.4.3 +cfml.loader.version=1.4.4 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} jre.version=1.8.0_102 diff --git a/build/build.xml b/build/build.xml index 14b69e69a..5efd8ad4a 100644 --- a/build/build.xml +++ b/build/build.xml @@ -803,7 +803,8 @@ External Dependencies: --> - + + From 38251570aa19863525a2209acd4451943c0494bf Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 8 Dec 2016 12:44:47 -0600 Subject: [PATCH 48/87] COMMANDBOX-531 --- src/cfml/system/BaseCommand.cfc | 10 ++++++---- src/cfml/system/Bootstrap.cfm | 5 ++--- src/cfml/system/Shell.cfc | 26 +++++++------------------- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/cfml/system/BaseCommand.cfc b/src/cfml/system/BaseCommand.cfc index ecec1c009..b52473fed 100644 --- a/src/cfml/system/BaseCommand.cfc +++ b/src/cfml/system/BaseCommand.cfc @@ -63,15 +63,17 @@ component accessors="true" singleton { function getCWD() { return shell.pwd(); } - + /** - * Ask the user a question and wait for response + * ask the user a question and wait for response * @message.hint message to prompt the user with * @mask.hint When not empty, keyboard input is masked as that character + * + * @return the response from the user **/ - function ask( required message, string mask='' ) { + string function ask( message, string mask='', string defaultResponse='' ) { print.toConsole(); - return shell.ask( arguments.message, arguments.mask ); + return shell.ask( arguments.message, arguments.mask, arguments.defaultResponse ); } /** diff --git a/src/cfml/system/Bootstrap.cfm b/src/cfml/system/Bootstrap.cfm index 9ae061a0c..efd187085 100644 --- a/src/cfml/system/Bootstrap.cfm +++ b/src/cfml/system/Bootstrap.cfm @@ -112,10 +112,9 @@ Type "help" for help, or "help [command]" to be more specific. while( shell.run( silent=silent ) ){ clearScreen = shell.getDoClearScreen(); - interceptorService.announceInterception( 'onCLIExit' ); - + interceptorService.announceInterception( 'onCLIExit' ); if( clearScreen ){ - shell.getReader().clearScreen(); + shell.clearScreen(); } // Clear all caches: template, ... diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index 6fca23e61..514758de8 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -197,10 +197,11 @@ component accessors="true" singleton { * ask the user a question and wait for response * @message.hint message to prompt the user with * @mask.hint When not empty, keyboard input is masked as that character + * @defaultResponse Text to populate the buffer with by default that will be submitted if the user presses enter without typing anything * * @return the response from the user **/ - string function ask( message, string mask='', string buffer='' ) { + string function ask( message, string mask='', string defaultResponse='' ) { try { // read reponse while masking input @@ -208,10 +209,10 @@ component accessors="true" singleton { // Prompt for the user arguments.message, // Optionally mask their input - len( arguments.mask ) ? javacast( "char", left( arguments.mask, 1 ) ) : javacast( "null", '' )//, + len( arguments.mask ) ? javacast( "char", left( arguments.mask, 1 ) ) : javacast( "null", '' ), // This won't work until we can upgrade to Jline 2.14 // Optionally pre-fill a default response for them - // len( arguments.buffer ) ? javacast( "String", arguments.buffer ) : javacast( "null", '' ) + len( arguments.defaultResponse ) ? javacast( "String", arguments.defaultResponse ) : javacast( "null", '' ) ); } catch( jline.console.UserInterruptException var e ) { throw( message='CANCELLED', type="UserInterruptException"); @@ -262,22 +263,9 @@ component accessors="true" singleton { * @note Almost works on Windows, but doesn't clear text background * **/ - Shell function clearScreen( addLines = true ) { - // This outputs a double prompt due to the redrawLine() call - // reader.clearScreen(); - - // A temporary workaround for windows. Since background colors aren't cleared - // this will force them off the screen with blank lines before clearing. - if( variables.fileSystem.isWindows() && arguments.addLines ) { - var i = 0; - while( ++i <= getTermHeight() + 5 ) { - variables.reader.println(); - } - } - - variables.reader.print( '' ); - variables.reader.print( '' ); - + Shell function clearScreen() { + reader.clearScreen(); + variables.reader.flush(); return this; } From 6faab94ba3f31136aab691296cfe845f143dae37 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 12 Dec 2016 15:15:40 -0600 Subject: [PATCH 49/87] OMMANDBOX-533 --- src/cfml/system/Shell.cfc | 6 ++++-- .../system/modules_app/system-commands/commands/tail.cfc | 5 +++-- src/cfml/system/services/InterceptorService.cfc | 4 ++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index 514758de8..be8a6e147 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -527,11 +527,13 @@ component accessors="true" singleton { setExitCode( 1 ); - getInterceptorService().announceInterception( 'onException', { exception=err } ); + // If CommandBox blows up while starting, the interceptor service won't be ready yet. + if( getInterceptorService().getConfigured() ) { + getInterceptorService().announceInterception( 'onException', { exception=err } ); + } variables.logger.error( '#arguments.err.message# #arguments.err.detail ?: ''#', arguments.err.stackTrace ?: '' ); - variables.reader.print( variables.print.whiteOnRedLine( 'ERROR (#variables.version#)' ) ); variables.reader.println(); variables.reader.print( variables.print.boldRedText( variables.formatterUtil.HTML2ANSI( arguments.err.message ) ) ); diff --git a/src/cfml/system/modules_app/system-commands/commands/tail.cfc b/src/cfml/system/modules_app/system-commands/commands/tail.cfc index 0dee1a0c7..87c48bebc 100644 --- a/src/cfml/system/modules_app/system-commands/commands/tail.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/tail.cfc @@ -45,6 +45,7 @@ component { } variables.file = createObject( "java", "java.io.File" ).init( filePath ); + var startPos = findStartPos(); var startingLength = 0; @@ -52,8 +53,8 @@ component { var lineCounter = 0; var buffer = []; - var randomAccessFile = createObject( "java", "java.io.RandomAccessFile" ).init( file, "r" ); - var startingLength = file.length(); + var randomAccessFile = createObject( "java", "java.io.RandomAccessFile" ).init( variables.file, "r" ); + var startingLength = variables.file.length(); variables.position = startingLength; // move to the end of the file diff --git a/src/cfml/system/services/InterceptorService.cfc b/src/cfml/system/services/InterceptorService.cfc index ea83b8a27..baca2c8f6 100644 --- a/src/cfml/system/services/InterceptorService.cfc +++ b/src/cfml/system/services/InterceptorService.cfc @@ -11,6 +11,7 @@ component accessors=true singleton { property name='Shell'; property name='EventPoolManager'; property name='InterceptionPoints'; + property name='configured' type="boolean" default="false"; // DI property name='log' inject='logbox:logger:{this}'; @@ -20,6 +21,8 @@ component accessors=true singleton { */ InterceptorService function init( required shell ) { + setConfigured( false ); + setShell( arguments.shell ); setInterceptionPoints( [ @@ -44,6 +47,7 @@ component accessors=true singleton { function configure() { setEventPoolManager( getShell().getWireBox().getEventManager() ); appendInterceptionPoints( getInterceptionPoints().toList() ); + setConfigured( true ); return this; } From fdc7b124e522a8341d4654e1190d2bf67a1b22e1 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 12 Dec 2016 16:19:37 -0600 Subject: [PATCH 50/87] Capture and use actual cfengine name and version --- .../server-commands/commands/server/list.cfc | 10 +++++----- .../server-commands/commands/server/status.cfc | 4 ++-- src/cfml/system/services/ServerEngineService.cfc | 2 ++ src/cfml/system/services/ServerService.cfc | 4 ++++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/list.cfc b/src/cfml/system/modules_app/server-commands/commands/server/list.cfc index 06321d322..e1209eec6 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/list.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/list.cfc @@ -89,9 +89,9 @@ component { if( arguments.verbose ) { - print.indentedLine( "host: " & thisServerInfo.host ); - if( len( thisServerInfo.cfengine ) ) { - print.indentedLine( "CF Engine: " & thisServerInfo.cfengine ); + print.indentedLine( "host: " & thisServerInfo.host ); + if( len( thisServerInfo.engineName ) ) { + print.indentedLine( "CF Engine: " & thisServerInfo.engineName & ' ' & serverInfo.engineVersion ); } if( len( thisServerInfo.WARPath ) ) { print.indentedLine( "WARPath: " & thisServerInfo.WARPath ); @@ -123,8 +123,8 @@ component { if( thisServerInfo.SSLEnable ) { print.indentedLine( 'https://' & thisServerInfo.host & ':' & thisServerInfo.SSLport ); } - if( len( thisServerInfo.cfengine ) ) { - print.indentedLine( 'CF Engine: ' & thisServerInfo.cfengine ); + if( len( thisServerInfo.engineName ) ) { + print.indentedLine( 'CF Engine: ' & thisServerInfo.engineName & ' ' & thisServerInfo.engineVersion ); } if( len( thisServerInfo.warPath ) ) { print.indentedLine( 'WAR Path: ' & thisServerInfo.warPath ); diff --git a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc index 3c54fd866..70d62ef45 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc @@ -106,8 +106,8 @@ component aliases='status,server info' { .bold( ')' ); print.indentedLine( thisServerInfo.host & ':' & thisServerInfo.port & ' --> ' & thisServerInfo.webroot ); - if( len( serverInfo.cfengine ) ) { - print.indentedLine( 'CF Engine: ' & serverInfo.cfengine ); + if( len( serverInfo.engineName ) ) { + print.indentedLine( 'CF Engine: ' & serverInfo.engineName & ' ' & serverInfo.engineVersion ); } if( len( serverInfo.warPath ) ) { print.indentedLine( 'WAR Path: ' & serverInfo.warPath ); diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index f508c9d9d..231a051ab 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -118,6 +118,7 @@ component accessors="true" singleton="true" { var installDetails = { internal : false, + engineName : '', version : '', installDir : '', initialInstall : false @@ -129,6 +130,7 @@ component accessors="true" singleton="true" { var endpointData = endpointService.resolveEndpoint( ID, shell.pwd() ); var endpoint = endpointData.endpoint; var engineName = endpoint.getDefaultName( arguments.ID ); + installDetails.engineName = engineName; // In order to prevent uneccessary work, we're going to try REALLY hard to figure out exactly what engine will be installed // before it actually happens so we can skip this whole mess if it's already in place. diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 2966cd036..1f8ccb47b 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -531,6 +531,8 @@ component accessors="true" singleton { thisVersion = ' ' & installDetails.version; serverInfo.serverHome = installDetails.installDir; serverInfo.logdir = installDetails.installDir & "/logs"; + serverInfo.engineName = installDetails.engineName; + serverInfo.engineVersion = installDetails.version; // This is for one-time setup tasks on first install if( installDetails.initialInstall ) { @@ -1314,6 +1316,8 @@ component accessors="true" singleton { JVMargs : "", runwarArgs : "", cfengine : "", + engineName : "", + engineVersion : "", WARPath : "", serverConfigFile : "" }; From ba58e2d1b5b2674bba0c73ded8ed3c7794220fa2 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 13 Dec 2016 22:23:38 -0600 Subject: [PATCH 51/87] COMMANDBOX-534 --- .../server-commands/commands/server/log.cfc | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/log.cfc b/src/cfml/system/modules_app/server-commands/commands/server/log.cfc index d111ed637..f11c3e514 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/log.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/log.cfc @@ -17,11 +17,13 @@ component { * @name.optionsUDF serverNameComplete * @directory.hint web root for the server * @serverConfigFile The path to the server's JSON file. + * @follow Tail the log file with the "follow" flag. Press Ctrl-C to quit. **/ function run( string name, string directory, - String serverConfigFile + String serverConfigFile, + Boolean follow=false ){ if( !isNull( arguments.directory ) ) { arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); @@ -39,7 +41,16 @@ component { var logfile = serverInfo.logdir & "/server.out.txt"; if( fileExists( logfile) ){ - return fileRead( logfile ); + + if( follow ) { + command( 'tail' ) + .params( logfile, 50 ) + .flags( 'follow' ) + .run(); + } else { + return fileRead( logfile ); + } + } else { print.boldRedLine( "No log file found for '#serverInfo.webroot#'!" ) .line( "#logFile#" ); From 7197074c4f35ac2ddc676d2d117f3cde45ac8992 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 13 Dec 2016 22:54:14 -0600 Subject: [PATCH 52/87] Fix off-by-one error on empty file --- .../system-commands/commands/tail.cfc | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/tail.cfc b/src/cfml/system/modules_app/system-commands/commands/tail.cfc index 87c48bebc..8bec01a15 100644 --- a/src/cfml/system/modules_app/system-commands/commands/tail.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/tail.cfc @@ -62,17 +62,15 @@ component { // Was the last character a line feed. // Remeber the CRLFs will be coming in reverse order var lastLF = false; + + while( true && startingLength ){ - while( true ){ - - // stop looping if we have met our line limit or if end of file - if ( position < startPos-1 || lineCounter == arguments.lines ) { - if( buffer.len() ) { - // Strip any CR or LF from the last (first really) line to eliminate leading line breaks in console output - buffer[ buffer.len() ] = listChangeDelims( buffer[ buffer.len() ], '', chr(13) & chr( 10 ) ); - } - break; - } + print + .redLine(position) + .redLine(startPos) + .redLine(lineCounter) + .redLine(arguments.lines) + .redLine(); var char = randomAccessFile.read(); @@ -88,10 +86,22 @@ component { lastLF=false; } if ( char != -1 ) buffer.append( chr( char ) ); + + position--; + + // stop looping if we have met our line limit or if end of file + if ( position < startPos || lineCounter == arguments.lines ) { + break; + } // move to the preceding character - randomAccessFile.seek( position-- ); + randomAccessFile.seek( position ); + } // End while + + if( buffer.len() ) { + // Strip any CR or LF from the last (first really) line to eliminate leading line breaks in console output + buffer[ buffer.len() ] = listChangeDelims( buffer[ buffer.len() ], '', chr(13) & chr( 10 ) ); } // print our file to console From b15da4fce8b00486006a0052d754ec70472ef479 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 13 Dec 2016 22:54:31 -0600 Subject: [PATCH 53/87] Remove debugging --- .../system/modules_app/system-commands/commands/tail.cfc | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/tail.cfc b/src/cfml/system/modules_app/system-commands/commands/tail.cfc index 8bec01a15..9a552e41f 100644 --- a/src/cfml/system/modules_app/system-commands/commands/tail.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/tail.cfc @@ -64,14 +64,7 @@ component { var lastLF = false; while( true && startingLength ){ - - print - .redLine(position) - .redLine(startPos) - .redLine(lineCounter) - .redLine(arguments.lines) - .redLine(); - + var char = randomAccessFile.read(); // Only increment CRs that were preceeded by a LF From 50ed4ac325cba68f67f0d724f2acb9178c401901 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 13 Dec 2016 23:06:23 -0600 Subject: [PATCH 54/87] Final cleanup of internal server --- .../system/services/ServerEngineService.cfc | 4 +--- src/cfml/system/services/ServerService.cfc | 18 ++++-------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index 231a051ab..f8b2ff215 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -85,7 +85,7 @@ component accessors="true" singleton="true" { public function installLucee( required destination, required version ) { var installDetails = installEngineArchive( 'lucee@#version#', destination ); - if( !installDetails.internal && installDetails.initialInstall ) { + if( installDetails.initialInstall ) { configureWebXML( cfengine="lucee", version=installDetails.version, source="#installDetails.installDir#/WEB-INF/web.xml", destination="#installDetails.installDir#/WEB-INF/web.xml" ); } return installDetails; } @@ -117,7 +117,6 @@ component accessors="true" singleton="true" { ) { var installDetails = { - internal : false, engineName : '', version : '', installDir : '', @@ -191,7 +190,6 @@ component accessors="true" singleton="true" { // If we're starting a Lucee server whose version matches the CLI engine, then don't download anything, we're using internal jars. if( listFirst( arguments.ID, '@' ) == 'lucee' && server.lucee.version == replace( installDetails.version, '+', '.', 'all' ) ) { - // installDetails.internal = true; consoleLogger.info( "Building a WAR from local jars."); diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 1f8ccb47b..255bb5964 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -543,8 +543,8 @@ component accessors="true" singleton { interceptorService.announceInterception( 'onServerInstall', { serverInfo=serverInfo, installDetails=installDetails } ); } - // If external Lucee server, set the java agent - if( !installDetails.internal && serverInfo.cfengine contains "lucee" ) { + // If Lucee server, set the java agent + if( serverInfo.cfengine contains "lucee" ) { // Detect Lucee 4.x if( installDetails.version.listFirst( '.' ) < 5 ) { javaagent = "-javaagent:#installDetails.installDir#/WEB-INF/lib/lucee-inst.jar"; @@ -554,14 +554,9 @@ component accessors="true" singleton { } } // If external Railo server, set the java agent - if( !installDetails.internal && serverInfo.cfengine contains "railo" ) { + if( serverInfo.cfengine contains "railo" ) { javaagent = "-javaagent:#installDetails.installDir#/WEB-INF/lib/railo-inst.jar"; } - // Using built in server that hasn't been started before - if( installDetails.internal && !directoryExists( serverInfo.webConfigDir & '/WEB-INF' ) ) { - serverInfo.webConfigDir = installDetails.installDir; - serverInfo.logdir = serverInfo.webConfigDir & "/logs"; - } // The process native name var processName = ( serverInfo.name is "" ? "CommandBox" : serverInfo.name ) & ' [' & listFirst( serverinfo.cfengine, '@' ) & thisVersion & ']'; @@ -678,13 +673,8 @@ component accessors="true" singleton { if (serverInfo.WARPath != "" ) { args &= " -war ""#serverInfo.WARPath#"""; // Stand alone server - } else if( !installDetails.internal ){ - args &= " -war ""#serverInfo.webroot#"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; - // internal server } else { - // The internal server borrows the CommandBox lib directory - serverInfo.libDirs = serverInfo.libDirs.listAppend( variables.libDir ); - args &= " -war ""#serverInfo.webroot#"""; + args &= " -war ""#serverInfo.webroot#"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; } if( len( serverInfo.libDirs ) ) { From e9b1fc9090053d6d9d3e8dde05db4ad388f4d3b2 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 13 Dec 2016 23:47:11 -0600 Subject: [PATCH 55/87] COMMANDBOX-535 --- src/cfml/system/modules_app/system-commands/commands/cp.cfc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cfml/system/modules_app/system-commands/commands/cp.cfc b/src/cfml/system/modules_app/system-commands/commands/cp.cfc index f03bb99be..c04c9b5bd 100644 --- a/src/cfml/system/modules_app/system-commands/commands/cp.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/cp.cfc @@ -39,6 +39,7 @@ component aliases="copy" { // It's a file } else if( fileExists( arguments.path ) ){ // Copy file + DirectoryCreate( getDirectoryFromPath( arguments.newPath ), true, true ); fileCopy( arguments.path, arguments.newPath ); print.greenLine( "File copied to #arguments.newPath#" ); } else { From f27cdb1c2157f9f333d637abcd97033d1476c693 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 15 Dec 2016 00:42:47 -0600 Subject: [PATCH 56/87] COMMANDBOX-532 --- src/cfml/system/endpoints/ForgeBox.cfc | 36 +++++++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/cfml/system/endpoints/ForgeBox.cfc b/src/cfml/system/endpoints/ForgeBox.cfc index e6158a6f3..1fa192406 100644 --- a/src/cfml/system/endpoints/ForgeBox.cfc +++ b/src/cfml/system/endpoints/ForgeBox.cfc @@ -41,9 +41,10 @@ component accessors="true" implements="IEndpointInteractive" singleton { public string function resolvePackage( required string package, boolean verbose=false ) { var slug = parseSlug( arguments.package ); var version = parseVersion( arguments.package ); - - // If we have a specific version and it exists in artifacts, use it. Otherwise, to ForgeBox!! - if( semanticVersion.isExactVersion( version ) && artifactService.artifactExists( slug, version ) ) { + var strVersion = semanticVersion.parseVersion( version ); + + // If we have a specific version and it exists in artifacts and this isn't a snapshot build, use it. Otherwise, to ForgeBox!! + if( semanticVersion.isExactVersion( version ) && artifactService.artifactExists( slug, version ) && strVersion.preReleaseID != 'snapshot' ) { consoleLogger.info( "Package found in local artifacts!"); // Install the package var thisArtifactPath = artifactService.getArtifactPath( slug, version ); @@ -368,8 +369,10 @@ component accessors="true" implements="IEndpointInteractive" singleton { // Advice we found it consoleLogger.info( "Verified entry in ForgeBox: '#slug#'" ); - // If the local artifact doesn't exist, download and create it - if( !artifactService.artifactExists( slug, version ) ) { + var strVersion = semanticVersion.parseVersion( version ); + + // If the local artifact doesn't exist or it's a snapshot build, download and create it + if( !artifactService.artifactExists( slug, version ) || strVersion.preReleaseID == 'snapshot' ) { // Test package location to see what endpoint we can refer to. var endpointData = endpointService.resolveEndpoint( downloadURL, 'fakePath' ); @@ -403,8 +406,27 @@ component accessors="true" implements="IEndpointInteractive" singleton { } catch( forgebox var e ) { - // This can include "expected" errors such as "slug not found" - throw( '#e.message##CR##e.detail#', 'endpointException' ); + + consoleLogger.error( "."); + consoleLogger.error( "Aww man, ForgeBox isn't feeling well."); + consoleLogger.debug( "#e.message# #e.detail#"); + consoleLogger.error( "We're going to look in your local artifacts cache and see if one of those versions will work."); + + // See if there's something usable in the artifacts cache. If so, we'll use that version. + var satisfyingVersion = artifactService.findSatisfyingVersion( slug, version ); + + if( len( satisfyingVersion ) ) { + consoleLogger.info( "."); + consoleLogger.info( "Sweet! We found a local version of [#satisfyingVersion#] that we can use in your artifacts."); + consoleLogger.info( "."); + + var thisArtifactPath = artifactService.getArtifactPath( slug, satisfyingVersion ); + // Defer to file endpoint + return fileEndpoint.resolvePackage( thisArtifactPath, arguments.verbose ); + } else { + throw( 'No satisfying version found for [#version#].', 'endpointException', 'Well, we tried as hard as we can. ForgeBox is unreachable and you don''t have a usable version in your local artifacts cache. Please try another version.' ); + } + } } From f1c1db92961ee9967e611aade559642b87dfc667 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 15 Dec 2016 00:50:34 -0600 Subject: [PATCH 57/87] COMMANDBOX-537 --- src/cfml/system/endpoints/Git.cfc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cfml/system/endpoints/Git.cfc b/src/cfml/system/endpoints/Git.cfc index 51406cdfe..9d82c1727 100644 --- a/src/cfml/system/endpoints/Git.cfc +++ b/src/cfml/system/endpoints/Git.cfc @@ -123,6 +123,12 @@ component accessors="true" implements="IEndpoint" singleton { } } + // Clean up a bit + var gitFolder = localPath.getPath() & '/.git'; + if( directoryExists( gitFolder ) ) { + directoryDelete( gitFolder, true ); + } + // Defer to file endpoint return folderEndpoint.resolvePackage( localPath.getPath(), arguments.verbose ); From ced1ce5b14287fee0cfedd71adb8affb9a1f7c08 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 15 Dec 2016 01:02:55 -0600 Subject: [PATCH 58/87] COMMANDBOX-222 --- build/build.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/build.xml b/build/build.xml index 5efd8ad4a..ae8579b30 100644 --- a/build/build.xml +++ b/build/build.xml @@ -182,14 +182,14 @@ External Dependencies: + dump-cfc="${temp.dir}/engine/cfml/cli/lucee-server/context/library/tag/Dump.cfc"/> From 114d45c1662bb2e71d1ff3043dee4af9eaf1fe1f Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 15 Dec 2016 17:34:55 -0600 Subject: [PATCH 59/87] COMMANDBOX-538 --- .../system/config/server-icons/trayicon.png | Bin 0 -> 53782 bytes .../commands/server/status.cfc | 49 ++++++++++++++++-- src/cfml/system/services/ServerService.cfc | 46 ++++++++++------ 3 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 src/cfml/system/config/server-icons/trayicon.png diff --git a/src/cfml/system/config/server-icons/trayicon.png b/src/cfml/system/config/server-icons/trayicon.png new file mode 100644 index 0000000000000000000000000000000000000000..06e27c927842a16ad5ba658212244fdd9bd28d55 GIT binary patch literal 53782 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

IO$gJ9uG|j%m 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 literal 0 HcmV?d00001 diff --git a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc index 70d62ef45..f37f0eea0 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc @@ -45,6 +45,8 @@ component aliases='status,server info' { * @showAll.hint show all server statuses * @verbose.hint Show extra details * @json Output the server data as json + * @property Name of a specific property to output. JSON default to true if this is used. + * @property.optionsUDF propertyComplete **/ function run( string name, @@ -52,11 +54,14 @@ component aliases='status,server info' { String serverConfigFile, boolean showAll=false, boolean verbose=false, - boolean JSON=false - ){ + boolean JSON=false, + string property='' ){ // Get all server definitions var servers = serverService.getServers(); + // If you specify a property, JSON gets enabled. + arguments.JSON = ( arguments.JSON || len( property ) ); + // Display ALL as JSON? if( arguments.showALL && arguments.json ){ print.line( @@ -92,9 +97,36 @@ component aliases='status,server info' { // Are we doing JSON? if( arguments.json ){ - print.line( - formatterUtil.formatJson( serializeJSON( thisServerInfo ) ) - ); + + // Are we outputing a specific propery + if( len( arguments.property ) ) { + + // If the key doesn't exist, give a useful error + if( !isDefined( 'thisServerInfo.#arguments.property#' ) ) { + error( "The propery [#arguments.property#] isn't defined in the JSON.", "Valid keys are: " & chr( 10 ) & " - " & thisServerInfo.keyList().lCase().listChangeDelims( chr( 10 ) & " - " ) ); + } + + // Output a single property + var thisValue = evaluate( 'thisServerInfo.#arguments.property#' ); + // Output simple values directly so they're useful + if( isSimpleValue( thisValue ) ) { + print.line( thisValue ); + // Format Complex values as JSON + } else { + print.line( + formatterUtil.formatJson( serializeJSON( thisValue ) ) + ); + } + + } else { + + // Output the entire object + print.line( + formatterUtil.formatJson( serializeJSON( thisServerInfo ) ) + ); + + } + continue; } @@ -140,5 +172,12 @@ component aliases='status,server info' { function serverNameComplete() { return serverService.getServerNames(); } + + /** + * AutoComplete serverInfo properties + */ + function propertyComplete() { + return serverService.newServerInfoStruct().keyArray(); + } } \ No newline at end of file diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 255bb5964..030977a70 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -481,15 +481,17 @@ component accessors="true" singleton { serverInfo.cfengine = serverProps.cfengine ?: serverJSON.app.cfengine ?: defaults.app.cfengine; + + // relative rewrite config path in server.json is resolved relative to the server.json + if( isDefined( 'serverJSON.app.WARPath' ) && len( serverJSON.app.WARPath ) ) { serverJSON.app.WARPath = fileSystemUtil.resolvePath( serverJSON.app.WARPath, defaultServerConfigFileDirectory ); } + if( isDefined( 'defaults.app.WARPath' ) && len( defaults.app.WARPath ) ) { defaults.app.WARPath = fileSystemUtil.resolvePath( defaults.app.WARPath, defaultwebroot ); } serverInfo.WARPath = serverProps.WARPath ?: serverJSON.app.WARPath ?: defaults.app.WARPath; // These are already hammered out above, so no need to go through all the defaults. serverInfo.serverConfigFile = defaultServerConfigFile; serverInfo.name = defaultName; serverInfo.webroot = defaultwebroot; - - serverInfo.logdir = serverInfo.webConfigDir & "/logs"; - + if( serverInfo.debug ) { consoleLogger.info( "start server in - " & serverInfo.webroot ); consoleLogger.info( "server name - " & serverInfo.name ); @@ -507,10 +509,6 @@ component accessors="true" singleton { var launchUtil = java.LaunchUtil; - // log directory location - if( !directoryExists( serverInfo.logDir ) ){ directoryCreate( serverInfo.logDir ); } - - // Default java agent for embedded Lucee engine var javaagent = serverinfo.cfengine contains 'lucee' ? '-javaagent:#libdir#/lucee-inst.jar' : ''; @@ -520,7 +518,6 @@ component accessors="true" singleton { CFEngineName = serverinfo.cfengine contains 'railo' ? 'railo' : CFEngineName; CFEngineName = serverinfo.cfengine contains 'adobe' ? 'adobe' : CFEngineName; - var thisVersion = ''; var processName = ( serverInfo.name is "" ? "CommandBox" : serverInfo.name ); // As long as there's no WAR Path, let's install the engine to use. @@ -528,9 +525,9 @@ component accessors="true" singleton { // This will install the engine war to start, possibly downloading it first var installDetails = serverEngineService.install( cfengine=serverInfo.cfengine, basedirectory=serverInfo.webConfigDir ); - thisVersion = ' ' & installDetails.version; serverInfo.serverHome = installDetails.installDir; serverInfo.logdir = installDetails.installDir & "/logs"; + serverInfo.consolelogPath = serverInfo.logdir & '/server.out.txt'; serverInfo.engineName = installDetails.engineName; serverInfo.engineVersion = installDetails.version; @@ -559,7 +556,7 @@ component accessors="true" singleton { } // The process native name - var processName = ( serverInfo.name is "" ? "CommandBox" : serverInfo.name ) & ' [' & listFirst( serverinfo.cfengine, '@' ) & thisVersion & ']'; + var processName = ( serverInfo.name is "" ? "CommandBox" : serverInfo.name ) & ' [' & listFirst( serverinfo.cfengine, '@' ) & ' ' & installDetails.version & ']'; // Find the correct tray icon for this server if( !len( serverInfo.trayIcon ) ) { @@ -587,11 +584,22 @@ component accessors="true" singleton { // This is a WAR } else { - serverInfo.serverHome = getDirectoryFromPath( serverInfo.WARPath ); + // If WAR is a file + if( fileExists( serverInfo.WARPath ) ){ + // It will be extracted into a folder named after the file + serverInfo.serverHome = reReplaceNoCase( serverInfo.WARPath, '(.*)(\.zip|\.war)', '\1' ); + // If WAR is a folder + } else { + // Just use it + serverInfo.serverHome = serverInfo.WARPath; + } + // Create a custom server folder to house the logs + serverInfo.logdir = getCustomServerFolder( serverInfo ) & "/logs"; + serverInfo.consolelogPath = serverInfo.logdir & '/server.out.txt'; } // Default tray icon - serverInfo.trayIcon = ( len( serverInfo.trayIcon ) ? serverInfo.trayIcon : '#variables.libdir#/trayicon.png' ); + serverInfo.trayIcon = ( len( serverInfo.trayIcon ) ? serverInfo.trayIcon : '/commandbox/system/config/server-icons/trayicon.png' ); serverInfo.trayIcon = expandPath( serverInfo.trayIcon ); // Set default options for all servers @@ -645,13 +653,12 @@ component accessors="true" singleton { } // Serialize tray options and write to temp file - var trayOptionsPath = serverInfo.serverHome & '/trayOptions.json'; + var trayOptionsPath = getCustomServerFolder( serverInfo ) & '/trayOptions.json'; var trayJSON = { 'title' : processName, 'tooltip' : processName, 'items' : serverInfo.trayOptions }; - fileWrite( trayOptionsPath, serializeJSON( trayJSON ) ); var background = !(serverProps.console ?: false); // The java arguments to execute: Shared server, custom web configs @@ -665,9 +672,9 @@ component accessors="true" singleton { & ' --server-name "#serverInfo.name#" #errorPages#' & ( len( serverInfo.welcomeFiles ) ? ' --welcome-files "#serverInfo.welcomeFiles#" ' : '' ) & ' --tray-icon "#serverInfo.trayIcon#" --tray-config "#trayOptionsPath#" --servlet-rest-mappings "/rest/*,/api/*"' - & ' --directoryindex "#serverInfo.directoryBrowsing#" --cfml-web-config "#serverInfo.webConfigDir#"' + & ' --directoryindex "#serverInfo.directoryBrowsing#" ' & ( len( CLIAliases ) ? ' --dirs "#CLIAliases#"' : '' ) - & ' --cfml-server-config "#serverInfo.serverConfigDir#" #serverInfo.runwarArgs# --timeout #serverInfo.startTimeout#'; + & ' #serverInfo.runwarArgs# --timeout #serverInfo.startTimeout#'; // Starting a WAR if (serverInfo.WARPath != "" ) { @@ -1287,10 +1294,12 @@ component accessors="true" singleton { }, name : "", logDir : "", + consolelogPath : "", trayicon : "", libDirs : "", webConfigDir : "", serverConfigDir : "", + serverHome : "", webroot : "", webXML : "", HTTPEnable : true, @@ -1309,7 +1318,10 @@ component accessors="true" singleton { engineName : "", engineVersion : "", WARPath : "", - serverConfigFile : "" + serverConfigFile : "", + aliases : {}, + errorPages : {}, + trayOptions : {} }; } From 1b1577b3151522e8d6f502904aa555a87d10689d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 16 Dec 2016 00:36:33 -0600 Subject: [PATCH 60/87] COMMANDBOX-511 --- .../system/services/ServerEngineService.cfc | 73 ++++++++++----- src/cfml/system/services/ServerService.cfc | 88 +++++++++++++------ 2 files changed, 114 insertions(+), 47 deletions(-) diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index f8b2ff215..be84586be 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -26,20 +26,21 @@ component accessors="true" singleton="true" { * * @cfengine CFML Engine name (lucee, adobe, railo) * @baseDirectory base directory for server install + * @serverInfo The struct of server settings **/ - public function install( required cfengine, required baseDirectory ) { + public function install( required cfengine, required baseDirectory, required struct serverInfo ) { var version = listLen( cfengine, "@" )>1 ? listLast( cfengine, "@" ) : ""; - var engineName = listFirst(cfengine,"@"); + var engineName = listFirst( cfengine, "@" ); arguments.baseDirectory = !arguments.baseDirectory.endsWith( "/" ) ? arguments.baseDirectory & "/" : arguments.baseDirectory; if( engineName == "adobe" ) { - return installAdobe( destination=arguments.baseDirectory, version=version ); + return installAdobe( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo ); } else if (engineName == "railo") { - return installRailo( destination=arguments.baseDirectory, version=version ); + return installRailo( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo ); } else if (engineName == "lucee") { - return installLucee( destination=arguments.baseDirectory, version=version ); + return installLucee( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo ); } else { - return installEngineArchive( cfengine, arguments.baseDirectory ); + return installEngineArchive( cfengine, arguments.baseDirectory, serverInfo ); } } @@ -48,9 +49,10 @@ component accessors="true" singleton="true" { * * @destination target directory * @version Version number or empty to use default + * @serverInfo Struct of server settings **/ - public function installAdobe( required destination, required version ) { - var installDetails = installEngineArchive( 'adobe@#version#', destination ); + public function installAdobe( required destination, required version, required struct serverInfo ) { + var installDetails = installEngineArchive( 'adobe@#version#', destination, serverInfo ); // set password to "commandbox" // TODO: Just make this changes directly in the WAR files @@ -81,12 +83,14 @@ component accessors="true" singleton="true" { * * @destination target directory * @version Version number or empty to use default + * @serverInfo struct of server settings **/ - public function installLucee( required destination, required version ) { - var installDetails = installEngineArchive( 'lucee@#version#', destination ); + public function installLucee( required destination, required version, required struct serverInfo ) { + var installDetails = installEngineArchive( 'lucee@#version#', destination, serverInfo ); - if( installDetails.initialInstall ) { - configureWebXML( cfengine="lucee", version=installDetails.version, source="#installDetails.installDir#/WEB-INF/web.xml", destination="#installDetails.installDir#/WEB-INF/web.xml" ); } + if( installDetails.initialInstall ) { + configureWebXML( cfengine="lucee", version=installDetails.version, source=serverInfo.webXML, destination=serverInfo.webXML, serverInfo=serverInfo ); + } return installDetails; } @@ -95,11 +99,13 @@ component accessors="true" singleton="true" { * * @destination target directory * @version Version number or empty to use default + * @serverInfo struct of server settings **/ - public function installRailo( required destination, required version ) { - var installDetails = installEngineArchive( 'railo@#version#', destination ); + public function installRailo( required destination, required version, required struct serverInfo ) { + var installDetails = installEngineArchive( 'railo@#version#', destination, serverInfo ); + if( installDetails.initialInstall ) { - configureWebXML( cfengine="railo", version=installDetails.version, source="#installDetails.installDir#/WEB-INF/web.xml", destination="#installDetails.installDir#/WEB-INF/web.xml" ); + configureWebXML( cfengine="railo", version=installDetails.version, source=serverInfo.webXML, destination=serverInfo.webXML, serverInfo=serverInfo ); } return installDetails; } @@ -113,7 +119,8 @@ component accessors="true" singleton="true" { */ function installEngineArchive( required string ID, - required string destination + required string destination, + required struct serverInfo ) { var installDetails = { @@ -178,6 +185,29 @@ component accessors="true" singleton="true" { } + // Set default web.xml path now that we have an install dir + if( !len( serverInfo.webXML ) ) { + serverInfo.webXML = "#installDetails.installDir#/WEB-INF/web.xml"; + } + // Set up server and web context dirs if Railo or Lucee + if( serverinfo.cfengine contains 'lucee' || serverinfo.cfengine contains 'railo' ) { + // Default web context + if( !len( serverInfo.webConfigDir ) ) { + serverInfo.webConfigDir = "/WEB-INF/#lcase( listFirst( serverinfo.cfengine, "@" ) )#-web"; + } + // Default server context + if( !len( serverInfo.serverConfigDir ) ) { + serverInfo.serverConfigDir = "/WEB-INF"; + } + // Make relative to WEB-INF if possible + serverInfo.webConfigDir = replace( serverInfo.webConfigDir, '\', '/', 'all' ); + serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, '\', '/', 'all' ); + installDetails.installDir = replace( installDetails.installDir, '\', '/', 'all' ); + + serverInfo.webConfigDir = replace( serverInfo.webConfigDir, installDetails.installDir, '' ); + serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, installDetails.installDir, '' ); + } + // Check to see if this WAR has already been exploded if( fileExists( installDetails.installDir & '/WEB-INF/web.xml' ) ) { @@ -240,20 +270,19 @@ component accessors="true" singleton="true" { /** * configure web.xml file for Lucee and Railo - * TODO: Just make these changes directly in the WAR files * * @cfengine lucee or railo * @source source web.xml * @destination target web.xml **/ - public function configureWebXML( required cfengine, required version, required source, destination ) { + public function configureWebXML( required cfengine, required version, required source, required destination, required struct serverInfo ) { var webXML = XMLParse( source ); var servlets = xmlSearch(webXML,"//:servlet-class[text()='#lcase( cfengine )#.loader.servlet.CFMLServlet']"); var initParam = xmlElemnew(webXML,"http://java.sun.com/xml/ns/javaee","init-param"); initParam.XmlChildren[1] = xmlElemnew(webXML,"param-name"); initParam.XmlChildren[1].XmlText = "#lcase( cfengine )#-web-directory"; initParam.XmlChildren[2] = xmlElemnew(webXML,"param-value"); - initParam.XmlChildren[2].XmlText = "/WEB-INF/#lcase( cfengine )#-web"; + initParam.XmlChildren[2].XmlText = serverInfo.webConfigDir; arrayInsertAt(servlets[1].XmlParent.XmlChildren,4,initParam); var servlets = xmlSearch(webXML,"//:servlet-class[text()='#lcase( cfengine )#.loader.servlet.CFMLServlet']"); @@ -261,7 +290,7 @@ component accessors="true" singleton="true" { initParam.XmlChildren[1] = xmlElemnew(webXML,"param-name"); initParam.XmlChildren[1].XmlText = "#lcase( cfengine )#-server-directory"; initParam.XmlChildren[2] = xmlElemnew(webXML,"param-value"); - initParam.XmlChildren[2].XmlText = "/WEB-INF"; + initParam.XmlChildren[2].XmlText = serverInfo.serverConfigDir; arrayInsertAt(servlets[1].XmlParent.XmlChildren,4,initParam); // Lucee 5+ has a LuceeServlet as well as will create the WEB-INF by default in your web root @@ -271,7 +300,7 @@ component accessors="true" singleton="true" { initParam.XmlChildren[1] = xmlElemnew(webXML,"param-name"); initParam.XmlChildren[1].XmlText = "#lcase( cfengine )#-web-directory"; initParam.XmlChildren[2] = xmlElemnew(webXML,"param-value"); - initParam.XmlChildren[2].XmlText = "/WEB-INF/#lcase( cfengine )#-web"; + initParam.XmlChildren[2].XmlText = serverInfo.webConfigDir; arrayInsertAt(servlets[1].XmlParent.XmlChildren,4,initParam); var servlets = xmlSearch(webXML,"//:servlet-class[text()='#lcase( cfengine )#.loader.servlet.LuceeServlet']"); @@ -279,7 +308,7 @@ component accessors="true" singleton="true" { initParam.XmlChildren[1] = xmlElemnew(webXML,"param-name"); initParam.XmlChildren[1].XmlText = "#lcase( cfengine )#-server-directory"; initParam.XmlChildren[2] = xmlElemnew(webXML,"param-value"); - initParam.XmlChildren[2].XmlText = "/WEB-INF"; + initParam.XmlChildren[2].XmlText = serverInfo.serverConfigDir; arrayInsertAt(servlets[1].XmlParent.XmlChildren,4,initParam); } diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 030977a70..62df87662 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -19,10 +19,6 @@ component accessors="true" singleton { */ property name="serverConfig"; /** - * Where core and custom servers are stored - */ - property name="serverHomeDirectory"; - /** * Where custom servers are stored */ property name="customServerDirectory"; @@ -82,8 +78,6 @@ component accessors="true" singleton { variables.homeDir = arguments.homeDir; // the lib dir location, populated from shell later. variables.libDir = arguments.homeDir & "/lib"; - // Where core server is installed - variables.serverHomeDirectory = arguments.homeDir & "/engine/cfml/server/"; // Where custom server configs are stored variables.serverConfig = arguments.homeDir & "/servers.json"; // Where custom servers are stored @@ -156,7 +150,7 @@ component accessors="true" singleton { logDir : d.app.logDir ?: '', libDirs : d.app.libDirs ?: '', webConfigDir : d.app.webConfigDir ?: '', - serverConfigDir : d.app.serverConfigDir ?: variables.serverHomeDirectory, + serverConfigDir : d.app.serverConfigDir ?: '', webXML : d.app.webXML ?: '', standalone : d.app.standalone ?: false, WARPath : d.app.WARPath ?: "", @@ -193,6 +187,15 @@ component accessors="true" singleton { if( !isNull( serverProps.rewritesConfig ) ) { serverProps.rewritesConfig = fileSystemUtil.resolvePath( serverProps.rewritesConfig ); } + if( !isNull( serverProps.webConfigDir ) ) { + serverProps.webConfigDir = fileSystemUtil.resolvePath( serverProps.webConfigDir ); + } + if( !isNull( serverProps.serverConfigDir ) ) { + serverProps.serverConfigDir = fileSystemUtil.resolvePath( serverProps.serverConfigDir ); + } + if( !isNull( serverProps.webXML ) ) { + serverProps.webXML = fileSystemUtil.resolvePath( serverProps.webXML ); + } if( !isNull( serverProps.libDirs ) ) { // Comma-delimited list needs each item resolved serverProps.libDirs = serverProps.libDirs @@ -293,10 +296,31 @@ component accessors="true" singleton { serverJSON[ 'stopsocket' ] = serverProps[ prop ]; break; case "webConfigDir": - serverJSON[ 'app' ][ 'webConfigDir' ] = serverProps[ prop ]; - break; + // This path is canonical already. + var thisDirectory = replace( serverProps[ 'webConfigDir' ], '\', '/', 'all' ) & '/'; + // If the webConfigDir is south of the server's JSON, make it relative for better portability. + if( thisDirectory contains configPath ) { + thisDirectory = replaceNoCase( thisDirectory, configPath, '' ); + } + serverJSON[ 'app' ][ 'webConfigDir' ] = thisDirectory; + break; case "serverConfigDir": - serverJSON[ 'app' ][ 'serverConfigDir' ] = serverProps[ prop ]; + // This path is canonical already. + var thisDirectory = replace( serverProps[ 'serverConfigDir' ], '\', '/', 'all' ) & '/'; + // If the webConfigDir is south of the server's JSON, make it relative for better portability. + if( thisDirectory contains configPath ) { + thisDirectory = replaceNoCase( thisDirectory, configPath, '' ); + } + serverJSON[ 'app' ][ 'serverConfigDir' ] = thisDirectory; + break; + case "webXML": + // This path is canonical already. + var thisFile = replace( serverProps[ 'webXML' ], '\', '/', 'all' ); + // If the webXML is south of the server's JSON, make it relative for better portability. + if( thisFile contains configPath ) { + thisFile = replaceNoCase( thisFile, configPath, '' ); + } + serverJSON[ 'app' ][ 'webXML' ] = thisFile; break; case "libDirs": serverJSON[ 'app' ][ 'libDirs' ] = serverProps[ 'libDirs' ] @@ -312,9 +336,6 @@ component accessors="true" singleton { } ); break; - case "webXML": - serverJSON[ 'app' ][ 'webXML' ] = serverProps[ prop ]; - break; case "cfengine": serverJSON[ 'app' ][ 'cfengine' ] = serverProps[ prop ]; break; @@ -398,17 +419,31 @@ component accessors="true" singleton { } serverInfo.stopsocket = serverProps.stopsocket ?: serverJSON.stopsocket ?: getRandomPort( serverInfo.host ); + + // relative trayIcon in server.json is resolved relative to the server.json + if( serverJSON.keyExists( 'app' ) && serverJSON.app.keyExists( 'webConfigDir' ) ) { serverJSON.app.webConfigDir = fileSystemUtil.resolvePath( serverJSON.app.webConfigDir, defaultServerConfigFileDirectory ); } + // relative trayIcon in config setting server defaults is resolved relative to the web root + if( len( defaults.app.webConfigDir ?: '' ) ) { defaults.app.webConfigDir = fileSystemUtil.resolvePath( defaults.app.webConfigDir, defaultwebroot ); } serverInfo.webConfigDir = serverProps.webConfigDir ?: serverJSON.app.webConfigDir ?: defaults.app.webConfigDir; - if( !len( serverInfo.webConfigDir ) ) { serverInfo.webConfigDir = getCustomServerFolder( serverInfo ); } + + // relative trayIcon in server.json is resolved relative to the server.json + if( serverJSON.keyExists( 'app' ) && serverJSON.app.keyExists( 'serverConfigDir' ) ) { serverJSON.app.serverConfigDir = fileSystemUtil.resolvePath( serverJSON.app.serverConfigDir, defaultServerConfigFileDirectory ); } + // relative trayIcon in config setting server defaults is resolved relative to the web root + if( len( defaults.app.serverConfigDir ?: '' ) ) { defaults.app.serverConfigDir = fileSystemUtil.resolvePath( defaults.app.serverConfigDir, defaultwebroot ); } serverInfo.serverConfigDir = serverProps.serverConfigDir ?: serverJSON.app.serverConfigDir ?: defaults.app.serverConfigDir; - + + // relative trayIcon in server.json is resolved relative to the server.json + if( serverJSON.keyExists( 'app' ) && serverJSON.app.keyExists( 'webXML' ) ) { serverJSON.app.webXML = fileSystemUtil.resolvePath( serverJSON.app.webXML, defaultServerConfigFileDirectory ); } + // relative trayIcon in config setting server defaults is resolved relative to the web root + if( len( defaults.app.webXML ?: '' ) ) { defaults.app.webXML = fileSystemUtil.resolvePath( defaults.app.webXML, defaultwebroot ); } + serverInfo.webXML = serverProps.webXML ?: serverJSON.app.webXML ?: defaults.app.webXML; + // relative trayIcon in server.json is resolved relative to the server.json if( serverJSON.keyExists( 'trayIcon' ) ) { serverJSON.trayIcon = fileSystemUtil.resolvePath( serverJSON.trayIcon, defaultServerConfigFileDirectory ); } // relative trayIcon in config setting server defaults is resolved relative to the web root if( defaults.keyExists( 'trayIcon' ) && len( defaults.trayIcon ) ) { defaults.trayIcon = fileSystemUtil.resolvePath( defaults.trayIcon, defaultwebroot ); } serverInfo.trayIcon = serverProps.trayIcon ?: serverJSON.trayIcon ?: defaults.trayIcon; - serverInfo.webXML = serverProps.webXML ?: serverJSON.app.webXML ?: defaults.app.webXML; serverInfo.SSLEnable = serverProps.SSLEnable ?: serverJSON.web.SSL.enable ?: defaults.web.SSL.enable; serverInfo.HTTPEnable = serverProps.HTTPEnable ?: serverJSON.web.HTTP.enable ?: defaults.web.HTTP.enable; serverInfo.SSLPort = serverProps.SSLPort ?: serverJSON.web.SSL.port ?: defaults.web.SSL.port; @@ -524,9 +559,9 @@ component accessors="true" singleton { if( serverInfo.WARPath == '' ){ // This will install the engine war to start, possibly downloading it first - var installDetails = serverEngineService.install( cfengine=serverInfo.cfengine, basedirectory=serverInfo.webConfigDir ); + var installDetails = serverEngineService.install( cfengine=serverInfo.cfengine, basedirectory=getCustomServerFolder( serverInfo ), serverInfo=serverInfo ); serverInfo.serverHome = installDetails.installDir; - serverInfo.logdir = installDetails.installDir & "/logs"; + serverInfo.logdir = serverInfo.serverHome & "/logs"; serverInfo.consolelogPath = serverInfo.logdir & '/server.out.txt'; serverInfo.engineName = installDetails.engineName; serverInfo.engineVersion = installDetails.version; @@ -544,7 +579,7 @@ component accessors="true" singleton { if( serverInfo.cfengine contains "lucee" ) { // Detect Lucee 4.x if( installDetails.version.listFirst( '.' ) < 5 ) { - javaagent = "-javaagent:#installDetails.installDir#/WEB-INF/lib/lucee-inst.jar"; + javaagent = "-javaagent:#serverInfo.serverHome#/WEB-INF/lib/lucee-inst.jar"; } else { // Lucee 5+ doesn't need the Java agent javaagent = ""; @@ -552,7 +587,7 @@ component accessors="true" singleton { } // If external Railo server, set the java agent if( serverInfo.cfengine contains "railo" ) { - javaagent = "-javaagent:#installDetails.installDir#/WEB-INF/lib/railo-inst.jar"; + javaagent = "-javaagent:#serverInfo.serverHome#/WEB-INF/lib/railo-inst.jar"; } // The process native name @@ -681,7 +716,14 @@ component accessors="true" singleton { args &= " -war ""#serverInfo.WARPath#"""; // Stand alone server } else { - args &= " -war ""#serverInfo.webroot#"" --web-xml-path ""#installDetails.installDir#/WEB-INF/web.xml"""; + args &= " -war ""#serverInfo.webroot#"""; + } + // Custom web.xml (doesn't work right now) + if ( Len( Trim( serverInfo.webXml ) ) && false ) { + args &= " --web-xml-path ""#serverInfo.webXml#"""; + // Default is in WAR home + } else { + args &= " --web-xml-path ""#serverInfo.serverHome#/WEB-INF/web.xml"""; } if( len( serverInfo.libDirs ) ) { @@ -696,10 +738,6 @@ component accessors="true" singleton { if( serverInfo.SSLEnable && serverInfo.SSLCert != "") { args &= " --ssl-cert ""#serverInfo.SSLCert#"" --ssl-key ""#serverInfo.SSLKey#"" --ssl-keypass ""#serverInfo.SSLKeyPass#"""; } - // Incorporate web-xml to command - if ( Len( Trim( serverInfo.webXml ?: "" ) ) ) { - args &= " --web-xml-path ""#serverInfo.webXml#"""; - } // Incorporate rewrites to command args &= " --urlrewrite-enable #serverInfo.rewritesEnable#"; From c0a4f4f6a319373e2a54c6008b7666a5729c3f4f Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 16 Dec 2016 00:43:10 -0600 Subject: [PATCH 61/87] COMMANDBOX-511 --- src/cfml/system/services/ServerEngineService.cfc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index be84586be..41cc81b69 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -255,6 +255,7 @@ component accessors="true" singleton="true" { } consoleLogger.info( "Exploding WAR/zip archive..."); + directoryCreate( installDetails.installDir, true, true ); zip action="unzip" file="#theArchive#" destination="#installDetails.installDir#" overwrite="true"; // Catch this to gracefully handle where the OS or another program From a8892b41230e2a0439badeaf073ba16449a47918 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 16 Dec 2016 14:06:49 -0600 Subject: [PATCH 62/87] COMMANDBOX-539 --- src/cfml/system/modules_app/system-commands/commands/run.cfc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/run.cfc b/src/cfml/system/modules_app/system-commands/commands/run.cfc index 00c1d1c0c..ef9ae92e6 100644 --- a/src/cfml/system/modules_app/system-commands/commands/run.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/run.cfc @@ -81,7 +81,7 @@ component{ // Output Results if( !isNull( executeResult ) && len( executeResult ) ) { - print.line( executeResult ); + print.Text( executeResult ); } // Output error if( !isNull( executeError ) && len( executeError ) ) { @@ -94,7 +94,7 @@ component{ executeError = replaceNoCase( executeError, 'bash: no job control in this shell', '' ); executeError = trim( executeError ); } - print.redLine( executeError ); + print.redText( executeError ); } } catch (any e) { From 16e58b17924c150026c7dbe752ab5e34b6761742 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 16 Dec 2016 14:42:42 -0600 Subject: [PATCH 63/87] COMMANDBOX-439 --- src/cfml/system/util/FileSystem.cfc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/util/FileSystem.cfc b/src/cfml/system/util/FileSystem.cfc index 301774a1c..fb52e0a7e 100644 --- a/src/cfml/system/util/FileSystem.cfc +++ b/src/cfml/system/util/FileSystem.cfc @@ -38,7 +38,12 @@ component accessors="true" singleton { // This tells us if it's a relative path // Note, at this point we don't actually know if it actually even exists yet - if( !oPath.isAbsolute() ) { + + // If we're on windows and the path starts with / or \ + if( isWindows() && reFind( '^[\\\/]', path ) ) { + // Concat it with the drive root in the base path so "/foo" becomes "C:/foo" (if the basepath is C:/etc) + oPath = createObject( 'java', 'java.io.File' ).init( listFirst( arguments.basePath, '/\' ) & '/' & path ); + } else if( !oPath.isAbsolute() ) { // If it's relative, we assume it's relative to the current working directory and make it absolute oPath = createObject( 'java', 'java.io.File' ).init( arguments.basePath & '/' & path ); } From 4730af170448f98a6be24dfb06742073f8b5c7c3 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 16 Dec 2016 17:08:17 -0600 Subject: [PATCH 64/87] COMMANDBOX-540 --- src/cfml/system/util/Completor.cfc | 37 ++++++++++++----------------- src/cfml/system/util/FileSystem.cfc | 21 ++++++++++++++-- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/cfml/system/util/Completor.cfc b/src/cfml/system/util/Completor.cfc index 7a7fc3165..5380baf59 100644 --- a/src/cfml/system/util/Completor.cfc +++ b/src/cfml/system/util/Completor.cfc @@ -357,23 +357,18 @@ component singleton { * @type.showFiles Whether to hit files as well as directories **/ private function pathCompletion(String startsWith, required candidates, showFiles=true ) { - // This is what we add to relative paths, with the slashes normalized - var relativeRootPath = replace( shell.pwd() & '/', "\", "/", "all" ); - - // Keep track of whether this is a relative path or not. - var isRelative = false; - - // Note, I'm NOT using fileSystemUtil.resolvePath() here because I don't want the - // path canoncalized since that will break my text comparisons. Leave the ../ stuff in - var oPath = createObject( 'java', 'java.io.File' ).init( arguments.startsWith ); - if( !oPath.isAbsolute() ) { - isRelative = true; - // If it's relative, we assume it's relative to the current working directory and make it absolute - arguments.startsWith = relativeRootPath & arguments.startsWith; - } - - // This is out absolute directory as typed by the user + + // keep track of the original here so we can put it back like the user had + var originalStartsWith = replace( arguments.startsWith, "\", "/", "all" ); + // Fully resolve the path. + arguments.startsWith = fileSystemUtil.resolvePath( arguments.startsWith ); startsWith = replace( startsWith, "\", "/", "all" ); + + // make sure dirs are suffixed with a trailing slash or we'll strip it off, thinking it's a partial name + if( ( originalStartsWith == '' || originalStartsWith.endsWith( '/' ) ) && !startsWith.endsWith( '/' ) ) { + startsWith &= '/'; + } + // searchIn strips off partial directories, and has the last complete actual directory for searching. var searchIn = startsWith; // This is the bit at the end that is a partially typed directory or file name @@ -387,7 +382,7 @@ component singleton { partialMatch = replaceNoCase( startsWith, searchIn, '' ); } } - + // Don't even bother if search location doesn't exist if( directoryExists( searchIn ) ) { // Pull a list of paths in there @@ -410,11 +405,9 @@ component singleton { // This is the absolute path that we matched var thisCandidate = searchIn & ( right( searchIn, 1 ) == '/' ? '' : '/' ) & path.name; - // If we started with a relative path... - if( isRelative ) { - // ...strip it back down to what they typed - thisCandidate = replaceNoCase( thisCandidate, relativeRootPath, '' ); - } + // ...strip it back down to what they typed + thisCandidate = replaceNoCase( thisCandidate, startsWith, originalStartsWith ); + // Finally add this candidate into the list candidates.add( thisCandidate & ( path.type == 'dir' ? '/' : '' ) ); } diff --git a/src/cfml/system/util/FileSystem.cfc b/src/cfml/system/util/FileSystem.cfc index fb52e0a7e..73eed4c00 100644 --- a/src/cfml/system/util/FileSystem.cfc +++ b/src/cfml/system/util/FileSystem.cfc @@ -21,6 +21,7 @@ component accessors="true" singleton { function init() { variables.os = createObject( "java", "java.lang.System" ).getProperty( "os.name" ).toLowerCase(); + variables.userHome = createObject( 'java', 'java.lang.System' ).getProperty( 'user.home' ); return this; } @@ -39,13 +40,29 @@ component accessors="true" singleton { // This tells us if it's a relative path // Note, at this point we don't actually know if it actually even exists yet - // If we're on windows and the path starts with / or \ + // If we're on windows and the path starts with / or \ if( isWindows() && reFind( '^[\\\/]', path ) ) { + // Concat it with the drive root in the base path so "/foo" becomes "C:/foo" (if the basepath is C:/etc) - oPath = createObject( 'java', 'java.io.File' ).init( listFirst( arguments.basePath, '/\' ) & '/' & path ); + oPath = createObject( 'java', 'java.io.File' ).init( listFirst( arguments.basePath, '/\' ) & '/' & path ); + + // If path is "~" + // Note, we're supporting this on Windows as well as Linux because it seems useful + } else if( path == '~' ) { + + var userHome = createObject( 'java', 'java.lang.System' ).getProperty( 'user.home' ); + oPath = createObject( 'java', 'java.io.File' ).init( userHome ); + + // If path starts with "~/something" but not "~foo" (a valid folder name) + } else if( reFind( '^~[\\\/]', path ) ) { + + oPath = createObject( 'java', 'java.io.File' ).init( userHome & right( path, len( path ) - 1 ) ); + } else if( !oPath.isAbsolute() ) { + // If it's relative, we assume it's relative to the current working directory and make it absolute oPath = createObject( 'java', 'java.io.File' ).init( arguments.basePath & '/' & path ); + } // This will standardize the name and calculate stuff like ../../ From 38d5beee1dd8bf4c7d778d5ca654369e4515f6fc Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 19 Dec 2016 22:12:04 -0600 Subject: [PATCH 65/87] COMMANDBOX-542 --- src/cfml/system/services/CommandService.cfc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/services/CommandService.cfc b/src/cfml/system/services/CommandService.cfc index e10ff28e9..08e82fbcd 100644 --- a/src/cfml/system/services/CommandService.cfc +++ b/src/cfml/system/services/CommandService.cfc @@ -156,10 +156,6 @@ component accessors="true" singleton { **/ function runCommand( required array commandChain, required string line, string piped ){ - if( structKeyExists( arguments, 'piped' ) ) { - var result = arguments.piped; - } - // If nothing is returned, something bad happened (like an error instatiating the CFC) if( !commandChain.len() ){ return 'Command not run.'; @@ -169,6 +165,12 @@ component accessors="true" singleton { // default behavior is to keep trucking var previousCommandSeparator = ';'; var lastCommandErrored = false; + + if( structKeyExists( arguments, 'piped' ) ) { + var result = arguments.piped; + previousCommandSeparator = '|'; + } + // If piping commands, each one will be an item in the chain. // i.e. forgebox show | grep | more // Would result in three separate, chained commands. From 2c71af74eff97ca8b8f0a54623996fd49d724f4a Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 19 Dec 2016 22:32:42 -0600 Subject: [PATCH 66/87] COMMANDBOX-543 --- src/cfml/system/Shell.cfc | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index be8a6e147..ef8382f22 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -83,6 +83,14 @@ component accessors="true" singleton { required string tempDir, boolean asyncLoad=true ){ + // Possible byte order marks + variables.BOMS = [ + chr( 254 ) & chr( 255 ), + chr( 255 ) & chr( 254 ), + chr( 239 ) & chr( 187 ) & chr( 191 ), + chr( 00 ) & chr( 254 ) & chr( 255 ), + chr( 255 ) & chr( 254 ) & chr( 00 ) + ]; // Version is stored in cli-build.xml. Build number is generated by Ant. // Both are replaced when CommandBox is built. @@ -401,12 +409,19 @@ component accessors="true" singleton { } else { line = variables.reader.readLine(); } - + // If the standard input isn't avilable, bail. This happens // when commands are piped in and we've reached the end of the piped stream if( !isDefined( 'line' ) ) { return false; } + + // Clean BOM from start of text in case something was piped from a file + BOMS.each( function( i ){ + if( line.startsWith( i ) ) { + line = replace( line, i, '' ); + } + } ); // If there's input, try to run it. if( len( trim( line ) ) ) { From 82e720974815688623d62cadba8ba83f3a7ba4e6 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 20 Dec 2016 00:28:45 -0600 Subject: [PATCH 67/87] COMMANDBOX-544 --- src/cfml/system/services/JSONService.cfc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/cfml/system/services/JSONService.cfc b/src/cfml/system/services/JSONService.cfc index 673ddb918..a1a4f4c82 100644 --- a/src/cfml/system/services/JSONService.cfc +++ b/src/cfml/system/services/JSONService.cfc @@ -65,7 +65,7 @@ component accessors="true" singleton { // The target property we're trying to append to var targetProperty = evaluate( fullPropertyName ); // The value we want to append - var complexValue = deserializeJSON( arguments.properties[ prop ] ); + var complexValue = deserializeJSON( propertyValue ); // The target property is not simple, and matches the same data type as the incoming data if( !isSimpleValue( targetProperty ) && ( isArray( targetProperty ) == isArray( complexValue ) ) ) { // Make this idempotent so arrays don't get duplicate values @@ -82,17 +82,23 @@ component accessors="true" singleton { } else { targetProperty.append( complexValue, true ); } - results.append( '#arguments.properties[ prop ]# appended to #prop#' ); + results.append( '#propertyValue# appended to #prop#' ); continue; } } // If any of the ifs above fail, we'll fall back through to this - evaluate( '#fullPropertyName# = deserializeJSON( arguments.properties[ prop ] )' ); + + // Double check if value is really JSON due to Lucee bug + if( listFind( '",{,[', left( propertyValue, 1 ) ) ) { + evaluate( '#fullPropertyName# = deserializeJSON( propertyValue )' ); + } else { + evaluate( '#fullPropertyName# = propertyValue' ); + } } else { - evaluate( '#fullPropertyName# = arguments.properties[ prop ]' ); + evaluate( '#fullPropertyName# = propertyValue' ); } - results.append( 'Set #prop# = #arguments.properties[ prop ]#' ); + results.append( 'Set #prop# = #propertyValue#' ); } return results; } From 835edbd53e2c69fcc277566507a5189315abd24c Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 20 Dec 2016 16:17:24 +0100 Subject: [PATCH 68/87] COMMANDBOX-545 #resolve The `commandbox-home` when used in symbolic link mode fails on mac --- src/cfml/Application.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/Application.cfc b/src/cfml/Application.cfc index 02356f202..75d1e5dab 100644 --- a/src/cfml/Application.cfc +++ b/src/cfml/Application.cfc @@ -15,7 +15,7 @@ component{ // Move everything over to this mapping which is the "root" of our app CFMLRoot = getDirectoryFromPath( getMetadata( this ).path ); this.mappings[ '/commandbox' ] = CFMLRoot; - this.mappings[ '/commandbox-home' ] = expandPath( CFMLRoot & '/../' ); + this.mappings[ '/commandbox-home' ] = createObject( 'java', 'java.lang.System' ).getProperty( 'cfml.cli.home' ); this.mappings[ '/wirebox' ] = CFMLRoot & '/system/wirebox'; function onApplicationStart(){ From e3b025882c41c4567666a6d5d3f4d4ef6cdfe224 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 26 Dec 2016 12:27:43 -0600 Subject: [PATCH 69/87] Fix issues when starting stand-alone CF wars --- src/cfml/system/services/ServerService.cfc | 71 ++++++++++++---------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 62df87662..5f915e689 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -552,6 +552,7 @@ component accessors="true" singleton { CFEngineName = serverinfo.cfengine contains 'lucee' ? 'lucee' : CFEngineName; CFEngineName = serverinfo.cfengine contains 'railo' ? 'railo' : CFEngineName; CFEngineName = serverinfo.cfengine contains 'adobe' ? 'adobe' : CFEngineName; + CFEngineName = serverinfo.warPath contains 'adobe' ? 'adobe' : CFEngineName; var processName = ( serverInfo.name is "" ? "CommandBox" : serverInfo.name ); @@ -592,37 +593,21 @@ component accessors="true" singleton { // The process native name var processName = ( serverInfo.name is "" ? "CommandBox" : serverInfo.name ) & ' [' & listFirst( serverinfo.cfengine, '@' ) & ' ' & installDetails.version & ']'; - - // Find the correct tray icon for this server - if( !len( serverInfo.trayIcon ) ) { - var iconSize = fileSystemUtil.isWindows() ? '-32px' : ''; - if( serverInfo.cfengine contains "lucee" ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-lucee#iconSize#.png'; - } else if( serverInfo.cfengine contains "railo" ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-railo#iconSize#.png'; - } else if( serverInfo.cfengine contains "adobe" ) { - - if( listFirst( installDetails.version, '.' ) == 9 ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf09#iconSize#.png'; - } else if( listFirst( installDetails.version, '.' ) == 10 ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf10#iconSize#.png'; - } else if( listFirst( installDetails.version, '.' ) == 11 ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf11#iconSize#.png'; - } else if( listFirst( installDetails.version, '.' ) == 2016 ) { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf2016#iconSize#.png'; - } else { - serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf2016#iconSize#.png'; - } - - } - } // This is a WAR } else { // If WAR is a file if( fileExists( serverInfo.WARPath ) ){ // It will be extracted into a folder named after the file - serverInfo.serverHome = reReplaceNoCase( serverInfo.WARPath, '(.*)(\.zip|\.war)', '\1' ); + serverInfo.serverHomeDirectory = reReplaceNoCase( serverInfo.WARPath, '(.*)(\.zip|\.war)', '\1' ); + + // Expand the war if it doesn't exist or we're forcing + if( !directoryExists( serverInfo.serverHomeDirectory ) || serverProps.force ?: false ) { + consoleLogger.info( "Exploding WAR archive..."); + directoryCreate( serverInfo.serverHomeDirectory, true, true ); + zip action="unzip" file="#serverInfo.WARPath#" destination="#serverInfo.serverHomeDirectory#" overwrite="true"; + } + // If WAR is a folder } else { // Just use it @@ -632,6 +617,30 @@ component accessors="true" singleton { serverInfo.logdir = getCustomServerFolder( serverInfo ) & "/logs"; serverInfo.consolelogPath = serverInfo.logdir & '/server.out.txt'; } + + // Find the correct tray icon for this server + if( !len( serverInfo.trayIcon ) ) { + var iconSize = fileSystemUtil.isWindows() ? '-32px' : ''; + if( CFEngineName contains "lucee" ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-lucee#iconSize#.png'; + } else if( CFEngineName contains "railo" ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-railo#iconSize#.png'; + } else if( CFEngineName contains "adobe" ) { + + if( listFirst( serverInfo.engineVersion, '.' ) == 9 ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf09#iconSize#.png'; + } else if( listFirst( serverInfo.engineVersion, '.' ) == 10 ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf10#iconSize#.png'; + } else if( listFirst( serverInfo.engineVersion, '.' ) == 11 ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf11#iconSize#.png'; + } else if( listFirst( serverInfo.engineVersion, '.' ) == 2016 ) { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf2016#iconSize#.png'; + } else { + serverInfo.trayIcon = '/commandbox/system/config/server-icons/trayicon-cf2016#iconSize#.png'; + } + + } + } // Default tray icon serverInfo.trayIcon = ( len( serverInfo.trayIcon ) ? serverInfo.trayIcon : '/commandbox/system/config/server-icons/trayicon.png' ); @@ -640,13 +649,13 @@ component accessors="true" singleton { // Set default options for all servers // TODO: Don't overwrite existing options with the same label. - if( serverInfo.cfengine contains "lucee" ) { + if( CFEngineName contains "lucee" ) { serverInfo.trayOptions.prepend( { 'label':'Open Web Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/lucee/admin/web.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/web_settings.png' ) } ); serverInfo.trayOptions.prepend( { 'label':'Open Server Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/lucee/admin/server.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/server_settings.png' ) } ); - } else if( serverInfo.cfengine contains "railo" ) { + } else if( CFEngineName contains "railo" ) { serverInfo.trayOptions.prepend( { 'label':'Open Web Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/railo-context/admin/web.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/web_settings.png' ) } ); serverInfo.trayOptions.prepend( { 'label':'Open Server Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/railo-context/admin/server.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/server_settings.png' ) } ); - } else if( serverInfo.cfengine contains "adobe" ) { + } else if( CFEngineName contains "adobe" ) { serverInfo.trayOptions.prepend( { 'label':'Open Server Admin', 'action':'openbrowser', 'url':'http://${runwar.host}:${runwar.port}/CFIDE/administrator/enter.cfm', 'image' : expandPath('/commandbox/system/config/server-icons/server_settings.png' ) } ); } @@ -699,7 +708,7 @@ component accessors="true" singleton { // The java arguments to execute: Shared server, custom web configs var args = ' #serverInfo.JVMargs# -Xmx#serverInfo.heapSize#m -Xms#serverInfo.heapSize#m' & ' #javaagent# -jar #variables.jarPath#' - & ' --background=#background# --port #serverInfo.port# --host #serverInfo.host# --debug=#serverInfo.debug#' + & ' --background #background# --port #serverInfo.port# --host #serverInfo.host# --debug #serverInfo.debug#' & ' --stop-port #serverInfo.stopsocket# --processname "#processName#" --log-dir "#serverInfo.logDir#"' & ' --open-browser #serverInfo.openbrowser#' & ' --open-url ' & ( serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#' ) @@ -722,8 +731,8 @@ component accessors="true" singleton { if ( Len( Trim( serverInfo.webXml ) ) && false ) { args &= " --web-xml-path ""#serverInfo.webXml#"""; // Default is in WAR home - } else { - args &= " --web-xml-path ""#serverInfo.serverHome#/WEB-INF/web.xml"""; + } else if( serverInfo.WARPath == "" ){ + args &= " --web-xml-path ""#serverInfo.serverHomeDirectory#/WEB-INF/web.xml"""; } if( len( serverInfo.libDirs ) ) { From 066c045f39642f32448ea9eeb66fd261863bb726 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 26 Dec 2016 13:50:35 -0600 Subject: [PATCH 70/87] Fix path --- src/cfml/system/services/ServerService.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 5f915e689..317a39e63 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -732,7 +732,7 @@ component accessors="true" singleton { args &= " --web-xml-path ""#serverInfo.webXml#"""; // Default is in WAR home } else if( serverInfo.WARPath == "" ){ - args &= " --web-xml-path ""#serverInfo.serverHomeDirectory#/WEB-INF/web.xml"""; + args &= " --web-xml-path ""#serverInfo.serverHome#/WEB-INF/web.xml"""; } if( len( serverInfo.libDirs ) ) { From f0b58f6022fd9c961b44a770ef4e50df611bca3e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 26 Dec 2016 18:11:00 -0600 Subject: [PATCH 71/87] OMMANDBOX-546 --- .../server-commands/commands/server/start.cfc | 4 +- .../system/services/ServerEngineService.cfc | 72 ++++++++++++++----- src/cfml/system/services/ServerService.cfc | 41 ++++++++--- src/cfml/system/util/ReaderFactory.cfc | 7 +- 4 files changed, 94 insertions(+), 30 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc index a74f75cb1..591ba37a1 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc @@ -80,6 +80,7 @@ component aliases="start" { * @startTimeout The amount of time in seconds to wait for the server to start (in the background). * @console Start this server in the forground console process and wait until Ctrl-C is pressed to stop it. * @welcomeFiles A comma-delimited list of default files to load when visiting a directory (index.cfm,index.htm,etc) + * @serverHomeDirectory The folder where the CF engine WAR should be extracted **/ function run( @@ -114,7 +115,8 @@ component aliases="start" { String serverConfigFile, Numeric startTimeout, Boolean console, - String welcomeFiles + String welcomeFiles, + String serverHomeDirectory ){ // This is a common mis spelling diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index 41cc81b69..3feacd7af 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -27,20 +27,21 @@ component accessors="true" singleton="true" { * @cfengine CFML Engine name (lucee, adobe, railo) * @baseDirectory base directory for server install * @serverInfo The struct of server settings + * @serverHomeDirectory Override where the server's home with be **/ - public function install( required cfengine, required baseDirectory, required struct serverInfo ) { + public function install( required cfengine, required baseDirectory, required struct serverInfo, required string serverHomeDirectory ) { var version = listLen( cfengine, "@" )>1 ? listLast( cfengine, "@" ) : ""; var engineName = listFirst( cfengine, "@" ); arguments.baseDirectory = !arguments.baseDirectory.endsWith( "/" ) ? arguments.baseDirectory & "/" : arguments.baseDirectory; if( engineName == "adobe" ) { - return installAdobe( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo ); + return installAdobe( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo, serverHomeDirectory=serverHomeDirectory ); } else if (engineName == "railo") { - return installRailo( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo ); + return installRailo( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo, serverHomeDirectory=serverHomeDirectory ); } else if (engineName == "lucee") { - return installLucee( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo ); + return installLucee( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo, serverHomeDirectory=serverHomeDirectory ); } else { - return installEngineArchive( cfengine, arguments.baseDirectory, serverInfo ); + return installEngineArchive( cfengine, arguments.baseDirectory, serverInfo, serverHomeDirectory ); } } @@ -50,9 +51,10 @@ component accessors="true" singleton="true" { * @destination target directory * @version Version number or empty to use default * @serverInfo Struct of server settings + * @serverHomeDirectory Override where the server's home with be **/ - public function installAdobe( required destination, required version, required struct serverInfo ) { - var installDetails = installEngineArchive( 'adobe@#version#', destination, serverInfo ); + public function installAdobe( required destination, required version, required struct serverInfo, required string serverHomeDirectory ) { + var installDetails = installEngineArchive( 'adobe@#version#', destination, serverInfo, serverHomeDirectory ); // set password to "commandbox" // TODO: Just make this changes directly in the WAR files @@ -84,9 +86,10 @@ component accessors="true" singleton="true" { * @destination target directory * @version Version number or empty to use default * @serverInfo struct of server settings + * @serverHomeDirectory Override where the server's home with be **/ - public function installLucee( required destination, required version, required struct serverInfo ) { - var installDetails = installEngineArchive( 'lucee@#version#', destination, serverInfo ); + public function installLucee( required destination, required version, required struct serverInfo, required string serverHomeDirectory ) { + var installDetails = installEngineArchive( 'lucee@#version#', destination, serverInfo, serverHomeDirectory ); if( installDetails.initialInstall ) { configureWebXML( cfengine="lucee", version=installDetails.version, source=serverInfo.webXML, destination=serverInfo.webXML, serverInfo=serverInfo ); @@ -100,9 +103,10 @@ component accessors="true" singleton="true" { * @destination target directory * @version Version number or empty to use default * @serverInfo struct of server settings + * @serverHomeDirectory Override where the server's home with be **/ - public function installRailo( required destination, required version, required struct serverInfo ) { - var installDetails = installEngineArchive( 'railo@#version#', destination, serverInfo ); + public function installRailo( required destination, required version, required struct serverInfo, required string serverHomeDirectory ) { + var installDetails = installEngineArchive( 'railo@#version#', destination, serverInfo, serverHomeDirectory ); if( installDetails.initialInstall ) { configureWebXML( cfengine="railo", version=installDetails.version, source=serverInfo.webXML, destination=serverInfo.webXML, serverInfo=serverInfo ); @@ -120,7 +124,8 @@ component accessors="true" singleton="true" { function installEngineArchive( required string ID, required string destination, - required struct serverInfo + required struct serverInfo, + required string serverHomeDirectory ) { var installDetails = { @@ -173,15 +178,31 @@ component accessors="true" singleton="true" { } } } - installDetails.installDir = destination & engineName & "-" & replace( satisfyingVersion, '+', '.', 'all' ); + // Overriding server home which is where the exploded war lives + if( len( arguments.serverHomeDirectory ) ) { + installDetails.installDir = arguments.serverHomeDirectory; + // Default is engine-version folder in base dir + } else { + installDetails.installDir = destination & engineName & "-" & replace( satisfyingVersion, '+', '.', 'all' ); + } installDetails.version = satisfyingVersion; + + var thisEngineTag = installDetails.engineName & '@' & installDetails.version; } else { // For all other endpoints, create a predictable folder based on the endpoint ID. // If the file that the endpoint points to changes, you'll have to forget the server to pick up changes. // The alternative is re-downloading the engine EVERY. SINGLE. TIME. - installDetails.installDir = destination & engineName; + // Overriding server home which is where the exploded war lives + if( len( arguments.serverHomeDirectory ) ) { + installDetails.installDir = arguments.serverHomeDirectory; + // Default is engine-version folder in base dir + } else { + installDetails.installDir = destination & engineName; + } + + var thisEngineTag = arguments.ID; } @@ -208,10 +229,21 @@ component accessors="true" singleton="true" { serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, installDetails.installDir, '' ); } + var engineTagFile = installDetails.installDir & '/.engineInstall'; // Check to see if this WAR has already been exploded - if( fileExists( installDetails.installDir & '/WEB-INF/web.xml' ) ) { + if( fileExists( engineTagFile ) ) { + + // Check and see if another version of this engine has already been started in the server home. + var previousEngineTag = fileRead( engineTagFile ); + if( previousEngineTag != thisEngineTag ) { + consoleLogger.warn( "You've asked for the engine [#thisEngineTag#] to be started," ); + consoleLogger.warn( "but this server home already has [#previousEngineTag#] deployed to it!" ); + consoleLogger.warn( "In orer to get the new version, you need to run 'server forget' on this server and start it again." ); + } + consoleLogger.info( "WAR/zip archive already installed."); + return installDetails; } @@ -232,6 +264,9 @@ component accessors="true" singleton="true" { directoryCreate( installDetails.installDir & '/WEB-INF', true, true ); directoryCopy( '/commandbox-home/lib', thislib, false, '*.jar' ); fileCopy( '/commandbox/system/config/web.xml', thisWebinf & '/web.xml'); + + // Mark this WAR as being exploded already + fileWrite( engineTagFile, thisEngineTag ); return installDetails; } @@ -239,7 +274,7 @@ component accessors="true" singleton="true" { if( !packageService.installPackage( ID=arguments.ID, directory=thisTempDir, save=false ) ) { throw( message='Server not installed.', type="commandException"); } - + // Look for a war or zip archive inside the package var theArchive = ''; for( var thisFile in directoryList( thisTempDir ) ) { @@ -256,7 +291,10 @@ component accessors="true" singleton="true" { consoleLogger.info( "Exploding WAR/zip archive..."); directoryCreate( installDetails.installDir, true, true ); - zip action="unzip" file="#theArchive#" destination="#installDetails.installDir#" overwrite="true"; + zip action="unzip" file="#theArchive#" destination="#installDetails.installDir#" overwrite="false"; + + // Mark this WAR as being exploded already + fileWrite( engineTagFile, thisEngineTag ); // Catch this to gracefully handle where the OS or another program // has the folder locked. diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 317a39e63..aae1c518c 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -154,7 +154,8 @@ component accessors="true" singleton { webXML : d.app.webXML ?: '', standalone : d.app.standalone ?: false, WARPath : d.app.WARPath ?: "", - cfengine : d.app.cfengine ?: "" + cfengine : d.app.cfengine ?: "", + serverHomeDirectory : d.app.serverHomeDirectory ?: "" }, runwar : { args : d.runwar.args ?: '' @@ -181,6 +182,9 @@ component accessors="true" singleton { if( !isNull( serverProps.WARPath ) ) { serverProps.WARPath = fileSystemUtil.resolvePath( serverProps.WARPath ); } + if( !isNull( serverProps.serverHomeDirectory ) ) { + serverProps.serverHomeDirectory = fileSystemUtil.resolvePath( serverProps.serverHomeDirectory ); + } if( !isNull( serverProps.trayIcon ) ) { serverProps.trayIcon = fileSystemUtil.resolvePath( serverProps.trayIcon ); } @@ -348,6 +352,15 @@ component accessors="true" singleton { } serverJSON[ 'app' ][ 'WARPath' ] = thisFile; break; + case "serverHomeDirectory": + // This path is canonical already. + var thisDirectory = replace( serverProps[ 'serverHomeDirectory' ], '\', '/', 'all' ) & '/'; + // If the webConfigDir is south of the server's JSON, make it relative for better portability. + if( thisDirectory contains configPath ) { + thisDirectory = replaceNoCase( thisDirectory, configPath, '' ); + } + serverJSON[ 'app' ][ 'serverHomeDirectory' ] = thisDirectory; + break; case "HTTPEnable": serverJSON[ 'web' ][ 'HTTP' ][ 'enable' ] = serverProps[ prop ]; break; @@ -521,6 +534,11 @@ component accessors="true" singleton { if( isDefined( 'serverJSON.app.WARPath' ) && len( serverJSON.app.WARPath ) ) { serverJSON.app.WARPath = fileSystemUtil.resolvePath( serverJSON.app.WARPath, defaultServerConfigFileDirectory ); } if( isDefined( 'defaults.app.WARPath' ) && len( defaults.app.WARPath ) ) { defaults.app.WARPath = fileSystemUtil.resolvePath( defaults.app.WARPath, defaultwebroot ); } serverInfo.WARPath = serverProps.WARPath ?: serverJSON.app.WARPath ?: defaults.app.WARPath; + + // relative rewrite config path in server.json is resolved relative to the server.json + if( isDefined( 'serverJSON.app.serverHomeDirectory' ) && len( serverJSON.app.serverHomeDirectory ) ) { serverJSON.app.serverHomeDirectory = fileSystemUtil.resolvePath( serverJSON.app.serverHomeDirectory, defaultServerConfigFileDirectory ); } + if( isDefined( 'defaults.app.serverHomeDirectory' ) && len( defaults.app.serverHomeDirectory ) ) { defaults.app.serverHomeDirectory = fileSystemUtil.resolvePath( defaults.app.serverHomeDirectory, defaultwebroot ); } + serverInfo.serverHomeDirectory = serverProps.serverHomeDirectory ?: serverJSON.app.serverHomeDirectory ?: defaults.app.serverHomeDirectory; // These are already hammered out above, so no need to go through all the defaults. serverInfo.serverConfigFile = defaultServerConfigFile; @@ -547,6 +565,9 @@ component accessors="true" singleton { // Default java agent for embedded Lucee engine var javaagent = serverinfo.cfengine contains 'lucee' ? '-javaagent:#libdir#/lucee-inst.jar' : ''; + // Regardless of a custom server home, this is still used for various temp files and logs + directoryCreate( getCustomServerFolder( serverInfo ), true, true ); + // Not sure what Runwar does with this, but it wants to know what CFEngine we're starting (if we know) var CFEngineName = ''; CFEngineName = serverinfo.cfengine contains 'lucee' ? 'lucee' : CFEngineName; @@ -560,9 +581,9 @@ component accessors="true" singleton { if( serverInfo.WARPath == '' ){ // This will install the engine war to start, possibly downloading it first - var installDetails = serverEngineService.install( cfengine=serverInfo.cfengine, basedirectory=getCustomServerFolder( serverInfo ), serverInfo=serverInfo ); - serverInfo.serverHome = installDetails.installDir; - serverInfo.logdir = serverInfo.serverHome & "/logs"; + var installDetails = serverEngineService.install( cfengine=serverInfo.cfengine, basedirectory=getCustomServerFolder( serverInfo ), serverInfo=serverInfo, serverHomeDirectory=serverInfo.serverHomeDirectory ); + serverInfo.serverHomeDirectory = installDetails.installDir; + serverInfo.logdir = serverInfo.serverHomeDirectory & "/logs"; serverInfo.consolelogPath = serverInfo.logdir & '/server.out.txt'; serverInfo.engineName = installDetails.engineName; serverInfo.engineVersion = installDetails.version; @@ -580,7 +601,7 @@ component accessors="true" singleton { if( serverInfo.cfengine contains "lucee" ) { // Detect Lucee 4.x if( installDetails.version.listFirst( '.' ) < 5 ) { - javaagent = "-javaagent:#serverInfo.serverHome#/WEB-INF/lib/lucee-inst.jar"; + javaagent = "-javaagent:#serverInfo.serverHomeDirectory#/WEB-INF/lib/lucee-inst.jar"; } else { // Lucee 5+ doesn't need the Java agent javaagent = ""; @@ -588,7 +609,7 @@ component accessors="true" singleton { } // If external Railo server, set the java agent if( serverInfo.cfengine contains "railo" ) { - javaagent = "-javaagent:#serverInfo.serverHome#/WEB-INF/lib/railo-inst.jar"; + javaagent = "-javaagent:#serverInfo.serverHomeDirectory#/WEB-INF/lib/railo-inst.jar"; } // The process native name @@ -611,7 +632,7 @@ component accessors="true" singleton { // If WAR is a folder } else { // Just use it - serverInfo.serverHome = serverInfo.WARPath; + serverInfo.serverHomeDirectory = serverInfo.WARPath; } // Create a custom server folder to house the logs serverInfo.logdir = getCustomServerFolder( serverInfo ) & "/logs"; @@ -731,8 +752,8 @@ component accessors="true" singleton { if ( Len( Trim( serverInfo.webXml ) ) && false ) { args &= " --web-xml-path ""#serverInfo.webXml#"""; // Default is in WAR home - } else if( serverInfo.WARPath == "" ){ - args &= " --web-xml-path ""#serverInfo.serverHome#/WEB-INF/web.xml"""; + } else { + args &= " --web-xml-path ""#serverInfo.serverHomeDirectory#/WEB-INF/web.xml"""; } if( len( serverInfo.libDirs ) ) { @@ -1346,7 +1367,7 @@ component accessors="true" singleton { libDirs : "", webConfigDir : "", serverConfigDir : "", - serverHome : "", + serverHomeDirectory : "", webroot : "", webXML : "", HTTPEnable : true, diff --git a/src/cfml/system/util/ReaderFactory.cfc b/src/cfml/system/util/ReaderFactory.cfc index b267736ef..723643595 100644 --- a/src/cfml/system/util/ReaderFactory.cfc +++ b/src/cfml/system/util/ReaderFactory.cfc @@ -38,12 +38,15 @@ component singleton{ reader = createObject( "java", "jline.console.ConsoleReader" ).init( arguments.inStream, arguments.outputStream ); } - // Let JLine handle Cntrl-C, and throw a UserInterruptException (instead of dying) - reader.setHandleUserInterrupt( true ); + // Let JLine handle Cntrl-C, and throw a UserInterruptException (instead of dying) + reader.setHandleUserInterrupt( true ); // This turns off special stuff that JLine2 looks for related to exclamation marks reader.setExpandEvents( false ); + // Turn off option to add space to end of completion that messes up stuff like path completion. + reader.getCompletionHandler().setPrintSpaceAfterFullCompletion( false ); + // Create our completer and set it in the console reader var jCompletor = createDynamicProxy( completor , [ 'jline.console.completer.Completer' ] ); reader.addCompleter( jCompletor ); From d242e1f1e6043f0f6efd97bd3c0664668702eb95 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 26 Dec 2016 21:35:14 -0600 Subject: [PATCH 72/87] COMMANDBOX-547 --- .../package-commands/commands/package/set.cfc | 2 +- .../server-commands/commands/server/set.cfc | 2 +- .../system-commands/commands/config/set.cfc | 2 +- src/cfml/system/services/ConfigService.cfc | 9 +- src/cfml/system/services/PackageService.cfc | 6 +- src/cfml/system/services/ServerService.cfc | 174 +++++++++--------- src/cfml/system/util/Completor.cfc | 8 +- 7 files changed, 109 insertions(+), 94 deletions(-) diff --git a/src/cfml/system/modules_app/package-commands/commands/package/set.cfc b/src/cfml/system/modules_app/package-commands/commands/package/set.cfc index c8acdde45..62ee63dfe 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/set.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/set.cfc @@ -81,6 +81,6 @@ component { function completeProperty() { var directory = fileSystemUtil.resolvePath( '' ); // all=true will cause "package set" to prompt all possible box.json properties - return packageService.completeProperty( directory, true ); + return packageService.completeProperty( directory, true, true ); } } \ No newline at end of file diff --git a/src/cfml/system/modules_app/server-commands/commands/server/set.cfc b/src/cfml/system/modules_app/server-commands/commands/server/set.cfc index ff8f32ee9..b133c746b 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/set.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/set.cfc @@ -84,6 +84,6 @@ component { // Dynamic completion for property name based on contents of server.json function completeProperty() { // all=true will cause "server set" to prompt all possible server.json properties - return ServerService.completeProperty( getCWD(), true ); + return ServerService.completeProperty( getCWD(), true, true ); } } \ No newline at end of file diff --git a/src/cfml/system/modules_app/system-commands/commands/config/set.cfc b/src/cfml/system/modules_app/system-commands/commands/config/set.cfc index 701a105e7..4b7805e51 100644 --- a/src/cfml/system/modules_app/system-commands/commands/config/set.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/config/set.cfc @@ -72,6 +72,6 @@ component { // Dynamic completion for property name based on contents of box.json function completeProperty() { // all=true will cause "config set" to prompt all possible commandbox.json settings - return ConfigService.completeProperty( true ); + return ConfigService.completeProperty( true, true ); } } \ No newline at end of file diff --git a/src/cfml/system/services/ConfigService.cfc b/src/cfml/system/services/ConfigService.cfc index bc22e193d..812c0b1d0 100644 --- a/src/cfml/system/services/ConfigService.cfc +++ b/src/cfml/system/services/ConfigService.cfc @@ -156,9 +156,10 @@ component accessors="true" singleton { /** * Dynamic completion for property name based on contents of commandbox.json - * @all.hint Pass false to ONLY suggest existing setting names. True will suggest all possible settings. + * @all Pass false to ONLY suggest existing setting names. True will suggest all possible settings. + * @asSet Pass true to add = to the end of the options */ - function completeProperty( all=false ) { + function completeProperty( all=false, asSet=false ) { // Get all config settings currently set var props = JSONService.addProp( [], '', '', getConfigSettings() ); @@ -167,7 +168,9 @@ component accessors="true" singleton { // ... Then add them in props.append( getPossibleConfigSettings(), true ); } - + if( asSet ) { + props = props.map( function( i ){ return i &= '='; } ); + } return props; } } \ No newline at end of file diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index fee46092d..ca43d1b74 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -1031,8 +1031,9 @@ component accessors="true" singleton { * Dynamic completion for property name based on contents of box.json * @directory The package root * @all Pass false to ONLY suggest existing property names. True will suggest all possible box.json properties. + * @asSet Pass true to add = to the end of the options */ - function completeProperty( required directory, all=false ) { + function completeProperty( required directory, all=false, asSet=false ) { var props = []; // Check and see if box.json exists @@ -1044,6 +1045,9 @@ component accessors="true" singleton { } props = JSONService.addProp( props, '', '', boxJSON ); } + if( asSet ) { + props = props.map( function( i ){ return i &= '='; } ); + } return props; } diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index aae1c518c..d7c616a02 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -109,56 +109,56 @@ component accessors="true" singleton { var d = ConfigService.getSetting( 'server.defaults', {} ); return { - name : d.name ?: '', - openBrowser : d.openBrowser ?: true, - startTimeout : 240, - stopsocket : d.stopsocket ?: 0, - debug : d.debug ?: false, - trayicon : d.trayicon ?: '', + 'name' : d.name ?: '', + 'openBrowser' : d.openBrowser ?: true, + 'startTimeout' : 240, + 'stopsocket' : d.stopsocket ?: 0, + 'debug' : d.debug ?: false, + 'trayicon' : d.trayicon ?: '', // Duplicate so onServerStart interceptors don't actually change config settings via refernce. - trayOptions : duplicate( d.trayOptions ?: [] ), - jvm : { - heapSize : d.jvm.heapSize ?: 512, - args : d.jvm.args ?: '' + 'trayOptions' : duplicate( d.trayOptions ?: [] ), + 'jvm' : { + 'heapSize' : d.jvm.heapSize ?: 512, + 'args' : d.jvm.args ?: '' }, - web : { - host : d.web.host ?: '127.0.0.1', - directoryBrowsing : d.web.directoryBrowsing ?: true, - webroot : d.web.webroot ?: '', + 'web' : { + 'host' : d.web.host ?: '127.0.0.1', + 'directoryBrowsing' : d.web.directoryBrowsing ?: true, + 'webroot' : d.web.webroot ?: '', // Duplicate so onServerStart interceptors don't actually change config settings via refernce. - aliases : duplicate( d.web.aliases ?: {} ), + 'aliases' : duplicate( d.web.aliases ?: {} ), // Duplicate so onServerStart interceptors don't actually change config settings via refernce. - errorPages : duplicate( d.web.errorPages ?: {} ), - welcomeFiles : d.web.welcomeFiles ?: '', - http : { - port : d.web.http.port ?: 0, - enable : d.web.http.enable ?: true + 'errorPages' : duplicate( d.web.errorPages ?: {} ), + 'welcomeFiles' : d.web.welcomeFiles ?: '', + 'http' : { + 'port' : d.web.http.port ?: 0, + 'enable' : d.web.http.enable ?: true }, - ssl : { - enable : d.web.ssl.enable ?: false, - port : d.web.ssl.port ?: 1443, - cert : d.web.ssl.cert ?: '', - key : d.web.ssl.key ?: '', - keyPass : d.web.ssl.keyPass ?: '' + 'ssl' : { + 'enable' : d.web.ssl.enable ?: false, + 'port' : d.web.ssl.port ?: 1443, + 'cert' : d.web.ssl.cert ?: '', + 'key' : d.web.ssl.key ?: '', + 'keyPass' : d.web.ssl.keyPass ?: '' }, - rewrites : { - enable : d.web.rewrites.enable ?: false, - config : d.web.rewrites.config ?: variables.rewritesDefaultConfig + 'rewrites' : { + 'enable' : d.web.rewrites.enable ?: false, + 'config' : d.web.rewrites.config ?: variables.rewritesDefaultConfig } }, - app : { - logDir : d.app.logDir ?: '', - libDirs : d.app.libDirs ?: '', - webConfigDir : d.app.webConfigDir ?: '', - serverConfigDir : d.app.serverConfigDir ?: '', - webXML : d.app.webXML ?: '', - standalone : d.app.standalone ?: false, - WARPath : d.app.WARPath ?: "", - cfengine : d.app.cfengine ?: "", - serverHomeDirectory : d.app.serverHomeDirectory ?: "" + 'app' : { + 'logDir' : d.app.logDir ?: '', + 'libDirs' : d.app.libDirs ?: '', + 'webConfigDir' : d.app.webConfigDir ?: '', + 'serverConfigDir' : d.app.serverConfigDir ?: '', + 'webXML' : d.app.webXML ?: '', + 'standalone' : d.app.standalone ?: false, + 'WARPath' : d.app.WARPath ?: "", + 'cfengine' : d.app.cfengine ?: "", + 'serverHomeDirectory' : d.app.serverHomeDirectory ?: "" }, - runwar : { - args : d.runwar.args ?: '' + 'runwar' : { + 'args' : d.runwar.args ?: '' } }; } @@ -583,6 +583,8 @@ component accessors="true" singleton { // This will install the engine war to start, possibly downloading it first var installDetails = serverEngineService.install( cfengine=serverInfo.cfengine, basedirectory=getCustomServerFolder( serverInfo ), serverInfo=serverInfo, serverHomeDirectory=serverInfo.serverHomeDirectory ); serverInfo.serverHomeDirectory = installDetails.installDir; + // TODO: As of 3.5 this is for backwards compat. Remove in later version + serverInfo.serverHome = installDetails.installDir; serverInfo.logdir = serverInfo.serverHomeDirectory & "/logs"; serverInfo.consolelogPath = serverInfo.logdir & '/server.out.txt'; serverInfo.engineName = installDetails.engineName; @@ -1349,47 +1351,47 @@ component accessors="true" singleton { */ struct function newServerInfoStruct(){ return { - id : "", - port : 0, - host : "127.0.0.1", - stopsocket : 0, - debug : false, - status : "stopped", - statusInfo : { - result : "", - arguments : "", - command : "" + 'id' : "", + 'port' : 0, + 'host' : "127.0.0.1", + 'stopSocket' : 0, + 'debug' : false, + 'status' : "stopped", + 'statusInfo' : { + 'result' : "", + 'arguments' : "", + 'command' : "" }, - name : "", - logDir : "", - consolelogPath : "", - trayicon : "", - libDirs : "", - webConfigDir : "", - serverConfigDir : "", - serverHomeDirectory : "", - webroot : "", - webXML : "", - HTTPEnable : true, - SSLEnable : false, - SSLPort : 1443, - SSLCert : "", - SSLKey : "", - SSLKeyPass : "", - rewritesEnable : false, - rewritesConfig : "", - heapSize : 512, - directoryBrowsing : true, - JVMargs : "", - runwarArgs : "", - cfengine : "", - engineName : "", - engineVersion : "", - WARPath : "", - serverConfigFile : "", - aliases : {}, - errorPages : {}, - trayOptions : {} + 'name' : "", + 'logDir' : "", + 'consolelogPath' : "", + 'trayicon' : "", + 'libDirs' : "", + 'webConfigDir' : "", + 'serverConfigDir' : "", + 'serverHomeDirectory' : "", + 'webroot' : "", + 'webXML' : "", + 'HTTPEnable' : true, + 'SSLEnable' : false, + 'SSLPort' : 1443, + 'SSLCert' : "", + 'SSLKey' : "", + 'SSLKeyPass' : "", + 'rewritesEnable' : false, + 'rewritesConfig' : "", + 'heapSize' : 512, + 'directoryBrowsing' : true, + 'JVMargs' : "", + 'runwarArgs' : "", + 'cfengine' : "", + 'engineName' : "", + 'engineVersion' : "", + 'WARPath' : "", + 'serverConfigFile' : "", + 'aliases' : {}, + 'errorPages' : {}, + 'trayOptions' : {} }; } @@ -1422,10 +1424,11 @@ component accessors="true" singleton { /** * Dynamic completion for property name based on contents of server.json - * @directory.hint web root - * @all.hint Pass false to ONLY suggest existing setting names. True will suggest all possible settings. + * @directory web root + * @all Pass false to ONLY suggest existing setting names. True will suggest all possible settings. + * @asSet Pass true to add = to the end of the options */ - function completeProperty( required directory, all=false ) { + function completeProperty( required directory, all=false, asSet=false ) { // Get all config settings currently set var props = JSONService.addProp( [], '', '', readServerJSON( arguments.directory & '/server.json' ) ); @@ -1444,6 +1447,9 @@ component accessors="true" singleton { } } ); } + if( asSet ) { + props = props.map( function( i ){ return i &= '='; } ); + } return props; } diff --git a/src/cfml/system/util/Completor.cfc b/src/cfml/system/util/Completor.cfc index 5380baf59..b9247cf2b 100644 --- a/src/cfml/system/util/Completor.cfc +++ b/src/cfml/system/util/Completor.cfc @@ -424,10 +424,12 @@ component singleton { * @candidates.hint tree to populate with completion candidates **/ private function addCandidateIfMatch( required match, required startsWith, required candidates ) { - match = lcase( match ); startsWith = lcase( startsWith ); - if( match.startsWith( startsWith ) || len( startsWith ) == 0 ) { - candidates.add( match & ' ' ); + if( lcase( match ).startsWith( startsWith ) || len( startsWith ) == 0 ) { + if( !match.endsWith( '=' ) ) { + match &= ' '; + } + candidates.add( match ); } } From 44f53b78a3aadf687ad81c2821d21af435816832 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 26 Dec 2016 23:00:13 -0600 Subject: [PATCH 73/87] COMMANDBOX-546 --- src/cfml/system/services/ServerService.cfc | 46 +++++++++++----------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index d7c616a02..a4af61b66 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1076,34 +1076,32 @@ component accessors="true" singleton { if( !arguments.all ){ var servers = getServers(); var serverdir = getCustomServerFolder( arguments.serverInfo ); - var defaultServerJSONPath = arguments.serverInfo.webroot & '/server.json'; - var serverJSONPath = arguments.serverInfo.webroot & '/server-#arguments.serverInfo.name#.json'; - - // try to delete from config first - structDelete( servers, arguments.serverInfo.id ); - setServers( servers ); - // try to delete server - if( directoryExists( serverDir ) ){ - // Catch this to gracefully handle where the OS or another program - // has the folder locked. - try { + + // Catch this to gracefully handle where the OS or another program + // has the folder locked. + try { + + // try to delete interal server dir server + if( directoryExists( serverDir ) ){ directoryDelete( serverdir, true ); - } catch( any e ) { - consoleLogger.error( '#e.message##chr(10)#Did you leave the server running? ' ); - logger.error( '#e.message# #e.detail#' , e.stackTrace ); } + + // Server home may be custom, so delete it as well + if( len( serverInfo.serverHomeDirectory ) && directoryExists( serverInfo.serverHomeDirectory ) ){ + directoryDelete( serverInfo.serverHomeDirectory, true ); + } + + + } catch( any e ) { + consoleLogger.error( '#e.message##chr(10)#Did you leave the server running? ' ); + logger.error( '#e.message# #e.detail#' , e.stackTrace ); + return ''; } - // Delete server.json if it exists - if( fileExists( serverJSONPath ) ) { - fileDelete( serverJSONPath ); - // return now so we don't wipe out the original server.json file too in the next - // if statement! (because this was a "server-#name#.json" file) - return "Poof! Wiped out server " & serverInfo.name; - } - if( fileExists( defaultServerJSONPath ) ) { - fileDelete( defaultServerJSONPath ); - } + // try to delete from config first + structDelete( servers, arguments.serverInfo.id ); + setServers( servers ); + // return message return "Poof! Wiped out server " & serverInfo.name; } else { From aeaeb97e1cedb5b48845181dc2211537df289187 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 26 Dec 2016 23:01:21 -0600 Subject: [PATCH 74/87] Bump for 3.5.0-rc release --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index ae8579b30..d04e60076 100644 --- a/build/build.xml +++ b/build/build.xml @@ -15,7 +15,7 @@ External Dependencies: - + From ec4d9dda02b7637a74ebd70b7192b08481c6be64 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 27 Dec 2016 01:26:44 -0600 Subject: [PATCH 75/87] Clean up runwar chatter from console output --- src/cfml/system/services/ServerService.cfc | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index a4af61b66..a467c2575 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -821,18 +821,27 @@ component accessors="true" singleton { var startOutput = createObject( 'java', 'java.lang.StringBuilder' ).init(); var inputStream = process.getInputStream(); + var inputStreamReader = createObject( 'java', 'java.io.InputStreamReader' ).init( inputStream ); + var bufferedReader = createObject( 'java', 'java.io.BufferedReader' ).init( inputStreamReader ); var print = wirebox.getInstance( "PrintBuffer" ); - while( ( var char = inputStream.read() ) > -1 ){ + var line = bufferedReader.readLine(); + while( !isNull( line ) ){ + // Clean log4j cruft from line + line = reReplaceNoCase( line, '^.* (INFO|DEBUG|ERROR|WARN) RunwarLogger processoutput: ', '' ); + line = reReplaceNoCase( line, '^.* (INFO|DEBUG|ERROR|WARN) RunwarLogger lib: ', 'Runwar: ' ); + line = reReplaceNoCase( line, '^.* (INFO|DEBUG|ERROR|WARN) RunwarLogger ', 'Runwar: ' ); + // Build up our output - startOutput.append( chr( char ) ); + startOutput.append( line & chr( 13 ) & chr( 10 ) ); // output it if we're being interactive if( attributes.interactiveStart ) { print - .text( chr( char ) ) + .line( line ) .toConsole(); } + line = bufferedReader.readLine(); } // End of inputStream // When we require Java 8 for CommandBox, we can pass a timeout to waitFor(). @@ -849,8 +858,8 @@ component accessors="true" singleton { serverInfo.status="unknown"; } finally { // Make sure we always close the file or the process will never quit! - if( isDefined( 'inputStream' ) ) { - inputStream.close(); + if( isDefined( 'bufferedReader' ) ) { + bufferedReader.close(); } serverInfo.statusInfo.result = startOutput.toString(); setServerInfo( serverInfo ); From 0b8e522eca82ea59c83ca111ac4fb50c63505e93 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Tue, 27 Dec 2016 15:02:03 -0500 Subject: [PATCH 76/87] remove manual write of cfadmin password to prevent overwriting the custom config - it is already set in the packaged WARs (#117) --- src/cfml/system/services/ServerEngineService.cfc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index 3feacd7af..51f5ad876 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -56,16 +56,13 @@ component accessors="true" singleton="true" { public function installAdobe( required destination, required version, required struct serverInfo, required string serverHomeDirectory ) { var installDetails = installEngineArchive( 'adobe@#version#', destination, serverInfo, serverHomeDirectory ); - // set password to "commandbox" - // TODO: Just make this changes directly in the WAR files - fileWrite( installDetails.installDir & "/WEB-INF/cfusion/lib/password.properties", "rdspassword=#cr#password=commandbox#cr#encrypted=false" ); // set flex log dir to prevent WEB-INF/cfform being created in project dir if (fileExists(installDetails.installDir & "/WEB-INF/cfform/flex-config.xml")) { var flexConfig = fileRead(installDetails.installDir & "/WEB-INF/cfform/flex-config.xml"); if( installDetails.initialInstall ) { flexConfig = replace(flexConfig, "/WEB-INF/", installDetails.installDir & "/WEB-INF/","all"); - - fileWrite(installDetails.installDir & "/WEB-INF/cfform/flex-config.xml", flexConfig); + fileWrite(installDetails.installDir & "/WEB-INF/cfform/flex-config.xml", flexConfig); } else { // TODO: Remove this ELSE block in a future revision. // This will fix the flex-config.xml files that have been corrupted because we weren't checking initialInstall, above. From 680a463b222472a5ba027c4b6da76664fe29ce78 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 28 Dec 2016 09:56:34 -0600 Subject: [PATCH 77/87] Bad comments --- src/cfml/system/services/ServerService.cfc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index a467c2575..6e5786180 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -915,10 +915,11 @@ component accessors="true" singleton { * * @returns a struct containing * - defaultName - * - defaults + * - defaultwebroot * - defaultServerConfigFile * - serverJSON - * - serverInfo + * - serverInfo + * - serverIsNew */ function resolveServerDetails( required struct serverProps From fa4593eef21daf4f8e7c22527d51dec603c247a7 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 28 Dec 2016 23:13:39 -0600 Subject: [PATCH 78/87] COMMANDBOX-549 --- src/cfml/system/Bootstrap.cfm | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/cfml/system/Bootstrap.cfm b/src/cfml/system/Bootstrap.cfm index efd187085..a567e5bb4 100644 --- a/src/cfml/system/Bootstrap.cfm +++ b/src/cfml/system/Bootstrap.cfm @@ -13,17 +13,18 @@ This file will stay running the entire time the shell is open - - _____ _ ____ - / ____| | | _ \ -| | ___ _ __ ___ _ __ ___ __ _ _ __ __| | |_) | _____ __ -| | / _ \| '_ ` _ \| '_ ` _ \ / _` | '_ \ / _` | _ < / _ \ \/ / -| |___| (_) | | | | | | | | | | | (_| | | | | (_| | |_) | (_) > < - \_____\___/|_| |_| |_|_| |_| |_|\__,_|_| |_|\__,_|____/ \___/_/\_\ v@@version@@ +#chr( 27 )#[32m#chr( 27 )#[40m#chr( 27 )#[1m + _____ _ ____ + / ____| | | _ \ + | | ___ _ __ ___ _ __ ___ __ _ _ __ __| | |_) | _____ __ + | | / _ \| '_ ` _ \| '_ ` _ \ / _` | '_ \ / _` | _ < / _ \ \/ / + | |___| (_) | | | | | | | | | | | (_| | | | | (_| | |_) | (_) > < + \_____\___/|_| |_| |_|_| |_| |_|\__,_|_| |_|\__,_|____/ \___/_/\_\ #chr( 27 )#[0m v@@version@@ -Welcome to CommandBox! -Type "help" for help, or "help [command]" to be more specific. - +#chr( 27 )#[1mWelcome to CommandBox! +Type "help" for help, or "help [command]" to be more specific.#chr( 27 )#[0m + + system = createObject( "java", "java.lang.System" ); args = system.getProperty( "cfml.cli.arguments" ); @@ -105,7 +106,7 @@ Type "help" for help, or "help [command]" to be more specific. if( !silent ) { // Output the welcome banner - systemOutput( replace( interceptData.banner, '@@version@@', shell.getVersion() ) ); + shell.printString( replace( interceptData.banner, '@@version@@', shell.getVersion() ) ); } // Running the "reload" command will enter this while loop once From 3bcc28f85c7c2b1aba0c4513ff2e0a444ee9c2a4 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 29 Dec 2016 21:19:11 -0600 Subject: [PATCH 79/87] CommandBox-549 --- src/cfml/system/Bootstrap.cfm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/Bootstrap.cfm b/src/cfml/system/Bootstrap.cfm index a567e5bb4..c0784c7e1 100644 --- a/src/cfml/system/Bootstrap.cfm +++ b/src/cfml/system/Bootstrap.cfm @@ -137,7 +137,7 @@ Type "help" for help, or "help [command]" to be more specific.#chr( 27 )#[0m if( clearScreen ){ // Output the welcome banner - systemOutput( replace( interceptData.banner, '@@version@@', shell.getVersion() ) ); + shell.printString( replace( interceptData.banner, '@@version@@', shell.getVersion() ) ); } } From 079f92fb6186a959e5b45ee70ed6f0c7b272a73e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 29 Dec 2016 22:59:36 -0600 Subject: [PATCH 80/87] COMMANDBOX-550 --- src/cfml/system/services/ServerService.cfc | 35 +++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 6e5786180..f9162c443 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -40,6 +40,7 @@ component accessors="true" singleton { property name='consoleLogger' inject='logbox:logger:console'; property name='wirebox' inject='wirebox'; property name='CR' inject='CR@constants'; + property name='parser' inject='parser'; /** * Constructor @@ -729,9 +730,35 @@ component accessors="true" singleton { fileWrite( trayOptionsPath, serializeJSON( trayJSON ) ); var background = !(serverProps.console ?: false); // The java arguments to execute: Shared server, custom web configs - var args = ' #serverInfo.JVMargs# -Xmx#serverInfo.heapSize#m -Xms#serverInfo.heapSize#m' - & ' #javaagent# -jar #variables.jarPath#' - & ' --background #background# --port #serverInfo.port# --host #serverInfo.host# --debug #serverInfo.debug#' + + // If background, wrap up JVM args to pass through to background servers + // "real" JVM args must come before Runwar args, so creating two variables, once of which will always be empty. + if( background ) { + var thisJVMArgs = ''; + // "borrow" the CommandBox commandline parser to tokenize the JVM args. Not perfect, but close. Handles quoted values with spaces. + var argTokens = parser.tokenizeInput( serverInfo.JVMargs ) + .map( function( i ){ + // Clean up a couple escapes the parser does that we don't need + return i.replace( '\=', '=', 'all' ).replace( '"', '\"', 'all' ); + }); + // Add in heap size and java agent + argTokens + .append( '-Xmx#serverInfo.heapSize#m' ) + .append( '-Xms#serverInfo.heapSize#m' ); + if( len( trim( javaAgent ) ) ) { argTokens.append( '#javaagent#' ); } + + argString = argTokens.toList( ';' ).replace( '\\', '\', 'all' ); + + var thispassthroughJVMArgs = '--jvm-args="#argString#"'; + // If foreground, just stick them in. + } else { + var thisJVMArgs = ' -Xmx#serverInfo.heapSize#m -Xms#serverInfo.heapSize#m #javaagent# #serverInfo.JVMargs# '; + var thispassthroughJVMArgs = ''; + } + + var args = ' #thisJVMArgs# -jar #variables.jarPath#' + // debug and background need to parse as a single token. Leave the = + & ' --background=#background# --port #serverInfo.port# --host #serverInfo.host# --debug=#serverInfo.debug#' & ' --stop-port #serverInfo.stopsocket# --processname "#processName#" --log-dir "#serverInfo.logDir#"' & ' --open-browser #serverInfo.openbrowser#' & ' --open-url ' & ( serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#' ) @@ -741,7 +768,7 @@ component accessors="true" singleton { & ' --tray-icon "#serverInfo.trayIcon#" --tray-config "#trayOptionsPath#" --servlet-rest-mappings "/rest/*,/api/*"' & ' --directoryindex "#serverInfo.directoryBrowsing#" ' & ( len( CLIAliases ) ? ' --dirs "#CLIAliases#"' : '' ) - & ' #serverInfo.runwarArgs# --timeout #serverInfo.startTimeout#'; + & ' #serverInfo.runwarArgs# --timeout #serverInfo.startTimeout# #thispassthroughJVMArgs# '; // Starting a WAR if (serverInfo.WARPath != "" ) { From a27589679ea445e0bffb0350daf97af34a75f10a Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 29 Dec 2016 23:04:26 -0600 Subject: [PATCH 81/87] COMMANDBOX-550 --- src/cfml/system/services/ServerService.cfc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index f9162c443..298ab6e0a 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -739,7 +739,7 @@ component accessors="true" singleton { var argTokens = parser.tokenizeInput( serverInfo.JVMargs ) .map( function( i ){ // Clean up a couple escapes the parser does that we don't need - return i.replace( '\=', '=', 'all' ).replace( '"', '\"', 'all' ); + return i.replace( '\=', '=', 'all' ).replace( '\\', '\', 'all' ); }); // Add in heap size and java agent argTokens @@ -747,7 +747,7 @@ component accessors="true" singleton { .append( '-Xms#serverInfo.heapSize#m' ); if( len( trim( javaAgent ) ) ) { argTokens.append( '#javaagent#' ); } - argString = argTokens.toList( ';' ).replace( '\\', '\', 'all' ); + argString = argTokens.toList( ';' ).replace( '"', '\"', 'all' ); var thispassthroughJVMArgs = '--jvm-args="#argString#"'; // If foreground, just stick them in. From 62d7cfd8112a2d851c12fb7d373340c164676d56 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 5 Jan 2017 20:28:12 -0600 Subject: [PATCH 82/87] Bump for runwar version --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 5d43ec69a..231d59d6f 100644 --- a/build/build.properties +++ b/build/build.properties @@ -12,7 +12,7 @@ java.pack200=false #dependencies dependencies.dir=${basedir}/lib cfml.version=4.5.4.017 -cfml.loader.version=1.4.4 +cfml.loader.version=1.4.5 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} jre.version=1.8.0_102 From 85fc6fc974454b9b0ba908c1aa006b0f53b07dad Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 5 Jan 2017 20:34:37 -0600 Subject: [PATCH 83/87] COMMANDBOX-551 --- .../interceptors/packageScripts.cfc | 21 ++++++++++++++----- src/cfml/system/services/PackageService.cfc | 14 +++++++------ src/cfml/system/services/ServerService.cfc | 10 ++++----- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/cfml/system/modules_app/package-commands/interceptors/packageScripts.cfc b/src/cfml/system/modules_app/package-commands/interceptors/packageScripts.cfc index 2c885ed30..2440ef2b2 100644 --- a/src/cfml/system/modules_app/package-commands/interceptors/packageScripts.cfc +++ b/src/cfml/system/modules_app/package-commands/interceptors/packageScripts.cfc @@ -26,11 +26,22 @@ component { function onServerStart() { processScripts( 'onServerStart', interceptData.serverinfo.webroot ); } function onServerStop() { processScripts( 'onServerStop', interceptData.serverinfo.webroot ); } function onException() { processScripts( 'onException' ); } - function preInstall() { processScripts( 'preInstall' ); } - function onInstall() { processScripts( 'onInstall' ); } - function postInstall() { processScripts( 'postInstall' ); } - function preUninstall() { processScripts( 'preUninstall' ); } - function postUninstall() { processScripts( 'postUninstall' ); } + + // preInstall gets package requesting the installation because dep isn't installed yet + function preInstall() { processScripts( 'preInstall', interceptData.packagePathRequestingInstallation ); } + + // onInstall gets package requesting the installation because dep isn't installed yet + function onInstall() { processScripts( 'onInstall', interceptData.packagePathRequestingInstallation ); } + + // postInstall runs in the newly installed package + function postInstall() { processScripts( 'postInstall', interceptData.installDirectory ); } + + // preUninstall runs in the package that's about to be uninstalled + function preUninstall() { processScripts( 'preUninstall', interceptData.uninstallDirectory ); } + + // postUninstall gets package that requested uninstallation because dep isn't there any longer + function postUninstall() { processScripts( 'postUninstall', interceptData.uninstallArgs.packagePathRequestingUninstallation ); } + function preVersion() { processScripts( 'preVersion' ); } function postVersion() { processScripts( 'postVersion' ); } function onRelease() { processScripts( 'onRelease' ); } diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index ca43d1b74..f46a89812 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -76,7 +76,7 @@ component accessors="true" singleton { string packagePathRequestingInstallation = arguments.currentWorkingDirectory ){ - interceptorService.announceInterception( 'preInstall', { installArgs=arguments } ); + interceptorService.announceInterception( 'preInstall', { installArgs=arguments, packagePathRequestingInstallation=packagePathRequestingInstallation } ); // If there is a package to install, install it if( len( arguments.ID ) ) { @@ -245,7 +245,8 @@ component accessors="true" singleton { artifactDescriptor = artifactDescriptor, ignorePatterns = ignorePatterns, endpointData = endpointData, - artifactPath = tmpPath + artifactPath = tmpPath, + packagePathRequestingInstallation = packagePathRequestingInstallation }; interceptorService.announceInterception( 'onInstall', interceptData ); // Make sure these get set back into their original variables in case the interceptor changed them. @@ -489,7 +490,7 @@ component accessors="true" singleton { consoleLogger.info( "No dependencies found to install, but it's the thought that counts, right?" ); } - interceptorService.announceInterception( 'postInstall', { installArgs=arguments } ); + interceptorService.announceInterception( 'postInstall', { installArgs=arguments, installDirectory=installDirectory } ); return true; } @@ -544,9 +545,7 @@ component accessors="true" singleton { required string currentWorkingDirectory, string packagePathRequestingUninstallation = arguments.currentWorkingDirectory ){ - - interceptorService.announceInterception( 'preUninstall', { uninstallArgs=arguments } ); - + // In case someone types "uninstall coldbox@4.0.0" var packageName = listFirst( arguments.ID, '@' ); @@ -569,6 +568,9 @@ component accessors="true" singleton { uninstallDirectory = fileSystemUtil.resolvePath( installPaths[ packageName ] ); } } + + // Wait to run this until we've decided where the package lives that's being uninstalled. + interceptorService.announceInterception( 'preUninstall', { uninstallArgs=arguments, uninstallDirectory=uninstallDirectory } ); // See if the package exists here if( len( uninstallDirectory ) && directoryExists( uninstallDirectory ) ) { diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 298ab6e0a..e913f03dc 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1473,11 +1473,11 @@ component accessors="true" singleton { props = JSONService.addProp( props, '', '', getDefaultServerJSON() ); // Suggest a couple optional web error pages props = JSONService.addProp( props, '', '', { - web : { - errorPages : { - 404 : '', - 500 : '', - default : '' + 'web' : { + 'errorPages' : { + '404' : '', + '500' : '', + 'default' : '' } } } ); From ddb925e5d62d70a3f470d0daee4f9affaf919444 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sun, 8 Jan 2017 22:31:41 -0600 Subject: [PATCH 84/87] Bump version for stable build. --- build/build.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build/build.xml b/build/build.xml index d04e60076..daa4231ba 100644 --- a/build/build.xml +++ b/build/build.xml @@ -15,8 +15,9 @@ External Dependencies: - - + + + From 8a67e91d4ddd95b0a602614d5b4ec9aad2b64c71 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sun, 8 Jan 2017 22:55:48 -0600 Subject: [PATCH 85/87] Turning on pack 200 for stable builds --- build/build.properties | 1 - build/build.xml | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.properties b/build/build.properties index 231d59d6f..75bccac78 100644 --- a/build/build.properties +++ b/build/build.properties @@ -7,7 +7,6 @@ commandbox.description=CommandBox is a ColdFusion (CFML) CLI, Package Manager, S #java build java.compiler=1.7 java.debug=true -java.pack200=false #dependencies dependencies.dir=${basedir}/lib diff --git a/build/build.xml b/build/build.xml index daa4231ba..1540df5f4 100644 --- a/build/build.xml +++ b/build/build.xml @@ -271,8 +271,9 @@ External Dependencies: - + + This is a stable build, let's compress these jars! From b00f9b809cf34385457d6cb54eacd8ebc692a0dc Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 9 Jan 2017 16:35:42 -0600 Subject: [PATCH 86/87] Bump to final release of Runwar 3.5.0 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 75bccac78..08d9df90b 100644 --- a/build/build.properties +++ b/build/build.properties @@ -16,7 +16,7 @@ cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} jre.version=1.8.0_102 launch4j.version=3.4 -runwar.version=3.5.0-SNAPSHOT +runwar.version=3.5.0 #build locations build.type=localdev From 52f08780668808b2fcc104c7fd724ca8a43c91f0 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 9 Jan 2017 16:36:06 -0600 Subject: [PATCH 87/87] Bump for latests runwar build --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 08d9df90b..305a04c03 100644 --- a/build/build.properties +++ b/build/build.properties @@ -11,7 +11,7 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib cfml.version=4.5.4.017 -cfml.loader.version=1.4.5 +cfml.loader.version=1.4.6 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} jre.version=1.8.0_102