Compare commits

...

50 Commits

Author SHA1 Message Date
Robinson 7572d4f407
Cleaned up logging 2024-02-13 20:23:40 +01:00
Robinson f1c97a983c
renamed TrieFileResourceManager, added better logging 2024-02-13 20:22:44 +01:00
Robinson a72efb6ed1
added safe toString/hash, as URL.toString() can cause DNS lookups! 2024-02-13 20:22:20 +01:00
Robinson 947947ea2e
comments and cleaned up logging 2024-02-13 20:21:27 +01:00
Robinson 6703932dd8
Updated logger (removed KotlinLogging) 2024-02-13 11:04:40 +01:00
Robinson 3cb5a8fb20
Added comments 2024-02-13 10:59:54 +01:00
Robinson 1f2efa40e9
Updated build deps 2023-10-09 12:27:09 +02:00
Robinson 11bb2330e6
Version 2.16 2023-10-02 23:49:12 +02:00
Robinson d9b88c4759
updated gradle 2023-10-02 16:12:50 +02:00
Robinson 20559c8240
Added http security handler 2023-08-18 14:00:25 +02:00
Robinson fd2e926fbb
added jakarta servlet license 2023-08-18 13:58:55 +02:00
Robinson 0538b93480
hardcoded project name 2023-08-05 13:49:14 -06:00
Robinson 0462bf600d
Added index.html 2023-08-05 13:48:39 -06:00
Robinson 572f97d58f
updated gitignore 2023-08-05 13:48:24 -06:00
Robinson b7d41d4da3
Updated license 2023-06-12 23:42:22 +02:00
Robinson 0bc08c6de2
Removed unused code 2023-06-12 23:42:13 +02:00
Robinson 3bb263e549
Updated version 2023-06-12 23:40:11 +02:00
Robinson bc4512eab7
Updated to support FSM maven project 2023-06-12 23:40:01 +02:00
Robinson ce507b5897
Updated license 2023-06-11 21:29:30 +02:00
Robinson ff41833f4a
Added more version information 2023-06-11 21:29:25 +02:00
Robinson 1cf792fb0a
code cleanup 2023-06-11 21:28:59 +02:00
Robinson cbc3ceb25e
Formatting 2023-02-03 12:23:01 +01:00
Robinson d89eda0bfd
Better JPMS support (less explicit deps) 2023-01-23 10:56:16 +01:00
Robinson de5fac04bc
updated version 2023-01-21 16:30:07 +01:00
Robinson 3220aa761e
vaadin 14.9 changed path loading. Updated error messages 2023-01-21 16:18:57 +01:00
Robinson 198775ea51
Updated to support Vaadin 14.9. Updated version 2023-01-20 16:02:29 +01:00
Robinson 792e35d1a7
Updated gradle 2022-11-27 21:29:49 +01:00
Robinson 3f3c4823a2
Removed devMode (didn't use it). Updated to Vaadin 14.8.20 (Flow 2.7.23) 2022-11-27 21:29:42 +01:00
Robinson 70379ef0c5
Added comments 2022-03-07 08:11:45 +01:00
Robinson 24c9928b8a
Set correct version info 2022-03-05 14:06:20 +01:00
Robinson 334dc34b9b
Updated version + deps 2022-03-05 13:40:23 +01:00
Robinson fdb5af2123
Not JPMS ready yet 2022-03-04 20:22:12 +01:00
Robinson 49ccbfcd17
Updated dependencies. 2022-03-04 20:18:58 +01:00
Robinson 854d39c86d
Updated to latest kotlin, added logging when actor.send() fails 2022-03-04 20:16:54 +01:00
Robinson 817b7a3f8d
Println goes to logger now 2022-03-03 16:17:37 +01:00
Robinson f015a9fd94
Updated gradle 2022-03-03 15:59:56 +01:00
Robinson 05322b175d
Changed logger to trace (from error) 2022-03-03 15:59:45 +01:00
Robinson 9bddbab3c5
Updated version 2021-12-08 15:46:12 -07:00
Robinson 540f4fa2e0
Added undertow to version specifications 2021-12-08 15:45:26 -07:00
Robinson 69bb27cb37
Upgraded kotlin-logging and classgraph 2021-12-08 11:23:41 -07:00
Robinson 488a3ce641
Upgraded undertow 2021-12-08 11:23:00 -07:00
Robinson d4e22a4814
Added comments for changing the session lock strategy 2021-12-08 08:59:30 -07:00
Robinson 6ced092716
Added startup log information 2021-12-08 08:48:56 -07:00
Robinson 7fcc899215
Cleaned up logic for how the stats.json file is loaded. Fixed issue loading the stats.json file via the classloader. Changed the theadgroup for Undertow startup. Removed unnecessary comments 2021-12-08 07:17:43 -07:00
Robinson 73ad4f3394
Added configuration for setting the stats.json URL location (if that option is desired) 2021-12-08 07:16:32 -07:00
Robinson 0626c10a6f
Added ability to add context classloader to thread factory 2021-12-07 19:45:45 -07:00
Robinson 4fdcc2b199
Fixed issue with stats.json base URL 2021-12-07 15:39:24 -07:00
Robinson e9929780f9
Updated version 2021-12-07 14:26:06 -07:00
Robinson 329da3105d
Updated vaadin to latest LTS 2021-12-07 14:25:43 -07:00
Robinson 9c8447b20f
Added more logging information, changed how trace logs are used 2021-12-07 14:24:14 -07:00
36 changed files with 1179 additions and 3805 deletions

1
.gitignore vendored
View File

@ -61,6 +61,7 @@ fabric.properties
# From https://github.com/github/gitignore/blob/master/Gradle.gitignore
.gradle
/build/
/frontend/generated/
# Ignore Gradle GUI config
gradle-app.setting

79
LICENSE
View File

@ -1,7 +1,7 @@
- VaadinUndertow - Vaadin support for the Undertow web server
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/VaadinUndertow
Copyright 2021
Copyright 2023
Dorkbox LLC
Extra license information
@ -35,28 +35,16 @@
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Vaadin - An open platform for building modern web apps for Java back ends
[The Apache Software License, Version 2.0]
https://github.com/vaadin/
Copyright 2021
Vaadin Ltd.
- Logback - Logback is a logging framework for Java applications
[The Apache Software License, Version 2.0]
http://logback.qos.ch
Copyright 2021
QOS.ch
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
[The Apache Software License, Version 2.0]
https://github.com/Kotlin/kotlinx.coroutines
Copyright 2021
Copyright 2023
JetBrains s.r.o.
- Undertow - High performance non-blocking webserver
[The Apache Software License, Version 2.0]
https://github.com/undertow-io/undertow
Copyright 2021
Copyright 2023
JBoss
Red Hat, Inc.
Individual contributors as listed in files
@ -64,27 +52,40 @@
- ClassGraph - An uber-fast parallelized Java classpath scanner and module scanner
[The Apache Software License, Version 2.0]
https://github.com/classgraph/classgraph
Copyright 2021
Copyright 2023
Luke Hutchison
- kotlin-logging - Lightweight logging framework for Kotlin
[The Apache Software License, Version 2.0]
https://github.com/MicroUtils/kotlin-logging
Copyright 2021
Copyright 2023
Ohad Shai
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2021
https://www.slf4j.org
Copyright 2023
QOS.ch
- JUL to SLF4J - Java Util Logging implemented over SLF4J
[MIT License]
http://www.slf4j.org
Copyright 2021
https://www.slf4j.org
Copyright 2023
QOS.ch
- Vaadin - An open platform for building modern web apps for Java back ends
[The Apache Software License, Version 2.0]
https://github.com/vaadin/
Copyright 2023
Vaadin Ltd.
- Jakarta Servlet - Jakarta Servlet™ is a standard technology for interacting with the web on the Jakarta EE platform.
[GNU General Public License, version 2, with the Classpath Exception]
https://projects.eclipse.org/projects/ee4j.servlet
Copyright 2023
Yamini K B
ee4j-pmc@eclipse.org
- Updates - Software Update Management
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Updates
@ -99,3 +100,39 @@
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- FiniteStateMachine - Finite State Machine using the AhoCorasick implementation using a Double Array Trie, java 8+
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/FSM
Copyright 2023
Dorkbox LLC
Extra license information
- AhoCorasickDoubleArrayTrie - An extremely fast implementation of Aho Corasick algorithm based on Double Array Trie structure.
[The Apache Software License, Version 2.0]
https://github.com/hankcs/AhoCorasickDoubleArrayTrie
Copyright 2018
hankcs <me@hankcs.com>
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Updates - Software Update Management
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Updates
Copyright 2021
Dorkbox LLC
Extra license information
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 2020
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md

347
LICENSE.GPLv2_CP Normal file
View File

@ -0,0 +1,347 @@
The GNU General Public License (GPL)
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your freedom to share
and change it. By contrast, the GNU General Public License is intended to
guarantee your freedom to share and change free software--to make sure the
software is free for all its users. This General Public License applies to
most of the Free Software Foundation's software and to any other program whose
authors commit to using it. (Some other Free Software Foundation software is
covered by the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom to
distribute copies of free software (and charge for this service if you wish),
that you receive source code or can get it if you want it, that you can change
the software or use pieces of it in new free programs; and that you know you
can do these things.
To protect your rights, we need to make restrictions that forbid anyone to deny
you these rights or to ask you to surrender the rights. These restrictions
translate to certain responsibilities for you if you distribute copies of the
software, or if you modify it.
For example, if you distribute copies of such a program, whether gratis or for
a fee, you must give the recipients all the rights that you have. You must
make sure that they, too, receive or can get the source code. And you must
show them these terms so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2)
offer you this license which gives you legal permission to copy, distribute
and/or modify the software.
Also, for each author's protection and ours, we want to make certain that
everyone understands that there is no warranty for this free software. If the
software is modified by someone else and passed on, we want its recipients to
know that what they have is not the original, so that any problems introduced
by others will not reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We
wish to avoid the danger that redistributors of a free program will
individually obtain patent licenses, in effect making the program proprietary.
To prevent this, we have made it clear that any patent must be licensed for
everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and modification
follow.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains a notice
placed by the copyright holder saying it may be distributed under the terms of
this General Public License. The "Program", below, refers to any such program
or work, and a "work based on the Program" means either the Program or any
derivative work under copyright law: that is to say, a work containing the
Program or a portion of it, either verbatim or with modifications and/or
translated into another language. (Hereinafter, translation is included
without limitation in the term "modification".) Each licensee is addressed as
"you".
Activities other than copying, distribution and modification are not covered by
this License; they are outside its scope. The act of running the Program is
not restricted, and the output from the Program is covered only if its contents
constitute a work based on the Program (independent of having been made by
running the Program). Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's source code as
you receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice and
disclaimer of warranty; keep intact all the notices that refer to this License
and to the absence of any warranty; and give any other recipients of the
Program a copy of this License along with the Program.
You may charge a fee for the physical act of transferring a copy, and you may
at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion of it, thus
forming a work based on the Program, and copy and distribute such modifications
or work under the terms of Section 1 above, provided that you also meet all of
these conditions:
a) You must cause the modified files to carry prominent notices stating
that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in whole or
in part contains or is derived from the Program or any part thereof, to be
licensed as a whole at no charge to all third parties under the terms of
this License.
c) If the modified program normally reads commands interactively when run,
you must cause it, when started running for such interactive use in the
most ordinary way, to print or display an announcement including an
appropriate copyright notice and a notice that there is no warranty (or
else, saying that you provide a warranty) and that users may redistribute
the program under these conditions, and telling the user how to view a copy
of this License. (Exception: if the Program itself is interactive but does
not normally print such an announcement, your work based on the Program is
not required to print an announcement.)
These requirements apply to the modified work as a whole. If identifiable
sections of that work are not derived from the Program, and can be reasonably
considered independent and separate works in themselves, then this License, and
its terms, do not apply to those sections when you distribute them as separate
works. But when you distribute the same sections as part of a whole which is a
work based on the Program, the distribution of the whole must be on the terms
of this License, whose permissions for other licensees extend to the entire
whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest your
rights to work written entirely by you; rather, the intent is to exercise the
right to control the distribution of derivative or collective works based on
the Program.
In addition, mere aggregation of another work not based on the Program with the
Program (or with a work based on the Program) on a volume of a storage or
distribution medium does not bring the other work under the scope of this
License.
3. You may copy and distribute the Program (or a work based on it, under
Section 2) in object code or executable form under the terms of Sections 1 and
2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable source
code, which must be distributed under the terms of Sections 1 and 2 above
on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three years, to
give any third party, for a charge no more than your cost of physically
performing source distribution, a complete machine-readable copy of the
corresponding source code, to be distributed under the terms of Sections 1
and 2 above on a medium customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer to
distribute corresponding source code. (This alternative is allowed only
for noncommercial distribution and only if you received the program in
object code or executable form with such an offer, in accord with
Subsection b above.)
The source code for a work means the preferred form of the work for making
modifications to it. For an executable work, complete source code means all
the source code for all modules it contains, plus any associated interface
definition files, plus the scripts used to control compilation and installation
of the executable. However, as a special exception, the source code
distributed need not include anything that is normally distributed (in either
source or binary form) with the major components (compiler, kernel, and so on)
of the operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the source
code from the same place counts as distribution of the source code, even though
third parties are not compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program except as
expressly provided under this License. Any attempt otherwise to copy, modify,
sublicense or distribute the Program is void, and will automatically terminate
your rights under this License. However, parties who have received copies, or
rights, from you under this License will not have their licenses terminated so
long as such parties remain in full compliance.
5. You are not required to accept this License, since you have not signed it.
However, nothing else grants you permission to modify or distribute the Program
or its derivative works. These actions are prohibited by law if you do not
accept this License. Therefore, by modifying or distributing the Program (or
any work based on the Program), you indicate your acceptance of this License to
do so, and all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the Program),
the recipient automatically receives a license from the original licensor to
copy, distribute or modify the Program subject to these terms and conditions.
You may not impose any further restrictions on the recipients' exercise of the
rights granted herein. You are not responsible for enforcing compliance by
third parties to this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues), conditions
are imposed on you (whether by court order, agreement or otherwise) that
contradict the conditions of this License, they do not excuse you from the
conditions of this License. If you cannot distribute so as to satisfy
simultaneously your obligations under this License and any other pertinent
obligations, then as a consequence you may not distribute the Program at all.
For example, if a patent license would not permit royalty-free redistribution
of the Program by all those who receive copies directly or indirectly through
you, then the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply and
the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents or
other property right claims or to contest validity of any such claims; this
section has the sole purpose of protecting the integrity of the free software
distribution system, which is implemented by public license practices. Many
people have made generous contributions to the wide range of software
distributed through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing to
distribute software through any other system and a licensee cannot impose that
choice.
This section is intended to make thoroughly clear what is believed to be a
consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in certain
countries either by patents or by copyrighted interfaces, the original
copyright holder who places the Program under this License may add an explicit
geographical distribution limitation excluding those countries, so that
distribution is permitted only in or among countries not thus excluded. In
such case, this License incorporates the limitation as if written in the body
of this License.
9. The Free Software Foundation may publish revised and/or new versions of the
General Public License from time to time. Such new versions will be similar in
spirit to the present version, but may differ in detail to address new problems
or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any later
version", you have the option of following the terms and conditions either of
that version or of any later version published by the Free Software Foundation.
If the Program does not specify a version number of this License, you may
choose any version ever published by the Free Software Foundation.
10. If you wish to incorporate parts of the Program into other free programs
whose distribution conditions are different, write to the author to ask for
permission. For software which is copyrighted by the Free Software Foundation,
write to the Free Software Foundation; we sometimes make exceptions for this.
Our decision will be guided by the two goals of preserving the free status of
all derivatives of our free software and of promoting the sharing and reuse of
software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE
PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE
PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER
OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible
use to the public, the best way to achieve this is to make it free software
which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach
them to the start of each source file to most effectively convey the exclusion
of warranty; and each file should have at least the "copyright" line and a
pointer to where the full notice is found.
One line to give the program's name and a brief idea of what it does.
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc., 59
Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this when it
starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free
software, and you are welcome to redistribute it under certain conditions;
type 'show c' for details.
The hypothetical commands 'show w' and 'show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may be
called something other than 'show w' and 'show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your school,
if any, to sign a "copyright disclaimer" for the program, if necessary. Here
is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
'Gnomovision' (which makes passes at compilers) written by James Hacker.
signature of Ty Coon, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General Public
License instead of this License.
"CLASSPATH" EXCEPTION TO THE GPL
Certain source files distributed by Oracle America and/or its affiliates are
subject to the following clarification and special exception to the GPL, but
only where Oracle has expressly included in the particular source file's header
the words "Oracle designates this particular file as subject to the "Classpath"
exception as provided by Oracle in the LICENSE file that accompanied this code."
Linking this library statically or dynamically with other modules is making
a combined work based on this library. Thus, the terms and conditions of
the GNU General Public License cover the whole combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,
and to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent module,
the terms and conditions of the license of that module. An independent
module is a module which is not derived from or based on this library. If
you modify this library, you may extend this exception to your version of
the library, but you are not obligated to do so. If you do not wish to do
so, delete this exception statement from your version.

View File

@ -23,7 +23,7 @@ Maven Info
<dependency>
<groupId>com.dorkbox</groupId>
<artifactId>VaadinUndertow</artifactId>
<version>14.7</version>
<version>14.10</version>
</dependency>
</dependencies>
```
@ -33,7 +33,7 @@ Gradle Info
```
dependencies {
...
implementation "com.dorkbox:VaadinUndertow:14.7"
implementation "com.dorkbox:VaadinUndertow:14.10"
}
````
@ -41,4 +41,3 @@ License
---------
This project is © 2020 dorkbox llc, and is distributed under the terms of the Apache v2.0 License. See file "LICENSE" for further
references.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021 dorkbox, llc
* Copyright 2023 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,18 +20,13 @@
////// RELEASE : (to sonatype/maven central), <'publish and release' - 'publishToSonatypeAndRelease'>
///////////////////////////////
import dorkbox.gradle.kotlin
import java.time.Instant
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace!
plugins {
id("com.dorkbox.GradleUtils") version "2.14"
id("com.dorkbox.Licensing") version "2.10"
id("com.dorkbox.VersionUpdate") version "2.4"
id("com.dorkbox.GradlePublish") version "1.11"
id("com.dorkbox.GradleUtils") version "3.18"
id("com.dorkbox.Licensing") version "2.28"
id("com.dorkbox.VersionUpdate") version "2.8"
id("com.dorkbox.GradlePublish") version "1.20"
kotlin("jvm") version "1.5.21"
kotlin("jvm") version "1.9.0"
}
object Extras {
@ -39,19 +34,17 @@ object Extras {
const val group = "com.dorkbox"
const val name = "VaadinUndertow"
const val id = "VaadinUndertow"
const val version = "14.7.1"
const val version = "14.10"
const val vendor = "Dorkbox LLC"
const val vendorUrl = "https://dorkbox.com"
const val url = "https://git.dorkbox.com/dorkbox/VaadinUndertow"
val buildDate = Instant.now().toString()
const val coroutineVer = "1.4.3"
// this must match the version information in the VaadinConfig.kt file (this is automatically passed into the plugin)
const val vaadinVer = "14.7.6"
const val undertowVer = "2.2.10.Final"
// These MUST be in lock-step with what the GradleVaadin (other project) + gradle.build.kts + VaadinApplication.kt define, otherwise horrific errors can occur.
const val vaadinVer = "14.10.1"
const val undertowVer = "2.2.22.Final"
}
///////////////////////////////
@ -59,8 +52,8 @@ object Extras {
///////////////////////////////
GradleUtils.load("$projectDir/../../gradle.properties", Extras)
GradleUtils.defaults()
GradleUtils.compileConfiguration(JavaVersion.VERSION_1_8)
//GradleUtils.jpms(JavaVersion.VERSION_1_9)
GradleUtils.compileConfiguration(JavaVersion.VERSION_11)
//GradleUtils.jpms(JavaVersion.VERSION_1_9) vaadin doesn't support jpms yet
licensing {
@ -97,74 +90,41 @@ licensing {
}
}
sourceSets {
main {
java {
setSrcDirs(listOf("src"))
// want to include java+kotlin files for the source. 'setSrcDirs' resets includes...
include("**/*.java", "**/*.kt")
}
}
}
kotlin {
sourceSets {
main {
kotlin {
setSrcDirs(listOf("src"))
// want to include kotlin files for the source. 'setSrcDirs' resets includes...
include("**/*.java", "**/*.kt")
}
}
test {
kotlin {
setSrcDirs(listOf("test"))
// want to include kotlin files for the source. 'setSrcDirs' resets includes...
include("**/*.java", "**/*.kt")
}
}
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Extras.coroutineVer}")
api("org.jetbrains.kotlin:kotlin-reflect")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
compileOnly("com.vaadin:vaadin:${Extras.vaadinVer}")
// we use undertow 2, with kotlin coroutines on top (with 1 actor per session)
implementation("io.undertow:undertow-core:${Extras.undertowVer}")
implementation("io.undertow:undertow-servlet:${Extras.undertowVer}")
implementation("io.undertow:undertow-websockets-jsr:${Extras.undertowVer}")
api("io.undertow:undertow-core:${Extras.undertowVer}")
api("io.undertow:undertow-servlet:${Extras.undertowVer}")
api("io.undertow:undertow-websockets-jsr:${Extras.undertowVer}")
// Uber-fast, ultra-lightweight Java classpath and module path scanner
implementation("io.github.classgraph:classgraph:4.8.116")
api("io.github.classgraph:classgraph:4.8.160")
implementation("com.dorkbox:Updates:1.1")
api("com.dorkbox:Updates:1.1")
api("com.dorkbox:FSM:1.0")
// implementation("com.conversantmedia:disruptor:1.2.19")
// awesome logging framework for kotlin.
// https://www.reddit.com/r/Kotlin/comments/8gbiul/slf4j_loggers_in_3_ways/
// https://github.com/MicroUtils/kotlin-logging
implementation("io.github.microutils:kotlin-logging:2.0.11")
api("io.github.microutils:kotlin-logging:3.0.5")
// 1.8.0-beta4 supports jpms
implementation("org.slf4j:slf4j-api:1.8.0-beta4")
implementation("org.slf4j:jul-to-slf4j:1.8.0-beta4")
api("org.slf4j:slf4j-api:2.0.7")
api("org.slf4j:jul-to-slf4j:2.0.7")
implementation("ch.qos.logback:logback-core:1.3.0-alpha4")
compileOnly("ch.qos.logback:logback-classic:1.3.0-alpha4")
testImplementation("junit:junit:4.13.2")
// api("ch.qos.logback:logback-core:1.4.5")
// compileOnly("ch.qos.logback:logback-classic:1.4.5")
//
//
// testImplementation("com.vaadin:vaadin:${Extras.vaadinVer}")
// testImplementation("ch.qos.logback:logback-classic:1.4.5")
}
tasks.jar.get().apply {
@ -177,7 +137,7 @@ tasks.jar.get().apply {
attributes["Specification-Vendor"] = Extras.vendor
attributes["Implementation-Title"] = "${Extras.group}.${Extras.id}"
attributes["Implementation-Version"] = Extras.buildDate
attributes["Implementation-Version"] = GradleUtils.now()
attributes["Implementation-Vendor"] = Extras.vendor
}
}

36
frontend/index.html Normal file
View File

@ -0,0 +1,36 @@
<!--
~ Copyright 2023 dorkbox, llc
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body, #outlet {
height: 100vh;
width: 100%;
margin: 0;
}
</style>
<!-- index.ts is included here automatically (either by the dev server or during the build) -->
</head>
<body>
<!-- This outlet div is where the views are rendered -->
<div id="outlet"></div>
</body>
</html>

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

269
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,67 +17,101 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -106,80 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@ -13,3 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
rootProject.name = "VaadinUndertow"

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021 dorkbox, llc
* Copyright 2023 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,14 +15,14 @@
*/
package dorkbox.vaadin
import com.vaadin.flow.server.VaadinContext
import com.vaadin.flow.server.frontend.FrontendUtils
import dorkbox.vaadin.devMode.DevModeInitializer
import dorkbox.fsm.DoubleArrayStringTrie
import dorkbox.vaadin.undertow.*
import dorkbox.vaadin.util.CallingClass
import dorkbox.vaadin.util.TrieClassLoader
import dorkbox.vaadin.util.UndertowBuilder
import dorkbox.vaadin.util.VaadinConfig
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
import io.github.classgraph.ClassGraph
import io.github.classgraph.ScanResult
import io.undertow.Undertow
@ -44,11 +44,13 @@ import java.io.File
import java.io.IOException
import java.net.URL
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import javax.servlet.*
import java.util.concurrent.*
import java.util.concurrent.atomic.*
import javax.servlet.Servlet
import javax.servlet.ServletContainerInitializer
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.SessionTrackingMode
import javax.servlet.annotation.HandlesTypes
import kotlin.reflect.KClass
@ -62,9 +64,17 @@ class VaadinApplication : ExceptionHandler {
/**
* Gets the version number.
*/
const val version = "14.7"
const val version = "14.10"
const val vaadinVersion = VaadinConfig.VAADIN_VERSION
// this must match the version information in the build.gradle.kts file (this is automatically passed into the plugin)
const val vaadinVersion = "14.10.1"
const val undertowVersion = "2.2.21.Final"
// Vaadin 14.9 changed how license checking works, and doesn't include this.
const val oshiVersion = "6.4.0"
// license checker requires JNA
const val jnaVersion = "5.12.1" //5.13 isn't properly published? It cannot be found.
init {
// Add this project to the updates system, which verifies this class + UUID + version information
@ -76,6 +86,7 @@ class VaadinApplication : ExceptionHandler {
private val httpLogger = KotlinLogging.logger(logger.name + ".http")
val runningAsJar: Boolean
var enableCachedHandlers = false
val tempDir: File = File(System.getProperty("java.io.tmpdir", "tmpDir"), "undertow").absoluteFile
private val onStopList = mutableListOf<Runnable>()
@ -98,9 +109,9 @@ class VaadinApplication : ExceptionHandler {
private lateinit var servletHttpHandler: HttpHandler
private lateinit var servletManager: DeploymentManager
private lateinit var jarStringTrie: DoubleArrayTrie<String>
private lateinit var jarUrlTrie: DoubleArrayTrie<URL>
private lateinit var diskTrie: DoubleArrayTrie<URL>
private lateinit var jarStringTrie: DoubleArrayStringTrie<String>
private lateinit var jarUrlTrie: DoubleArrayStringTrie<URL>
private lateinit var diskTrie: DoubleArrayStringTrie<URL>
private lateinit var servletBuilder: DeploymentInfo
private lateinit var serverBuilder: UndertowBuilder
@ -110,6 +121,8 @@ class VaadinApplication : ExceptionHandler {
lateinit var baseUrl: String
private val threadGroup = ThreadGroup("Web Server")
@Volatile
private var undertowServer: Undertow? = null
@ -155,7 +168,7 @@ class VaadinApplication : ExceptionHandler {
// must ALSO use forward slash (unix)!!
val relativePath = resourcePath.path.substring(rootFileSize)
logger.error {
logger.trace {
"Disk resource: $relativePath"
}
@ -353,115 +366,23 @@ class VaadinApplication : ExceptionHandler {
}
// EVERYTHING IS ACCESSED VIA TRIE, NOT VIA HASHMAP! (it's faster this way)
jarStringTrie = DoubleArrayTrie(jarStringResourceRequestMap)
jarUrlTrie = DoubleArrayTrie(jarUrlResourceRequestMap)
diskTrie = DoubleArrayTrie(diskResourceRequestMap)
jarStringTrie = DoubleArrayStringTrie(jarStringResourceRequestMap)
jarUrlTrie = DoubleArrayStringTrie(jarUrlResourceRequestMap)
diskTrie = DoubleArrayStringTrie(diskResourceRequestMap)
// so we can use the undertow cache to serve resources, instead of the vaadin servlet (which doesn't cache, and is really slow)
// NOTE: this will load the stats.json file!
val debug = vaadinConfig.debug
// for our classloader, we have to make sure that we are BY DIRECTORY, not by file, for the resource array!
val toTypedArray = jarLocations.map { it.resourceDir }.toSet().toTypedArray()
this.trieClassLoader = TrieClassLoader(diskTrie, jarStringTrie, toTypedArray, this.javaClass.classLoader, debug)
this.trieClassLoader = TrieClassLoader(diskTrie, jarStringTrie, toTypedArray, this.javaClass.classLoader, logger)
// we want to start ALL aspects of the application using our NEW classloader (instead of the "current" classloader)
Thread.currentThread().contextClassLoader = this.trieClassLoader
val strictFileResourceManager = StrictFileResourceManager("Static Files", diskTrie, debug)
val jarResourceManager = JarResourceManager("Jar Files", jarUrlTrie, debug)
// val jarResources = ArrayList<JarResourceManager>()
// jarLocations.forEach { (requestPath, resourcePath, relativeResourcePath) ->
//// val cleanedUrl = java.net.URLDecoder.decode(jarUrl.file, Charsets.UTF_8)
// val file = File(resourcePath.file)
//
// if (debugResources) {
// println(" JAR: $file")
// }
//
// // the location IN THE JAR is actually "META-INF/resources", so we want to make sure of that when
// // serving the request, that the correct path is used.
// jarResources.add(JarResourceManager(file, metaInfResources))
// }
// collect all the resources available from each location to ALSO be handled by undertow
// val diskResources = ArrayList<FileResourceManager>()
// val fileResources = ArrayList<FileResourceManager>()
// diskLocations.forEach { (requestPath, resourcePath) ->
// val wwwCompatiblePath = java.net.URLDecoder.decode(requestPath, Charsets.UTF_8)
// val diskFile = resourcePath.file
//
//
// // this serves a BASE location!
// diskResources.add(FileResourceManager(metaInfResourcesLocation))
//
// // if this location is where our "META-INF/resources" directory exists, ALSO add that location, because the
// // vaadin will request resources based on THAT location as well.
// val metaInfResourcesLocation = File(file, metaInfResources)
//
// if (metaInfResourcesLocation.isDirectory) {
// diskResources.add(FileResourceManager(metaInfResourcesLocation))
//
// // we will also serve content from ALL child directories
// metaInfResourcesLocation.listFiles()?.forEach { childFile ->
// val element = "/${childFile.relativeTo(metaInfResourcesLocation)}"
// if (debugResources) {
// println(" DISK: $cleanedUrl")
// }
//
// when {
// childFile.isDirectory -> prefixResources.add(element)
// else -> exactResources.add(element)
// }
// }
// }
//
// if (debugResources) {
// println(" DISK: $cleanedUrl")
// }
//
// diskResources.add(FileResourceManager(file))
//
// // we will also serve content from ALL child directories
// // (except for the META-INF dir, which we are ALREADY serving content)
// file.listFiles()?.forEach { childFile ->
// val element = "/${childFile.relativeTo(file)}"
//
// if (debugResources) {
// println(" DISK: $element")
// }
//
// when {
// childFile.isDirectory -> {
// if (childFile.name != "META-INF") {
// prefixResources.add(element)
// }
// }
// else -> exactResources.add(element)
// }
// }
// }
// jarLocations.forEach { jarUrl ->
// val cleanedUrl = java.net.URLDecoder.decode(jarUrl.file, Charsets.UTF_8)
// val file = File(cleanedUrl)
//
// if (debugResources) {
// println(" JAR: $cleanedUrl")
// }
//
// // the location IN THE JAR is actually "META-INF/resources", so we want to make sure of that when
// // serving the request, that the correct path is used.
// jarResources.add(JarResourceManager(file, metaInfResources))
// }
val strictFileResourceManager = StrictFileResourceManager("Static Files", diskTrie, httpLogger)
val jarResourceManager = JarResourceManager("Jar Files", jarUrlTrie, httpLogger)
// When we are searching for resources, the following search order is optimized for access speed and request hit order
// DISK
@ -473,8 +394,6 @@ class VaadinApplication : ExceptionHandler {
// then every other jar
resources.add(strictFileResourceManager)
resources.add(jarResourceManager)
// resources.addAll(diskResources)
// resources.addAll(fileResources)
// val client = jarResources.firstOrNull { it.name.contains("flow-client") }
@ -507,10 +426,11 @@ class VaadinApplication : ExceptionHandler {
fun initServlet(enableCachedHandlers: Boolean, cacheTimeoutSeconds: Int,
servletClass: Class<out Servlet> = com.vaadin.flow.server.VaadinServlet::class.java,
servletName: String = "Vaadin",
secureService: Boolean = false,
servletConfig: ServletInfo.() -> Unit = {},
undertowConfig: UndertowBuilder.() -> Unit) {
this.enableCachedHandlers = enableCachedHandlers
resourceCollectionManager = ResourceCollectionManager(resources)
val conditionalResourceManager =
@ -575,8 +495,7 @@ class VaadinApplication : ExceptionHandler {
// val instance = servletClass.constructors[0].newInstance()
// val immediateInstanceFactory = ImmediateInstanceFactory(instance) as ImmediateInstanceFactory<out Servlet>
val threadGroup = ThreadGroup("Web Server")
val executor = Executors.newCachedThreadPool(DaemonThreadFactory("HttpWrapper", threadGroup))
val executor = Executors.newCachedThreadPool(DaemonThreadFactory("HttpWrapper", threadGroup, trieClassLoader))
servlet = Servlets.servlet(servletName, servletClass)
@ -628,11 +547,20 @@ class VaadinApplication : ExceptionHandler {
// // destroy the actors on session invalidation
// servletBuilder.addSessionListener(ActorSessionCleanup(coroutineHttpWrapper.actorsPerSession))
// NOTE: To use a DIFFERENT lock strategy (ie: one compatible with coroutines), start here
// flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java
// protected Lock lockSession(WrappedSession wrappedSession
// protected void unlockSession(WrappedSession wrappedSession, Lock lock) {
// and
// for custom lock storage
// * strategy override {@link #getSessionLock(WrappedSession)} and
// * {@link #setSessionLock(WrappedSession,Lock)} instead.
// configure how the servlet behaves
val servletSessionConfig = ServletSessionConfig()
// servletSessionConfig.isSecure = true // cookies are only possible when via HTTPS
servletSessionConfig.isSecure = secureService // cookies are only possible when via HTTPS
servletSessionConfig.sessionTrackingModes = setOf(SessionTrackingMode.COOKIE)
servletSessionConfig.name = sessionCookieName
servletBuilder.servletSessionConfig = servletSessionConfig
@ -661,7 +589,7 @@ class VaadinApplication : ExceptionHandler {
// instead of the default, we load **OUR** dev-mode initializer.
// The vaadin one is super buggy for custom environments
servletBuilder.addServletContainerInitializer(
ServletContainerInitializerInfo(DevModeInitializer::class.java, classSet))
ServletContainerInitializerInfo(com.vaadin.flow.server.startup.DevModeInitializer::class.java, classSet))
}
} else {
// do not load the dev-mode initializer for production mode
@ -750,20 +678,36 @@ class VaadinApplication : ExceptionHandler {
@Throws(IOException::class)
fun start() {
// make sure that the stats.json file is accessible
// the request will come in as 'VAADIN/config/stats.json' or '/VAADIN/config/stats.json'
//
// If stats.json DOES NOT EXIST, there will be infinite recursive lookups for this file.
val statsFile = "VAADIN/config/stats.json"
// if we don't have it defined, then we use the classloader.
val statsUrlFromConfig = vaadinConfig.statsUrl
if (statsUrlFromConfig.isEmpty()) {
val statsFile = "META-INF/resources/VAADIN/config/stats.json"
// our resource manager ONLY manages disk + jars!
if (diskTrie[statsFile] == null && jarStringTrie[statsFile] == null) {
throw IOException("Unable to startup the VAADIN webserver. The 'stats.json' definition file is not available. (Usually at '$statsFile'')" )
// in a roundabout way, this is how vaadin actually load the stats.json file.
// (it could be different, but this is the generic way vaadin does it)
if (VaadinContext::class.java.classLoader.getResource(statsFile) == null) {
throw IOException("Unable to startup the VAADIN webserver. The 'stats.json' definition file is not available. (Usually on the classloader at '$statsFile'')" )
}
logger.info("Loading the stats.json file via the classloader")
vaadinConfig.setupStatsJsonClassloader(servlet, statsFile)
} else {
// make sure that the stats.json file is accessible
// the request will come in as 'VAADIN/config/stats.json' or '/VAADIN/config/stats.json'
//
// If stats.json DOES NOT EXIST, there will be infinite recursive lookups for this file.
val statsFile = "VAADIN/config/stats.json"
// our resource manager ONLY manages disk + jars!
if (diskTrie[statsFile] == null && jarStringTrie[statsFile] == null) {
throw IOException("Unable to startup the VAADIN webserver. The 'stats.json' definition file is not available. (Usually at '$statsFile'')" )
}
val statsUrl = "$baseUrl/$statsFile"
logger.info("Loading the stats.json file via URL: $statsUrl")
vaadinConfig.setupStatsJsonUrl(servlet, statsUrl)
}
val statsUrl = "$baseUrl/$statsFile"
logger.info("Setting up stats file http location as: $statsUrl")
vaadinConfig.setupStatsJsonUrl(servlet, baseUrl)
undertowServer = serverBuilder.build()
@ -773,12 +717,12 @@ class VaadinApplication : ExceptionHandler {
servletManager = Servlets.defaultContainer().addDeployment(servletBuilder)
servletManager.deploy()
// TODO: adjust the session timeout (default is 30 minutes) from when the LAST heartbeat is detected
// manager.deployment.sessionManager.setDefaultSessionTimeout(TimeUnit.MINUTES.toSeconds(Args.webserver.sessionTimeout).toInt())
servletHttpHandler = servletManager.start()
// NOTE: look into SessionRestoringHandler to keep session state across re-deploys (this is normally not used in production). this might just be tricks with classloaders to keep sessions around
// we also want to save sessions to disk, and be able to read from them if we want See InMemorySessionManager (we would have to write our own)
@ -797,8 +741,6 @@ class VaadinApplication : ExceptionHandler {
*/
val threadGroup = ThreadGroup("Undertow Web Server")
// NOTE: we start this in a NEW THREAD so we can create and use a thread-group for all of the undertow threads created. This allows
// us to keep our main thread group "un-cluttered" when analyzing thread/stack traces.
//
@ -837,12 +779,6 @@ class VaadinApplication : ExceptionHandler {
}
try {
// servletBridge.shutdown();
// serverChannel.close().awaitUninterruptibly();
// bootstrap.releaseExternalResources();
// servletWebapp.destroy()
// allChannels.close().awaitUninterruptibly()
val worker = worker
if (worker != null) {
worker.shutdown()
@ -860,16 +796,11 @@ class VaadinApplication : ExceptionHandler {
fun handleRequest(exchange: HttpServerExchange) {
// dev-mode : incoming requests USUALLY start with a '/'
val path = exchange.relativePath
val isTraceEnabled = httpLogger.isTraceEnabled
if (isTraceEnabled) {
httpLogger.trace("REQUEST undertow: $path")
}
httpLogger.trace { "REQUEST undertow: $path" }
if (path.length == 1) {
if (isTraceEnabled) {
httpLogger.trace("REQUEST of length 1: $path")
}
httpLogger.trace { "REQUEST of length 1: $path" }
servletHttpHandler.handleRequest(exchange)
return
}
@ -881,9 +812,7 @@ class VaadinApplication : ExceptionHandler {
// our resource manager ONLY manages disk + jars!
val diskResourcePath: URL? = diskTrie[path]
if (diskResourcePath != null) {
if (isTraceEnabled) {
httpLogger.trace("URL DISK TRIE: $diskResourcePath")
}
httpLogger.trace { "URL DISK TRIE: $diskResourcePath" }
cacheHandler.handleRequest(exchange)
return
@ -891,21 +820,32 @@ class VaadinApplication : ExceptionHandler {
val jarResourcePath: String? = jarStringTrie[path]
if (jarResourcePath != null) {
if (isTraceEnabled) {
httpLogger.trace("URL JAR TRIE: $jarResourcePath")
}
httpLogger.trace { "URL JAR TRIE: $jarResourcePath" }
cacheHandler.handleRequest(exchange)
return
}
// this is the default, and will use the servlet to handle the request
if (isTraceEnabled) {
httpLogger.trace("Forwarding request to servlet")
}
httpLogger.trace { "Forwarding request to servlet" }
servletHttpHandler.handleRequest(exchange)
}
fun logStartupInfo() {
logger.info { "Temp dir: $tempDir" }
logger.info { "Launched from jar: $runningAsJar" }
logger.info { "Cached HTTP handlers: $enableCachedHandlers" }
if (vaadinConfig.devMode) {
logger.info { "Vaadin running in DEVELOPMENT mode" }
} else {
logger.info { "Vaadin running in PRODUCTION mode" }
}
logger.info { "Loader version: $version" }
logger.info { "Vaadin version: $vaadinVersion" }
}
/**
* Handles an exception. If this method returns true then the request/response cycle is considered to be finished,
* and no further action will take place, if this returns false then standard error page redirect will take place.

View File

@ -1,45 +0,0 @@
package dorkbox.vaadin.devMode
import com.vaadin.flow.server.startup.DevModeInitializer
import java.util.*
import javax.servlet.annotation.HandlesTypes
/**
* THIS IS COPIED DIRECTLY FROM VAADIN 14.7.1 (flow 2.7.1)
*
* CHANGES FROM DEFAULT ARE MANAGED AS DIFFERENT REVISIONS.
*
* The initial commit is exactly as-is from vaadin.
*
* This file is NOT extensible/configurable AT-ALL, so this is required...
*/
internal class DevModeClassFinder(classes: Set<Class<*>?>?) : com.vaadin.flow.server.frontend.scanner.ClassFinder.DefaultClassFinder(classes) {
companion object {
private val APPLICABLE_CLASS_NAMES = Collections.unmodifiableSet(calculateApplicableClassNames())
private fun calculateApplicableClassNames(): Set<String> {
val handlesTypes: HandlesTypes = DevModeInitializer::class.java.getAnnotation(HandlesTypes::class.java)
return handlesTypes.value.map { it.qualifiedName!! }.toSet()
}
}
override fun getAnnotatedClasses(annotation: Class<out Annotation?>): Set<Class<*>> {
ensureImplementation(annotation)
return super.getAnnotatedClasses(annotation)
}
override fun <T> getSubTypesOf(type: Class<T>): Set<Class<out T>> {
ensureImplementation(type)
return super.getSubTypesOf(type)
}
private fun ensureImplementation(clazz: Class<*>) {
require(APPLICABLE_CLASS_NAMES.contains(clazz.name)) {
("Unexpected class name "
+ clazz + ". Implementation error: the class finder "
+ "instance is not aware of this class. "
+ "Fix @HandlesTypes annotation value for "
+ DevModeInitializer::class.java.name)
}
}
}

View File

@ -1,933 +0,0 @@
/*
* Copyright 2000-2020 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package dorkbox.vaadin.devMode
import com.vaadin.flow.function.DeploymentConfiguration
import com.vaadin.flow.internal.BrowserLiveReload
import com.vaadin.flow.internal.Pair
import com.vaadin.flow.server.*
import com.vaadin.flow.server.communication.StreamRequestHandler
import com.vaadin.flow.server.frontend.FrontendTools
import com.vaadin.flow.server.frontend.FrontendUtils
import dorkbox.vaadin.devMode.HandlerHelper.isPathUnsafe
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.*
import java.net.HttpURLConnection
import java.net.ServerSocket
import java.net.URL
import java.nio.charset.StandardCharsets
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
import java.util.function.BiConsumer
import java.util.function.Consumer
import java.util.regex.Pattern
import javax.servlet.ServletOutputStream
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
/**
* THIS IS COPIED DIRECTLY FROM VAADIN 14.7.1 (flow 2.7.1)
*
* CHANGES FROM DEFAULT ARE MANAGED AS DIFFERENT REVISIONS.
*
* The initial commit is exactly as-is from vaadin.
*
* This file is NOT extensible/configurable AT-ALL, so this is required...
*
*
* Handles getting resources from `webpack-dev-server`.
*
*
* This class is meant to be used during developing time. For a production mode
* site `webpack` generates the static bundles that will be served
* directly from the servlet (using a default servlet if such exists) or through
* a stand alone static file server.
*
* By default it keeps updated npm dependencies and node imports before running
* webpack server
*
*
*
* For internal use only. May be renamed or removed in a future release.
*
* @since 2.0
*/
class DevModeHandler private constructor(
config: DeploymentConfiguration, runningPort: Int,
npmFolder: File, waitFor: CompletableFuture<Void?>
) : RequestHandler {
companion object {
private const val START_FAILURE = "Couldn't start dev server because"
private val atomicHandler = AtomicReference<DevModeHandler?>()
// webpack dev-server allows " character if passed through, need to
// explicitly check requests for it
private val WEBPACK_ILLEGAL_CHAR_PATTERN = Pattern
.compile("\"|%22")
// It's not possible to know whether webpack is ready unless reading output
// messages. When webpack finishes, it writes either a `Compiled` or a
// `Failed` in the last line
private const val DEFAULT_OUTPUT_PATTERN = ": Compiled."
private const val DEFAULT_ERROR_PATTERN = ": Failed to compile."
private const val FAILED_MSG = "\n------------------ Frontend compilation failed. -----------------"
private const val SUCCEED_MSG = "\n----------------- Frontend compiled successfully. -----------------"
private const val START = "\n------------------ Starting Frontend compilation. ------------------\n"
private const val END = "\n------------------------- Webpack stopped -------------------------\n"
private const val LOG_START = "Running webpack to compile frontend resources. This may take a moment, please stand by..."
private const val LOG_END = "Started webpack-dev-server. Time: {}ms"
// If after this time in millisecs, the pattern was not found, we unlock the
// process and continue. It might happen if webpack changes their output
// without advise.
private const val DEFAULT_TIMEOUT_FOR_PATTERN = "60000"
private const val DEFAULT_BUFFER_SIZE = 32 * 1024
private const val DEFAULT_TIMEOUT = 120 * 1000
private const val WEBPACK_HOST = "http://localhost"
/**
* The local installation path of the webpack-dev-server node script.
*/
const val WEBPACK_SERVER = "node_modules/webpack-dev-server/bin/webpack-dev-server.js"
// Matcher to match string starting with '/themes/[theme-name]/'
protected val APP_THEME_PATTERN = Pattern.compile("^\\/themes\\/[\\s\\S]+?\\/")
/**
* Start the dev mode handler if none has been started yet.
*
* @param configuration
* deployment configuration
* @param npmFolder
* folder with npm configuration files
* @param waitFor
* a completable future whose execution result needs to be
* available to start the webpack dev server
*
* @return the instance in case everything is alright, null otherwise
*/
fun start(
configuration: DeploymentConfiguration,
npmFolder: File, waitFor: CompletableFuture<Void?>
): DevModeHandler? {
return start(0, configuration, npmFolder, waitFor)
}
/**
* Start the dev mode handler if none has been started yet.
*
* @param runningPort
* port on which Webpack is listening.
* @param configuration
* deployment configuration
* @param npmFolder
* folder with npm configuration files
* @param waitFor
* a completable future whose execution result needs to be
* available to start the webpack dev server
*
* @return the instance in case everything is alright, null otherwise
*/
fun start(
runningPort: Int,
configuration: DeploymentConfiguration, npmFolder: File,
waitFor: CompletableFuture<Void?>
): DevModeHandler? {
if (configuration.isProductionMode
|| configuration.isCompatibilityMode
|| !configuration.enableDevServer()
) {
return null
}
var handler = atomicHandler.get()
if (handler == null) {
handler = createInstance(
runningPort, configuration, npmFolder,
waitFor
)
atomicHandler.compareAndSet(null, handler)
}
return devModeHandler
}
/**
* Get the instantiated DevModeHandler.
*
* @return devModeHandler or `null` if not started
*/
val devModeHandler: DevModeHandler?
get() = atomicHandler.get()
private fun createInstance(
runningPort: Int,
configuration: DeploymentConfiguration, npmFolder: File,
waitFor: CompletableFuture<Void?>
): DevModeHandler {
return DevModeHandler(
configuration, runningPort, npmFolder,
waitFor
)
}
// Using an short prefix so as webpack output is more readable
private val logger: Logger
private get() = // Using an short prefix so as webpack output is more readable
LoggerFactory.getLogger("dev-webpack")
private val runningDevServerPort: Int
private get() {
var port = 0
val portFile = LazyDevServerPortFileInit.DEV_SERVER_PORT_FILE
if (portFile.canRead()) {
try {
val portString = FileUtils
.readFileToString(portFile, StandardCharsets.UTF_8)
.trim { it <= ' ' }
if (!portString.isEmpty()) {
port = portString.toInt()
}
} catch (e: IOException) {
throw UncheckedIOException(e)
}
}
return port
}
/**
* Returns an available tcp port in the system.
*
* @return a port number which is not busy
*/
val freePort: Int
get() {
try {
ServerSocket(0).use { s ->
s.reuseAddress = true
return s.localPort
}
} catch (e: IOException) {
throw IllegalStateException(
"Unable to find a free port for running webpack", e
)
}
}
}
private var notified = false
private var cumulativeOutput = StringBuilder()
/**
* Return webpack console output when a compilation error happened.
*
* @return console output if error or null otherwise.
*/
@Volatile
var failedOutput: String? = null
private set
private val isDevServerFailedToStart = AtomicBoolean()
/**
* Get the live reload service instance.
*
* @return the live reload instance
*/
/**
* Set the live reload service instance.
*
* @param liveReload
* the live reload instance
*/
@Transient
var liveReload: BrowserLiveReload? = null
/**
* Get the listening port of the 'webpack-dev-server'.
*
* @return the listening port of webpack
*/
@Volatile
var port: Int
private set
private val webpackProcess = AtomicReference<Process>()
private val reuseDevServer: Boolean
private val watchDog = AtomicReference<DevServerWatchDog?>()
private var devServerStartFuture: CompletableFuture<Void?>? = null
private val npmFolder: File
init {
this.npmFolder = Objects.requireNonNull(npmFolder)
port = runningPort
reuseDevServer = config.reuseDevServer()
// Check whether executor is provided by the caller (framework)
val service = config.initParameters[Executor::class.java]
val action: BiConsumer<Void?, in Throwable> = BiConsumer { value: Void?, exception: Throwable? ->
// this will throw an exception if an exception has been thrown by
// the waitFor task
waitFor.getNow(null)
runOnFutureComplete(config)
}
devServerStartFuture = if (service is Executor) {
// if there is an executor use it to run the task
waitFor.whenCompleteAsync(
action,
service as Executor?
)
} else {
waitFor.whenCompleteAsync(action)
}
}
@Throws(IOException::class)
override fun handleRequest(
session: VaadinSession?, request: VaadinRequest?,
response: VaadinResponse
): Boolean {
return if (devServerStartFuture!!.isDone) {
try {
devServerStartFuture!!.getNow(null)
} catch (exception: CompletionException) {
isDevServerFailedToStart.set(true)
throw getCause(exception)
}
false
} else {
val inputStream = DevModeHandler::class.java.getResourceAsStream("dev-mode-not-ready.html")
IOUtils.copy(inputStream, response.outputStream)
response.setContentType("text/html;charset=utf-8")
true
}
}
private fun getCause(exception: Throwable?): RuntimeException {
return if (exception is CompletionException) {
getCause(exception.cause)
} else if (exception is RuntimeException) {
exception
} else {
IllegalStateException(exception)
}
}
/**
* Returns true if it's a request that should be handled by webpack.
*
* @param request
* the servlet request
* @return true if the request should be forwarded to webpack
*/
fun isDevModeRequest(request: HttpServletRequest): Boolean {
val pathInfo = request.pathInfo
return pathInfo != null && (pathInfo.startsWith("/" + Constants.VAADIN_MAPPING)
|| APP_THEME_PATTERN.matcher(pathInfo).find()) && !pathInfo
.startsWith("/" + StreamRequestHandler.DYN_RES_PREFIX)
}
/**
* Serve a file by proxying to webpack.
*
*
* Note: it considers the [HttpServletRequest.getPathInfo] that will
* be the path passed to the 'webpack-dev-server' which is running in the
* context root folder of the application.
*
*
* Method returns `false` immediately if dev server failed on its
* startup.
*
* @param request
* the servlet request
* @param response
* the servlet response
* @return false if webpack returned a not found, true otherwise
* @throws IOException
* in the case something went wrong like connection refused
*/
@Throws(IOException::class)
fun serveDevModeRequest(
request: HttpServletRequest,
response: HttpServletResponse
): Boolean {
// Do not serve requests if dev server starting or failed to start.
if (isDevServerFailedToStart.get() || !devServerStartFuture!!.isDone) {
return false
}
// Since we have 'publicPath=/VAADIN/' in webpack config,
// a valid request for webpack-dev-server should start with '/VAADIN/'
var requestFilename = request.pathInfo
if (isPathUnsafe(requestFilename)
|| WEBPACK_ILLEGAL_CHAR_PATTERN.matcher(requestFilename)
.find()
) {
logger.info(
"Blocked attempt to access file: {}",
requestFilename
)
response.status = HttpServletResponse.SC_FORBIDDEN
return true
}
// Redirect theme source request
if (APP_THEME_PATTERN.matcher(requestFilename).find()) {
requestFilename = "/VAADIN/static$requestFilename"
}
val connection = prepareConnection(
requestFilename,
request.method
)
// Copies all the headers from the original request
val headerNames = request.headerNames
while (headerNames.hasMoreElements()) {
val header = headerNames.nextElement()
connection.setRequestProperty(
header, // Exclude keep-alive
if ("Connect" == header) "close" else request.getHeader(header)
)
}
// Send the request
logger.debug(
"Requesting resource to webpack {}",
connection.url
)
val responseCode = connection.responseCode
if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
logger.debug(
"Resource not served by webpack {}",
requestFilename
)
// webpack cannot access the resource, return false so as flow can
// handle it
return false
}
logger.debug(
"Served resource by webpack: {} {}", responseCode,
requestFilename
)
// Copies response headers
connection.headerFields.forEach { (header: String?, values: List<String?>) ->
if (header != null) {
if ("Transfer-Encoding" == header) {
return@forEach
}
response.addHeader(header, values[0])
}
}
if (responseCode == HttpURLConnection.HTTP_OK) {
// Copies response payload
writeStream(
response.outputStream,
connection.inputStream
)
} else if (responseCode < 400) {
response.status = responseCode
} else {
// Copies response code
response.sendError(responseCode)
}
// Close request to avoid issues in CI and Chrome
response.outputStream.close()
return true
}
private fun checkWebpackConnection(): Boolean {
try {
prepareConnection("/", "GET").responseCode
return true
} catch (e: IOException) {
logger.debug(
"Error checking webpack dev server connection",
e
)
}
return false
}
/**
* Prepare a HTTP connection against webpack-dev-server.
*
* @param path
* the file to request
* @param method
* the http method to use
* @return the connection
* @throws IOException
* on connection error
*/
@Throws(IOException::class)
fun prepareConnection(path: String, method: String?): HttpURLConnection {
// path should have been checked at this point for any outside requests
val uri = URL(WEBPACK_HOST + ":" + port + path)
val connection = uri.openConnection() as HttpURLConnection
connection.requestMethod = method
connection.readTimeout = DEFAULT_TIMEOUT
connection.connectTimeout = DEFAULT_TIMEOUT
return connection
}
@Synchronized
private fun doNotify() {
if (!notified) {
notified = true
(this as Object).notifyAll() // NOSONAR
}
}
// mirrors a stream to logger, and check whether a success or error pattern
// is found in the output.
private fun logStream(
input: InputStream, success: Pattern,
failure: Pattern
) {
val thread = Thread {
val reader = BufferedReader(
InputStreamReader(input, StandardCharsets.UTF_8)
)
try {
readLinesLoop(success, failure, reader)
} catch (e: IOException) {
if ("Stream closed" == e.message) {
FrontendUtils.console(FrontendUtils.GREEN, END)
logger.debug(
"Exception when reading webpack output.",
e
)
} else {
logger.error(
"Exception when reading webpack output.",
e
)
}
}
// Process closed stream, means that it exited, notify
// DevModeHandler to continue
doNotify()
}
thread.isDaemon = true
thread.name = "webpack"
thread.start()
}
@Throws(IOException::class)
private fun readLinesLoop(
success: Pattern, failure: Pattern,
reader: BufferedReader
) {
var output = outputBuilder
val info = Consumer { s: String? ->
logger
.debug(String.format(FrontendUtils.GREEN, "{}"), s)
}
val error = Consumer { s: String? ->
logger
.error(String.format(FrontendUtils.RED, "{}"), s)
}
val warn = Consumer { s: String? ->
logger
.debug(String.format(FrontendUtils.YELLOW, "{}"), s)
}
var log = info
var line: String
while (reader.readLine().also { line = it } != null) {
val cleanLine = line // remove color escape codes for console
.replace("\u001b\\[[;\\d]*m".toRegex(), "") // remove babel query string which is confusing
.replace("\\?babel-target=[\\w\\d]+".toRegex(), "")
// write each line read to logger, but selecting its correct level
log = if (line.contains("WARNING")) warn else if (line.contains("ERROR")) error else if (isInfo(line, cleanLine)) info else log
log.accept(cleanLine)
// Only store webpack errors to be shown in the browser.
if (log == error) {
// save output so as it can be used to alert user in browser.
output.append(cleanLine).append(System.lineSeparator())
}
// save output so as it can be used to log exception if run fails
cumulativeOutput.append(cleanLine).append(System.lineSeparator())
val succeed = success.matcher(line).find()
val failed = failure.matcher(line).find()
// We found the success or failure pattern in stream
if (succeed || failed) {
log.accept(if (succeed) SUCCEED_MSG else FAILED_MSG)
// save output in case of failure
failedOutput = if (failed) output.toString() else null
// reset output and logger for the next compilation
output = outputBuilder
cumulativeOutput = StringBuilder()
log = info
// Notify DevModeHandler to continue
doNotify()
}
}
}
private fun isInfo(line: String, cleanLine: String): Boolean {
return line.trim { it <= ' ' }.isEmpty() || cleanLine.trim { it <= ' ' }.startsWith("i")
}
private val outputBuilder: StringBuilder
private get() {
val output = StringBuilder()
output.append(String.format("Webpack build failed with errors:%n"))
return output
}
@Throws(IOException::class)
private fun writeStream(
outputStream: ServletOutputStream,
inputStream: InputStream
) {
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var bytes: Int
while (inputStream.read(buffer).also { bytes = it } >= 0) {
outputStream.write(buffer, 0, bytes)
}
}
/**
* Remove the running port from the vaadinContext and temporary file.
*/
fun removeRunningDevServerPort() {
FileUtils.deleteQuietly(LazyDevServerPortFileInit.DEV_SERVER_PORT_FILE)
}
private fun runOnFutureComplete(config: DeploymentConfiguration) {
try {
doStartDevModeServer(config)
} catch (exception: ExecutionFailedException) {
logger.error(null, exception)
throw CompletionException(exception)
}
}
private fun saveRunningDevServerPort() {
val portFile = LazyDevServerPortFileInit.DEV_SERVER_PORT_FILE
try {
FileUtils.writeStringToFile(
portFile, port.toString(),
StandardCharsets.UTF_8
)
} catch (e: IOException) {
throw UncheckedIOException(e)
}
}
@Throws(ExecutionFailedException::class)
private fun doStartDevModeServer(config: DeploymentConfiguration) {
// If port is defined, means that webpack is already running
if (port > 0) {
check(checkWebpackConnection()) {
String.format(
"%s webpack-dev-server port '%d' is defined but it's not working properly",
START_FAILURE, port
)
}
reuseExistingPort(port)
return
}
port = runningDevServerPort
if (port > 0) {
port = if (checkWebpackConnection()) {
reuseExistingPort(port)
return
} else {
logger.warn(
"webpack-dev-server port '%d' is defined but it's not working properly. Using a new free port...",
port
)
0
}
}
// here the port == 0
val webPackFiles = validateFiles(npmFolder)
logger.info("Starting webpack-dev-server")
watchDog.set(DevServerWatchDog())
// Look for a free port
port = freePort
saveRunningDevServerPort()
var success = false
success = try {
doStartWebpack(config, webPackFiles)
} finally {
if (!success) {
removeRunningDevServerPort()
}
}
}
private fun doStartWebpack(
config: DeploymentConfiguration,
webPackFiles: Pair<File, File>
): Boolean {
val processBuilder = ProcessBuilder()
.directory(npmFolder)
val useHomeNodeExec = config.getBooleanProperty(
InitParameters.REQUIRE_HOME_NODE_EXECUTABLE, false
)
val tools = FrontendTools(
npmFolder.absolutePath,
{ FrontendUtils.getVaadinHomeDirectory().absolutePath },
useHomeNodeExec
)
tools.validateNodeAndNpmVersion()
var nodeExec: String? = null
nodeExec = if (useHomeNodeExec) {
tools.forceAlternativeNodeExecutable()
} else {
tools.nodeExecutable
}
val command = makeCommands(
config, webPackFiles.first,
webPackFiles.second, nodeExec
)
FrontendUtils.console(FrontendUtils.GREEN, START)
if (logger.isDebugEnabled) {
logger.debug(
FrontendUtils.commandToString(npmFolder.absolutePath, command)
)
}
val start = System.nanoTime()
processBuilder.command(command)
try {
webpackProcess.set(
processBuilder.redirectError(ProcessBuilder.Redirect.PIPE)
.redirectErrorStream(true).start()
)
// We only can save the webpackProcess reference the first time that
// the DevModeHandler is created. There is no way to store
// it in the servlet container, and we do not want to save it in the
// global JVM.
// We instruct the JVM to stop the webpack-dev-server daemon when
// the JVM stops, to avoid leaving daemons running in the system.
// NOTE: that in the corner case that the JVM crashes or it is
// killed
// the daemon will be kept running. But anyways it will also happens
// if the system was configured to be stop the daemon when the
// servlet context is destroyed.
Runtime.getRuntime().addShutdownHook(Thread { stop() })
val succeed = Pattern.compile(
config.getStringProperty(
InitParameters.SERVLET_PARAMETER_DEVMODE_WEBPACK_SUCCESS_PATTERN,
DEFAULT_OUTPUT_PATTERN
)
)
val failure = Pattern.compile(
config.getStringProperty(
InitParameters.SERVLET_PARAMETER_DEVMODE_WEBPACK_ERROR_PATTERN,
DEFAULT_ERROR_PATTERN
)
)
logStream(webpackProcess.get().inputStream, succeed, failure)
logger.info(LOG_START)
synchronized(this) {
(this as Object).wait(
config.getStringProperty( // NOSONAR
InitParameters.SERVLET_PARAMETER_DEVMODE_WEBPACK_TIMEOUT,
DEFAULT_TIMEOUT_FOR_PATTERN
).toInt().toLong()
)
}
if (!webpackProcess.get().isAlive) {
logger.error(
String.format(
"Webpack failed with the exception:%n%s",
cumulativeOutput.toString()
)
)
throw IllegalStateException("Webpack exited prematurely")
}
val ms = (System.nanoTime() - start) / 1000000
logger.info(LOG_END, ms)
saveRunningDevServerPort()
return true
} catch (e: IOException) {
logger.error("Failed to start the webpack process", e)
} catch (e: InterruptedException) {
logger.debug("Webpack process start has been interrupted", e)
}
return false
}
private fun reuseExistingPort(port: Int) {
logger.info(
"Reusing webpack-dev-server running at {}:{}",
WEBPACK_HOST, port
)
// Save running port for next usage
saveRunningDevServerPort()
watchDog.set(null)
}
private fun makeCommands(
config: DeploymentConfiguration,
webpack: File, webpackConfig: File, nodeExec: String?
): List<String?> {
val command: MutableList<String?> = ArrayList()
command.add(nodeExec)
command.add(webpack.absolutePath)
command.add("--config")
command.add(webpackConfig.absolutePath)
command.add("--port")
command.add(port.toString())
command.add("--watchDogPort=" + watchDog.get()!!.watchDogPort)
command.addAll(
Arrays.asList(
*config
.getStringProperty(
InitParameters.SERVLET_PARAMETER_DEVMODE_WEBPACK_OPTIONS,
"-d --inline=false"
)
.split(" +".toRegex()).toTypedArray()
)
)
return command
}
@Throws(ExecutionFailedException::class)
private fun validateFiles(npmFolder: File): Pair<File, File> {
assert(port == 0)
// Skip checks if we have a webpack-dev-server already running
val webpack = File(npmFolder, WEBPACK_SERVER)
val webpackConfig = File(npmFolder, FrontendUtils.WEBPACK_CONFIG)
if (!npmFolder.exists()) {
logger.warn("No project folder '{}' exists", npmFolder)
throw ExecutionFailedException(
START_FAILURE
+ " the target execution folder doesn't exist."
)
}
if (!webpack.exists()) {
logger.warn(
"'{}' doesn't exist. Did you run `npm install`?",
webpack
)
throw ExecutionFailedException(
String.format(
"%s '%s' doesn't exist. `npm install` has not run or failed.",
START_FAILURE, webpack
)
)
} else if (!webpack.canExecute()) {
logger.warn(
" '{}' is not an executable. Did you run `npm install`?",
webpack
)
throw ExecutionFailedException(
String.format(
"%s '%s' is not an executable."
+ " `npm install` has not run or failed.",
START_FAILURE, webpack
)
)
}
if (!webpackConfig.canRead()) {
logger.warn(
"Webpack configuration '{}' is not found or is not readable.",
webpackConfig
)
throw ExecutionFailedException(
String.format(
"%s '%s' doesn't exist or is not readable.",
START_FAILURE, webpackConfig
)
)
}
return Pair(webpack, webpackConfig)
}
/**
* Whether the 'webpack-dev-server' should be reused on servlet reload.
* Default true.
*
* @return true in case of reusing the server.
*/
fun reuseDevServer(): Boolean {
return reuseDevServer
}
/**
* Stop the webpack-dev-server.
*/
fun stop() {
if (atomicHandler.get() == null) {
return
}
try {
// The most reliable way to stop the webpack-dev-server is
// by informing webpack to exit. We have implemented in webpack a
// a listener that handles the stop command via HTTP and exits.
prepareConnection("/stop", "GET").responseCode
} catch (e: IOException) {
logger.debug(
"webpack-dev-server does not support the `/stop` command.",
e
)
}
val watchDogInstance = watchDog.get()
watchDogInstance?.stop()
val process = webpackProcess.get()
if (process != null && process.isAlive) {
process.destroy()
}
atomicHandler.set(null)
removeRunningDevServerPort()
}
/**
* Waits for the dev server to start.
*
*
* Suspends the caller's thread until the dev mode server is started (or
* failed to start).
*
* @see Thread.join
*/
fun join() {
devServerStartFuture!!.join()
}
private object LazyDevServerPortFileInit {
val DEV_SERVER_PORT_FILE = createDevServerPortFile()
private fun createDevServerPortFile(): File {
return try {
File.createTempFile("flow-dev-server", "port")
} catch (exception: IOException) {
throw UncheckedIOException(exception)
}
}
}
}

View File

@ -1,508 +0,0 @@
/*
* Copyright 2000-2020 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package dorkbox.vaadin.devMode
import com.vaadin.flow.component.WebComponentExporter
import com.vaadin.flow.component.WebComponentExporterFactory
import com.vaadin.flow.component.dependency.CssImport
import com.vaadin.flow.component.dependency.JavaScript
import com.vaadin.flow.component.dependency.JsModule
import com.vaadin.flow.component.dependency.NpmPackage
import com.vaadin.flow.function.DeploymentConfiguration
import com.vaadin.flow.router.HasErrorParameter
import com.vaadin.flow.router.Route
import com.vaadin.flow.server.*
import com.vaadin.flow.server.DevModeHandler
import com.vaadin.flow.server.frontend.FrontendUtils
import com.vaadin.flow.server.frontend.NodeTasks
import com.vaadin.flow.server.startup.ClassLoaderAwareServletContainerInitializer
import com.vaadin.flow.server.startup.ServletDeployer.StubServletConfig
import com.vaadin.flow.theme.NoTheme
import com.vaadin.flow.theme.Theme
import elemental.json.Json
import elemental.json.JsonObject
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.*
import java.lang.reflect.InvocationTargetException
import java.net.URL
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
import java.util.concurrent.Executor
import java.util.regex.Pattern
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.servlet.*
import javax.servlet.annotation.HandlesTypes
import javax.servlet.annotation.WebListener
/**
* THIS IS COPIED DIRECTLY FROM VAADIN 14.7.1 (flow 2.7.1)
*
* CHANGES FROM DEFAULT ARE MANAGED AS DIFFERENT REVISIONS.
*
* The initial commit is exactly as-is from vaadin.
*
* This file is NOT extensible/configurable AT-ALL, so this is required...
*
*
*
* Servlet initializer starting node updaters as well as the webpack-dev-mode
* server.
*
*
* For internal use only. May be renamed or removed in a future release.
*
* @since 2.0
*/
@HandlesTypes(
Route::class,
UIInitListener::class,
VaadinServiceInitListener::class,
WebComponentExporter::class,
WebComponentExporterFactory::class,
NpmPackage::class,
NpmPackage.Container::class,
JsModule::class,
JsModule.Container::class,
CssImport::class,
CssImport.Container::class,
JavaScript::class,
JavaScript.Container::class,
Theme::class,
NoTheme::class,
HasErrorParameter::class
)
@WebListener
class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializable, ServletContextListener {
@Throws(ServletException::class)
override fun process(classes: Set<Class<*>?>?, context: ServletContext) {
val registrations: Collection<ServletRegistration> = context.servletRegistrations.values
var vaadinServletRegistration: ServletRegistration? = null
for (registration in registrations) {
if (registration.className != null
&& isVaadinServletSubClass(registration.className)
) {
vaadinServletRegistration = registration
break
}
}
val config: DeploymentConfiguration
config = if (vaadinServletRegistration != null) {
StubServletConfig.createDeploymentConfiguration(
context,
vaadinServletRegistration, VaadinServlet::class.java
)
} else {
StubServletConfig.createDeploymentConfiguration(
context,
VaadinServlet::class.java
)
}
initDevModeHandler(classes, context, config)
}
private fun isVaadinServletSubClass(className: String): Boolean {
return try {
VaadinServlet::class.java.isAssignableFrom(Class.forName(className))
} catch (exception: ClassNotFoundException) { // NOSONAR
log().debug(
String.format(
"Servlet class name (%s) can't be found!",
className
)
)
false
}
}
override fun contextInitialized(ctx: ServletContextEvent) {
// No need to do anything on init
}
override fun contextDestroyed(ctx: ServletContextEvent) {
val handler = DevModeHandler.getDevModeHandler()
if (handler != null && !handler.reuseDevServer()) {
handler.stop()
}
}
companion object {
private val JAR_FILE_REGEX = Pattern.compile(".*file:(.+\\.jar).*")
// Path of jar files in a URL with zip protocol doesn't start with "zip:"
// nor "file:". It contains only the path of the file.
// Weblogic uses zip protocol.
private val ZIP_PROTOCOL_JAR_FILE_REGEX = Pattern.compile("(.+\\.jar).*")
private val VFS_FILE_REGEX = Pattern.compile("(vfs:/.+\\.jar).*")
private val VFS_DIRECTORY_REGEX = Pattern.compile("vfs:/.+")
// allow trailing slash
private val DIR_REGEX_FRONTEND_DEFAULT = Pattern.compile("^(?:file:0)?(.+)" + Constants.RESOURCES_FRONTEND_DEFAULT + "/?$")
// allow trailing slash
private val DIR_REGEX_RESOURCES_JAR_DEFAULT = Pattern.compile("^(?:file:0)?(.+)" + Constants.RESOURCES_THEME_JAR_DEFAULT+ "/?$")
// allow trailing slash
private val DIR_REGEX_COMPATIBILITY_FRONTEND_DEFAULT = Pattern.compile("^(?:file:)?(.+)" + Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT+ "/?$")
/**
* Initialize the devmode server if not in production mode or compatibility
* mode.
*
* @param classes
* classes to check for npm- and js modules
* @param context
* servlet context we are running in
* @param config
* deployment configuration
*
* @throws ServletException
* if dev mode can't be initialized
*/
@Throws(ServletException::class)
fun initDevModeHandler(classes: Set<Class<*>?>?, context: ServletContext?, config: DeploymentConfiguration) {
if (config.isProductionMode) {
log().debug("Skipping DEV MODE because PRODUCTION MODE is set.")
return
}
if (config.isCompatibilityMode) {
log().debug("Skipping DEV MODE because BOWER MODE is set.")
return
}
if (!config.enableDevServer()) {
log().debug("Skipping DEV MODE because dev server shouldn't be enabled.")
return
}
// these are BOTH set by VaadinApplication.kt
var baseDir = config.getStringProperty(FrontendUtils.PROJECT_BASEDIR, null)
if (baseDir == null) {
baseDir = baseDirectoryFallback
}
val generatedDir = System.getProperty(FrontendUtils.PARAM_GENERATED_DIR, FrontendUtils.DEFAULT_GENERATED_DIR)
val frontendFolder = config.getStringProperty(FrontendUtils.PARAM_FRONTEND_DIR,
System.getProperty(FrontendUtils.PARAM_FRONTEND_DIR, FrontendUtils.DEFAULT_FRONTEND_DIR))
val builder = NodeTasks.Builder(
DevModeClassFinder(classes),
File(baseDir), File(generatedDir),
File(frontendFolder)
)
log().info("Starting dev-mode updaters in {} folder.", builder.npmFolder)
if (!builder.generatedFolder.exists()) {
try {
FileUtils.forceMkdir(builder.generatedFolder)
} catch (e: IOException) {
throw UncheckedIOException(
String.format(
"Failed to create directory '%s'",
builder.generatedFolder
),
e
)
}
}
val generatedPackages = File(builder.generatedFolder, Constants.PACKAGE_JSON)
// Always update to auto-generated webpack configuration
builder.withWebpack(builder.npmFolder, FrontendUtils.WEBPACK_CONFIG, FrontendUtils.WEBPACK_GENERATED)
// If we are missing either the base or generated package json files
// generate those
if (!File(builder.npmFolder, Constants.PACKAGE_JSON).exists() || !generatedPackages.exists()) {
builder.createMissingPackageJson(true)
}
val frontendLocations = getFrontendLocationsFromClassloader(
DevModeInitializer::class.java.classLoader
)
val useByteCodeScanner = config.getBooleanProperty(
InitParameters.SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE,
java.lang.Boolean.parseBoolean(
System.getProperty(
InitParameters.SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE,
java.lang.Boolean.FALSE.toString()
)
)
)
val enablePnpm = config.isPnpmEnabled
val useHomeNodeExec = config.getBooleanProperty(InitParameters.REQUIRE_HOME_NODE_EXECUTABLE, false)
val vaadinContext: VaadinContext = VaadinServletContext(context)
val tokenFileData = Json.createObject()
try {
builder.enablePackagesUpdate(true)
.useByteCodeScanner(useByteCodeScanner)
.copyResources(frontendLocations)
.copyLocalResources(File(baseDir, Constants.LOCAL_FRONTEND_RESOURCES_PATH))
.enableImportsUpdate(true).runNpmInstall(true)
.populateTokenFileData(tokenFileData)
.withEmbeddableWebComponents(true).enablePnpm(enablePnpm)
.withHomeNodeExecRequired(useHomeNodeExec).build()
.execute()
val chunk = FrontendUtils.readFallbackChunk(tokenFileData)
if (chunk != null) {
vaadinContext.setAttribute(chunk)
}
} catch (exception: ExecutionFailedException) {
log().debug("Could not initialize dev mode handler. One of the node tasks failed", exception)
throw ServletException(exception)
}
val tasks = builder.enablePackagesUpdate(true)
.useByteCodeScanner(useByteCodeScanner)
.copyResources(frontendLocations)
.copyLocalResources(File(baseDir, Constants.LOCAL_FRONTEND_RESOURCES_PATH))
.enableImportsUpdate(true).runNpmInstall(true)
.populateTokenFileData(tokenFileData)
.withEmbeddableWebComponents(true).enablePnpm(enablePnpm)
.withHomeNodeExecRequired(useHomeNodeExec).build()
// Check whether executor is provided by the caller (framework)
val service = config.initParameters[Executor::class.java]
val runnable = Runnable {
runNodeTasks(
vaadinContext, tokenFileData,
tasks
)
}
val nodeTasksFuture = if (service is Executor) {
// if there is an executor use it to run the task
CompletableFuture.runAsync(runnable, service as Executor?)
} else {
CompletableFuture.runAsync(runnable)
}
DevModeHandler.start(config, builder.npmFolder, nodeTasksFuture)
}
private fun log(): Logger {
return LoggerFactory.getLogger(DevModeInitializer::class.java)
}
/**
* Accept user.dir or cwd as a fallback only if the directory seems to be a
* Maven or Gradle project. Check to avoid cluttering server directories
* (see tickets #8249, #8403).
*/
private val baseDirectoryFallback: String
private get() {
val baseDirCandidate = System.getProperty("user.dir", ".")
val path = Paths.get(baseDirCandidate)
return if (path.toFile().isDirectory
&& (path.resolve("pom.xml").toFile().exists()
|| path.resolve("build.gradle").toFile().exists())
) {
path.toString()
} else {
throw IllegalStateException(
String.format(
"Failed to determine project directory for dev mode. "
+ "Directory '%s' does not look like a Maven or "
+ "Gradle project. Ensure that you have run the "
+ "prepare-frontend Maven goal, which generates "
+ "'flow-build-info.json', prior to deploying your "
+ "application",
path.toString()
)
)
}
}
/**
* This method returns all folders of jar files having files in the
* META-INF/resources/frontend and META-INF/resources/themes folder. We
* don't use URLClassLoader because will fail in Java 9+
*/
@Throws(ServletException::class)
fun getFrontendLocationsFromClassloader(classLoader: ClassLoader): Set<File> {
val frontendFiles: MutableSet<File> = HashSet()
frontendFiles.addAll(
getFrontendLocationsFromClassloader(
classLoader,
Constants.RESOURCES_FRONTEND_DEFAULT
)
)
frontendFiles.addAll(
getFrontendLocationsFromClassloader(
classLoader,
Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT
)
)
frontendFiles.addAll(
getFrontendLocationsFromClassloader(
classLoader,
Constants.RESOURCES_THEME_JAR_DEFAULT
)
)
return frontendFiles
}
private fun runNodeTasks(vaadinContext: VaadinContext, tokenFileData: JsonObject, tasks: NodeTasks) {
try {
tasks.execute()
val chunk = FrontendUtils.readFallbackChunk(tokenFileData)
if (chunk != null) {
vaadinContext.setAttribute(chunk)
}
} catch (exception: ExecutionFailedException) {
log().debug("Could not initialize dev mode handler. One of the node tasks failed", exception)
throw CompletionException(exception)
}
}
@Throws(ServletException::class)
private fun getFrontendLocationsFromClassloader(
classLoader: ClassLoader, resourcesFolder: String
): Set<File> {
val frontendFiles: MutableSet<File> = HashSet()
try {
val en = classLoader.getResources(resourcesFolder) ?: return frontendFiles
val vfsJars: MutableSet<String> = HashSet()
while (en.hasMoreElements()) {
val url = en.nextElement()
val urlString = url.toString()
val path = URLDecoder.decode(url.path, StandardCharsets.UTF_8.name())
val jarMatcher = JAR_FILE_REGEX.matcher(path)
val zipProtocolJarMatcher = ZIP_PROTOCOL_JAR_FILE_REGEX.matcher(path)
val dirMatcher = DIR_REGEX_FRONTEND_DEFAULT.matcher(path)
val dirResourcesMatcher = DIR_REGEX_RESOURCES_JAR_DEFAULT.matcher(path)
val dirCompatibilityMatcher = DIR_REGEX_COMPATIBILITY_FRONTEND_DEFAULT.matcher(path)
val jarVfsMatcher = VFS_FILE_REGEX.matcher(urlString)
val dirVfsMatcher = VFS_DIRECTORY_REGEX.matcher(urlString)
if (jarVfsMatcher.find()) {
val vfsJar = jarVfsMatcher.group(1)
if (vfsJars.add(vfsJar)) frontendFiles.add(
getPhysicalFileOfJBossVfsJar(URL(vfsJar))
)
} else if (dirVfsMatcher.find()) {
val vfsDirUrl = URL(urlString.substring(0, urlString.lastIndexOf(resourcesFolder)))
frontendFiles.add(getPhysicalFileOfJBossVfsDirectory(vfsDirUrl))
} else if (jarMatcher.find()) {
frontendFiles.add(File(jarMatcher.group(1)))
} else if ("zip".equals(url.protocol, ignoreCase = true) && zipProtocolJarMatcher.find()
) {
frontendFiles.add(File(zipProtocolJarMatcher.group(1)))
} else if (dirMatcher.find()) {
frontendFiles.add(File(dirMatcher.group(1)))
} else if (dirResourcesMatcher.find()) {
frontendFiles.add(File(dirResourcesMatcher.group(1)))
} else if (dirCompatibilityMatcher.find()) {
frontendFiles.add(File(dirCompatibilityMatcher.group(1)))
} else {
log().warn("Resource {} not visited because does not meet supported formats.", url.path)
}
}
} catch (e: IOException) {
throw UncheckedIOException(e)
}
return frontendFiles
}
@Throws(IOException::class, ServletException::class)
private fun getPhysicalFileOfJBossVfsDirectory(url: URL): File {
return try {
val virtualFile = url.openConnection().content
val virtualFileClass: Class<*> = virtualFile.javaClass
// Reflection as we cannot afford a dependency to WildFly or JBoss
val getChildrenRecursivelyMethod = virtualFileClass.getMethod("getChildrenRecursively")
val getPhysicalFileMethod = virtualFileClass.getMethod("getPhysicalFile")
// By calling getPhysicalFile, we make sure that the corresponding
// physical files/directories of the root directory and its children
// are created. Later, these physical files are scanned to collect
// their resources.
val virtualFiles = getChildrenRecursivelyMethod.invoke(virtualFile) as List<*>
val rootDirectory = getPhysicalFileMethod.invoke(virtualFile) as File
for (child in virtualFiles) {
// side effect: create real-world files
getPhysicalFileMethod.invoke(child)
}
rootDirectory
} catch (exc: NoSuchMethodException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
} catch (exc: IllegalAccessException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
} catch (exc: InvocationTargetException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
}
}
@Throws(IOException::class, ServletException::class)
private fun getPhysicalFileOfJBossVfsJar(url: URL): File {
return try {
val jarVirtualFile = url.openConnection().content
// Creating a temporary jar file out of the vfs files
val vfsJarPath = url.toString()
val fileNamePrefix = vfsJarPath.substring(vfsJarPath.lastIndexOf('/') + 1, vfsJarPath.lastIndexOf(".jar"))
val tempJar = Files.createTempFile(fileNamePrefix, ".jar")
generateJarFromJBossVfsFolder(jarVirtualFile, tempJar)
val tempJarFile = tempJar.toFile()
tempJarFile.deleteOnExit()
tempJarFile
} catch (exc: NoSuchMethodException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
} catch (exc: IllegalAccessException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
} catch (exc: InvocationTargetException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
}
}
@Throws(IOException::class, IllegalAccessException::class, InvocationTargetException::class, NoSuchMethodException::class)
private fun generateJarFromJBossVfsFolder(jarVirtualFile: Any, tempJar: Path) {
// We should use reflection to use JBoss VFS API as we cannot afford a
// dependency to WildFly or JBoss
val virtualFileClass: Class<*> = jarVirtualFile.javaClass
val getChildrenRecursivelyMethod = virtualFileClass.getMethod("getChildrenRecursively")
val openStreamMethod = virtualFileClass.getMethod("openStream")
val isFileMethod = virtualFileClass.getMethod("isFile")
val getPathNameRelativeToMethod = virtualFileClass.getMethod("getPathNameRelativeTo", virtualFileClass)
val jarVirtualChildren = getChildrenRecursivelyMethod.invoke(jarVirtualFile) as List<*>
ZipOutputStream(Files.newOutputStream(tempJar)).use { zipOutputStream ->
for (child in jarVirtualChildren) {
if (!(isFileMethod.invoke(child) as Boolean)) continue
val relativePath = getPathNameRelativeToMethod.invoke(child, jarVirtualFile) as String
val inputStream = openStreamMethod.invoke(child) as InputStream
val zipEntry = ZipEntry(relativePath)
zipOutputStream.putNextEntry(zipEntry)
IOUtils.copy(inputStream, zipOutputStream)
zipOutputStream.closeEntry()
}
}
}
}
}

View File

@ -1,131 +0,0 @@
/*
* Copyright 2000-2020 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package dorkbox.vaadin.devMode
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.net.ServerSocket
import java.net.Socket
import java.nio.charset.StandardCharsets
/**
* THIS IS COPIED DIRECTLY FROM VAADIN 14.7.1 (flow 2.7.1)
*
* CHANGES FROM DEFAULT ARE MANAGED AS DIFFERENT REVISIONS.
*
* The initial commit is exactly as-is from vaadin.
*
* This file is NOT extensible/configurable AT-ALL, so this is required...
* Opens a server socket which is supposed to be opened until dev mode is active
* inside JVM.
*
*
* If this socket is closed then there is no anymore Java "client" for the
* webpack dev server and it should be stopped.
*
*
* For internal use only. May be renamed or removed in a future release.
*
* @author Vaadin Ltd
* @since 2.0
*/
internal class DevServerWatchDog {
private class WatchDogServer internal constructor() : Runnable {
internal var server: ServerSocket? = null
override fun run() {
while (!server!!.isClosed) {
try {
val accept = server!!.accept()
accept.soTimeout = 0
enterReloadMessageReadLoop(accept)
} catch (e: IOException) {
logger.debug(
"Error occurred during accept a connection", e
)
}
}
}
fun stop() {
val server = server
if (server != null) {
try {
server.close()
} catch (e: IOException) {
logger.debug(
"Error occurred during close the server socket", e
)
}
}
}
private val logger: Logger
private get() = LoggerFactory.getLogger(WatchDogServer::class.java)
@Throws(IOException::class)
private fun enterReloadMessageReadLoop(accept: Socket) {
val `in` = BufferedReader(
InputStreamReader(
accept.getInputStream(), StandardCharsets.UTF_8
)
)
var line: String
while (`in`.readLine().also { line = it } != null) {
val devModeHandler: DevModeHandler? = DevModeHandler.devModeHandler
if ("reload" == line) {
devModeHandler?.liveReload?.reload()
}
}
}
init {
try {
val server = ServerSocket(0)
this.server = server
server.soTimeout = 0
if (logger.isDebugEnabled) {
logger.debug(
"Watchdog server has started on port {}",
server.localPort
)
}
} catch (e: IOException) {
throw RuntimeException("Could not open a server socket", e)
}
}
}
private val watchDogServer: WatchDogServer
val watchDogPort: Int
get() = watchDogServer.server!!.localPort
fun stop() {
watchDogServer.stop()
}
init {
watchDogServer = WatchDogServer()
val serverThread = Thread(watchDogServer)
serverThread.isDaemon = true
serverThread.start()
}
}

View File

@ -1,212 +0,0 @@
/*
* Copyright 2000-2020 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package dorkbox.vaadin.devMode
import com.vaadin.flow.server.VaadinRequest
import com.vaadin.flow.server.VaadinService
import com.vaadin.flow.server.VaadinSession
import com.vaadin.flow.shared.ApplicationConstants
import java.io.Serializable
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.util.*
import java.util.function.BiConsumer
import java.util.regex.Pattern
/**
* THIS IS COPIED DIRECTLY FROM VAADIN 14.7.1 (flow 2.7.1)
*
* CHANGES FROM DEFAULT ARE MANAGED AS DIFFERENT REVISIONS.
*
* The initial commit is exactly as-is from vaadin.
*
* This file is NOT extensible/configurable AT-ALL, so this is required...
*
* Contains helper methods for [VaadinServlet] and generally for handling
* [VaadinRequests][VaadinRequest].
*
* @since 1.0
*/
object HandlerHelper : Serializable {
/**
* The default SystemMessages (read-only).
*/
// static final SystemMessages DEFAULT_SYSTEM_MESSAGES = new SystemMessages(); // NOTE: not compatible with how we pull out this class
/**
* The pattern of error message shown when the URL path contains unsafe
* double encoding.
*/
const val UNSAFE_PATH_ERROR_MESSAGE_PATTERN = "Blocked attempt to access file: {}"
private val PARENT_DIRECTORY_REGEX = Pattern
.compile("(/|\\\\)\\.\\.(/|\\\\)?", Pattern.CASE_INSENSITIVE)
/**
* Returns whether the given request is of the given type.
*
* @param request
* the request to check
* @param requestType
* the type to check for
* @return `true` if the request is of the given type,
* `false` otherwise
*/
fun isRequestType(
request: VaadinRequest,
requestType: RequestType
): Boolean {
return requestType.identifier == request
.getParameter(ApplicationConstants.REQUEST_TYPE_PARAMETER)
}
/**
* Helper to find the most most suitable Locale. These potential sources are
* checked in order until a Locale is found:
*
* 1. The passed component (or UI) if not null
* 1. [UI.getCurrent] if defined
* 1. The passed session if not null
* 1. [VaadinSession.getCurrent] if defined
* 1. The passed request if not null
* 1. [VaadinService.getCurrentRequest] if defined
* 1. [Locale.getDefault]
*
*
* @param session
* the session that is searched for locale or `null`
* if not available
* @param request
* the request that is searched for locale or `null`
* if not available
* @return the found locale
*/
fun findLocale(
session: VaadinSession?,
request: VaadinRequest?
): Locale {
var session = session
var request = request
if (session == null) {
session = VaadinSession.getCurrent()
}
if (session != null) {
val locale = session.locale
if (locale != null) {
return locale
}
}
if (request == null) {
request = VaadinService.getCurrentRequest()
}
if (request != null) {
val locale = request.locale
if (locale != null) {
return locale
}
}
return Locale.getDefault()
}
/**
* Sets no cache headers to the specified response.
*
* @param headerSetter
* setter for string value headers
* @param longHeaderSetter
* setter for long value headers
*/
fun setResponseNoCacheHeaders(
headerSetter: BiConsumer<String?, String?>,
longHeaderSetter: BiConsumer<String?, Long?>
) {
headerSetter.accept("Cache-Control", "no-cache, no-store")
headerSetter.accept("Pragma", "no-cache")
longHeaderSetter.accept("Expires", 0L)
}
/**
* Gets a relative path that cancels the provided path. This essentially
* adds one .. for each part of the path to cancel.
*
* @param pathToCancel
* the path that should be canceled
* @return a relative path that cancels out the provided path segment
*/
fun getCancelingRelativePath(pathToCancel: String): String {
val sb = StringBuilder(".")
// Start from i = 1 to ignore first slash
for (i in 1 until pathToCancel.length) {
if (pathToCancel[i] == '/') {
sb.append("/..")
}
}
return sb.toString()
}
/**
* Checks if the given URL path contains the directory change instruction
* (dot-dot), taking into account possible double encoding in hexadecimal
* format, which can be injected maliciously.
*
* @param path
* the URL path to be verified.
* @return `true`, if the given path has a directory change
* instruction, `false` otherwise.
*/
fun isPathUnsafe(path: String?): Boolean {
// Check that the path does not have '/../', '\..\', %5C..%5C,
// %2F..%2F, nor '/..', '\..', %5C.., %2F..
var path = path
path = try {
URLDecoder.decode(path, StandardCharsets.UTF_8.name())
} catch (e: UnsupportedEncodingException) {
throw RuntimeException(
"An error occurred during decoding URL.",
e
)
}
return PARENT_DIRECTORY_REGEX.matcher(path).find()
}
/**
* Framework internal enum for tracking the type of a request.
*/
enum class RequestType(
/**
* Returns the identifier for the request type.
*
* @return the identifier
*/
val identifier: String
) {
/**
* UIDL requests.
*/
UIDL(ApplicationConstants.REQUEST_TYPE_UIDL),
/**
* Heartbeat requests.
*/
HEARTBEAT(ApplicationConstants.REQUEST_TYPE_HEARTBEAT),
/**
* Push requests (any transport).
*/
PUSH(ApplicationConstants.REQUEST_TYPE_PUSH);
}
}

View File

@ -1,166 +0,0 @@
/*
* Copyright 2020 Dorkbox LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.vaadin.undertow
import dorkbox.vaadin.util.logger
import io.undertow.server.Connectors
import io.undertow.server.HandlerWrapper
import io.undertow.server.HttpHandler
import io.undertow.server.HttpServerExchange
import io.undertow.server.session.Session
import io.undertow.server.session.SessionListener
import io.undertow.util.AttachmentKey
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.sendBlocking
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
val ACTOR = AttachmentKey.create<SendChannel<HttpServerExchange>>(SendChannel::class.java)!!
/**
* This runs just BEFORE the main servlet handler, and lets us run the servlet logic on an actor. Each servlet has it's own actor,
* and can queue 8 "servlet" requests at a time
*/
class CoroutineHttpWrapper(private val sessionCookieName: String, private val capacity: Int, concurrencyFactor: Int) : HandlerWrapper {
private val logger = logger()
var actorsPerSession = ConcurrentHashMap<String, SendChannel<HttpServerExchange>>(8, 0.9f, concurrencyFactor)
private lateinit var defaultActor: SendChannel<HttpServerExchange>
// Our UI Needs to run on a different thread pool so that if the UI is blocked, it doesn't block our cache.
private val uiThreadPool = Executors.newCachedThreadPool(DaemonThreadFactory("HttpWrapper")) as ExecutorService
private val uiDispatcher = uiThreadPool.asCoroutineDispatcher()
private val handler = CoroutineExceptionHandler { _, exception ->
logger.error { "Uncaught Coroutine Error: $exception" }
}
private val scope = CoroutineScope(uiDispatcher + handler)
val job = Job()
// val mutex = Mutex()
@kotlinx.coroutines.ObsoleteCoroutinesApi
private fun createActor(handler: HttpHandler) = scope.actor<HttpServerExchange>(context = job, capacity = capacity) {
logger.info { "Starting actor $this" }
for (msg in channel) {
Connectors.executeRootHandler(handler, msg)
}
println("stopping actor $this")
}
override fun wrap(handler: HttpHandler): HttpHandler {
@kotlinx.coroutines.ObsoleteCoroutinesApi
defaultActor = createActor(handler)
return HttpHandler { exchange ->
exchange.startBlocking()
// IO is on a single thread
// how to check if we are running in the actor?
if (exchange.isInIoThread) {
// check if we've already created the actor for this exchange
var actor: SendChannel<HttpServerExchange> = exchange.getAttachment(ACTOR) ?: defaultActor
if (actor == defaultActor) {
actor = getOrCreateActor(handler, exchange)
exchange.putAttachment(ACTOR, actor)
}
// suppressed because we DO NOT use threading, we use coroutines, which are semantically different
@Suppress("DEPRECATION")
exchange.dispatch() // this marks the exchange as having been DISPATCHED
actor.sendBlocking(exchange)
}
else {
// if we are not in the IO thread, just process as normal
handler.handleRequest(exchange)
}
}
}
private fun getOrCreateActor(handler: HttpHandler, exchange: HttpServerExchange): SendChannel<HttpServerExchange> {
// we key off of the session ID for this. If this is changed, then we have to use whatever is different
@Suppress("MoveVariableDeclarationIntoWhen")
val sessionCookie = exchange.getRequestCookie(sessionCookieName)
return when (sessionCookie) {
null -> defaultActor
else -> {
// we have a session ID, so use that to create/use an actor
var maybeActor = actorsPerSession[sessionCookie.value]
if (maybeActor == null) {
@kotlinx.coroutines.ObsoleteCoroutinesApi
maybeActor = createActor(handler)
// pass coroutine info through java?
// https://stackoverflow.com/questions/51808992/kotlin-suspend-fun/51811597#51811597
// NOTE: For vaadin, we also have to create the lock via QUASAR !!!! since vaadin uses explicit locking (on a lock object) VaadinService.lockSession
// /*
// * No lock found in the session attribute. Ensure only one lock is
// * created and used by everybody by doing double checked locking.
// * Assumes there is a memory barrier for the attribute (i.e. that
// * the CPU flushes its caches and reads the value directly from main
// * memory).
// */
// synchronized (VaadinService.class) {
// lock = getSessionLock(wrappedSession);
// if (lock == null) {
// lock = new ReentrantLock();
// setSessionLock(wrappedSession, lock);
// }
// }
actorsPerSession[sessionCookie.value] = maybeActor
exchange.putAttachment(ACTOR, maybeActor)
}
maybeActor
}
}
}
suspend fun stop() = coroutineScope {
job.cancelAndJoin()
actorsPerSession.clear()
}
}
/**
* Destroys the actor when the session is destroyed
*/
class ActorSessionCleanup(private val map: MutableMap<String, SendChannel<HttpServerExchange>>) : SessionListener {
override fun sessionDestroyed(session: Session,
exchange: HttpServerExchange?,
reason: SessionListener.SessionDestroyedReason) {
// destroy the Actor and remove the session object
map.remove(session.id)?.close()
}
}

View File

@ -21,7 +21,9 @@ import java.util.concurrent.atomic.AtomicInteger
/**
* @param threadGroup the thread-group to assign, otherwise null (which will use the current thread's threadGroup)
*/
class DaemonThreadFactory(private val threadName: String, private val threadGroup: ThreadGroup? = null) : ThreadFactory {
class DaemonThreadFactory(private val threadName: String,
private val threadGroup: ThreadGroup? = null,
private val classLoader: ClassLoader? = null) : ThreadFactory {
private val threadCount = AtomicInteger(0)
override fun newThread(r: Runnable): Thread {
@ -33,6 +35,9 @@ class DaemonThreadFactory(private val threadName: String, private val threadGrou
thread.isDaemon = true
thread.name = threadName + "-" + threadCount.getAndIncrement()
if (classLoader != null) {
thread.contextClassLoader = classLoader
}
return thread
}

View File

@ -18,6 +18,7 @@
package dorkbox.vaadin.undertow
import dorkbox.vaadin.util.logger
import io.undertow.UndertowLogger
import io.undertow.io.IoCallback
import io.undertow.predicate.Predicate
@ -31,12 +32,12 @@ import io.undertow.server.handlers.cache.ResponseCache
import io.undertow.server.handlers.encoding.ContentEncodedResourceManager
import io.undertow.server.handlers.resource.*
import io.undertow.util.*
import org.slf4j.Logger
import java.io.File
import java.io.IOException
import java.nio.file.Paths
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
import java.util.concurrent.*
@Suppress("unused")
/**
@ -49,6 +50,7 @@ import java.util.concurrent.TimeUnit
*/
class DirectResourceHandler(@Volatile private var resourceManager: ResourceManager?,
@Volatile private var resourceSupplier: ResourceSupplier = DefaultResourceSupplier(resourceManager),
val logger: Logger,
private val next: HttpHandler = ResponseCodeHandler.HANDLE_404) : HttpHandler {
private val welcomeFiles = CopyOnWriteArrayList(arrayOf("index.html", "index.htm", "default.html", "default.htm"))
@ -451,12 +453,13 @@ class DirectResourceHandler(@Volatile private var resourceManager: ResourceManag
}
private class Wrapper internal constructor(private val location: String,
private val allowDirectoryListing: Boolean) : HandlerWrapper {
private class Wrapper(private val location: String,
private val allowDirectoryListing: Boolean) : HandlerWrapper {
override fun wrap(handler: HttpHandler): HttpHandler {
val rm = PathResourceManager(Paths.get(location), 1024)
val resourceHandler = DirectResourceHandler(rm)
val resourceManager = PathResourceManager(Paths.get(location), 1024)
// use a default logger
val resourceHandler = DirectResourceHandler(resourceManager, DefaultResourceSupplier(resourceManager), logger())
resourceHandler.setDirectoryListingEnabled(allowDirectoryListing)
return resourceHandler
}

View File

@ -0,0 +1,21 @@
package dorkbox.vaadin.undertow
import io.undertow.server.HttpHandler
import java.util.function.Function
class HandleBuilder private constructor(private val function: Function<HttpHandler, HttpHandler>) {
companion object {
fun begin(function: Function<HttpHandler, HttpHandler>): HandleBuilder {
return HandleBuilder(function)
}
}
fun next(before: Function<HttpHandler, HttpHandler>): HandleBuilder {
return HandleBuilder(function.compose(before))
}
fun complete(handler: HttpHandler): HttpHandler {
return function.apply(handler)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2024 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2012-2019 the original author or authors.
*
@ -15,12 +31,13 @@
*/
package dorkbox.vaadin.undertow
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
import dorkbox.fsm.DoubleArrayStringTrie
import io.undertow.UndertowMessages
import io.undertow.server.handlers.resource.Resource
import io.undertow.server.handlers.resource.ResourceChangeListener
import io.undertow.server.handlers.resource.ResourceManager
import io.undertow.server.handlers.resource.URLResource
import org.slf4j.Logger
import java.io.IOException
import java.net.URL
@ -31,25 +48,25 @@ import java.net.URL
* @author Andy Wilkinson
* @author Dorkbox LLC
*/
internal class JarResourceManager(val name: String, val trie: DoubleArrayTrie<URL>, val debug: Boolean) : ResourceManager {
internal class JarResourceManager(val name: String,
private val trie: DoubleArrayStringTrie<URL>,
private val logger: Logger) : ResourceManager {
@Throws(IOException::class)
override fun getResource(path: String): Resource? {
if (debug) {
println("REQUEST jar: $path")
}
val url = trie[path] ?: return null
if (debug) {
println("TRIE: $url")
val url = trie[path]
if (url === null) {
logger.trace("REQUEST not found for PATH: {}", path)
return null
}
val resource = URLResource(url, path)
if (path.isNotBlank() && path != "/" && resource.contentLength < 0) {
logger.trace("REQUEST file not found for PATH: {}, {}", path, url)
return null
}
logger.debug("REQUEST found: {}, {}", path, url)
return resource
}

View File

@ -0,0 +1,25 @@
package dorkbox.vaadin.undertow
import io.undertow.server.HttpServerExchange
import io.undertow.util.Headers
import io.undertow.util.StatusCodes
object Redirect {
fun temporary(exchange: HttpServerExchange, location: String) {
exchange.statusCode = StatusCodes.FOUND
exchange.responseHeaders.put(Headers.LOCATION, location)
exchange.endExchange()
}
fun permanent(exchange: HttpServerExchange, location: String) {
exchange.statusCode = StatusCodes.MOVED_PERMANENTLY
exchange.responseHeaders.put(Headers.LOCATION, location)
exchange.endExchange()
}
fun referer(exchange: HttpServerExchange) {
exchange.statusCode = StatusCodes.FOUND
exchange.responseHeaders.put(Headers.LOCATION, exchange.requestHeaders[Headers.REFERER, 0])
exchange.endExchange()
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2020 Dorkbox LLC
* Copyright 2024 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,14 +16,14 @@
package dorkbox.vaadin.undertow
import dorkbox.vaadin.util.logger
import io.undertow.UndertowMessages
import io.undertow.server.handlers.resource.Resource
import io.undertow.server.handlers.resource.ResourceChangeListener
import io.undertow.server.handlers.resource.ResourceManager
import mu.KotlinLogging
class ResourceCollectionManager(private val resources: List<ResourceManager>) : ResourceManager {
private val logger = KotlinLogging.logger {}
private val logger = logger()
private val changeListenerSupported : Boolean by lazy {
var supported = true
@ -77,7 +77,7 @@ class ResourceCollectionManager(private val resources: List<ResourceManager>) :
try {
it.close()
} catch (e: Exception) {
logger.error(e) { "Error closing resourceManager" }
logger.error("Error closing resourceManager", e)
}
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 dorkbox.vaadin.undertow
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
import io.undertow.UndertowMessages
import io.undertow.server.handlers.resource.FileResource
import io.undertow.server.handlers.resource.Resource
import io.undertow.server.handlers.resource.ResourceChangeListener
import io.undertow.server.handlers.resource.ResourceManager
import java.io.File
import java.io.IOException
import java.net.URL
/**
* [ResourceManager] for pre-scanned file resources.
*
* @author Dorkbox LLC
*/
internal class StrictFileResourceManager(val name: String, val trie: DoubleArrayTrie<URL>, val debug: Boolean) : io.undertow.server.handlers.resource.FileResourceManager(File(".")) {
@Throws(IOException::class)
override fun getResource(path: String): Resource? {
if (debug) {
println("REQUEST static: $path")
}
val url = trie[path] ?: return null
if (debug) {
println("TRIE: $url")
}
val resource = FileResource(File(url.file), this, path)
if (path.isNotBlank() && path != "/" && resource.contentLength < 0) {
return null
}
return resource
}
override fun isResourceChangeListenerSupported(): Boolean {
return false
}
override fun registerResourceChangeListener(listener: ResourceChangeListener) {
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported()
}
override fun removeResourceChangeListener(listener: ResourceChangeListener) {
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported()
}
@Throws(IOException::class)
override fun close() {
}
override fun toString(): String {
return "FileResourceManager($name)"
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright 2024 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://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 dorkbox.vaadin.undertow
import dorkbox.fsm.DoubleArrayStringTrie
import io.undertow.UndertowMessages
import io.undertow.server.handlers.resource.*
import org.slf4j.Logger
import java.io.File
import java.io.IOException
import java.net.URL
/**
* [ResourceManager] for pre-scanned file resources.
*
* @author Dorkbox LLC
*/
internal class TrieFileResourceManager(val name: String,
private val trie: DoubleArrayStringTrie<URL>,
private val logger: Logger,
base: File = File("").absoluteFile,
/**
* Size to use direct FS to network transfer (if supported by OS/JDK) instead of read/write
*/
transferMinSize: Long = 1024,
/**
* Check to validate caseSensitive issues for specific case-insensitive FS.
* @see io.undertow.server.handlers.resource.PathResourceManager#isFileSameCase(java.nio.file.Path, String)
*/
caseSensitive: Boolean = false,
/**
* Check to allow follow symbolic links
*/
followLinks: Boolean = false,
/**
* Used if followLinks == true. Set of paths valid to follow symbolic links. If this is empty and followLinks
* it true then all links will be followed
*/
vararg safePaths: String)
: FileResourceManager(base, transferMinSize, caseSensitive, followLinks, *safePaths) {
@Throws(IOException::class)
override fun getResource(path: String): Resource? {
val url = trie[path]
if (url === null) {
logger.trace("REQUEST not found for PATH: {}", path)
return null
}
val file = File(url.file)
val resource = FileResource(file, this, path)
if (path.isNotBlank() && path != "/" && resource.contentLength < 0) {
logger.trace("REQUEST file not found for PATH: {}, {}", path, file)
return null
}
logger.debug("REQUEST found: {}, {}", path, file)
return resource
}
override fun isResourceChangeListenerSupported(): Boolean {
return false
}
override fun registerResourceChangeListener(listener: ResourceChangeListener) {
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported()
}
override fun removeResourceChangeListener(listener: ResourceChangeListener) {
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported()
}
@Throws(IOException::class)
override fun close() {
}
override fun toString(): String {
return "FileResourceManager($name)"
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021 dorkbox, llc
* Copyright 2024 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,4 +17,22 @@ package dorkbox.vaadin.undertow
import java.net.URL
data class WebResource(val requestPath: String, val resourcePath: URL)
// stored in a set, and URL.toString() can cause a DNS lookup!
data class WebResource(val requestPath: String, val resourcePath: URL) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is WebResource) return false
if (requestPath != other.requestPath) return false
return true
}
override fun hashCode(): Int {
return requestPath.hashCode()
}
override fun toString(): String {
return "WebResource(requestPath='$requestPath')"
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021 dorkbox, llc
* Copyright 2024 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,4 +17,25 @@ package dorkbox.vaadin.undertow
import java.net.URL
data class WebResourceString(val requestPath: String, val resourcePath: URL, val relativeResourcePath: String, val resourceDir: URL)
// stored in a set, and URL.toString() can cause a DNS lookup!
data class WebResourceString(val requestPath: String, val resourcePath: URL, val relativeResourcePath: String, val resourceDir: URL) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is WebResourceString) return false
if (requestPath != other.requestPath) return false
if (relativeResourcePath != other.relativeResourcePath) return false
return true
}
override fun hashCode(): Int {
var result = requestPath.hashCode()
result = 31 * result + relativeResourcePath.hashCode()
return result
}
override fun toString(): String {
return "WebResourceString(requestPath='$requestPath', relativeResourcePath='$relativeResourcePath')"
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021 dorkbox, llc
* Copyright 2024 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,7 +16,7 @@
package dorkbox.vaadin.util
/**
*
* This class is overridden by the multi-source compile for java 9+
*/
object CallingClass {
fun get(): Class<*> {

View File

@ -1,3 +1,19 @@
/*
* Copyright 2023 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2017 Pronghorn Technology LLC
*
@ -16,16 +32,20 @@
package dorkbox.vaadin.util
import mu.KLogger
import mu.KotlinLogging
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.PrintWriter
import java.io.StringWriter
inline fun <reified T> T.logger(): KLogger {
inline fun <reified T> T.logger(): Logger {
if (T::class.isCompanion) {
return KotlinLogging.logger(T::class.java.enclosingClass.name)
return LoggerFactory.getLogger(T::class.java.enclosingClass.name)
}
return KotlinLogging.logger(T::class.java.name)
return LoggerFactory.getLogger(T::class.java.name)
}
inline fun <reified T> T.logger(name: String): Logger {
return LoggerFactory.getLogger(name)
}

View File

@ -1,3 +1,19 @@
/*
* Copyright 2023 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2017 Pronghorn Technology LLC
*
@ -16,7 +32,7 @@
package dorkbox.vaadin.util
import mu.KotlinLogging
import org.slf4j.LoggerFactory
private const val MAX_POW2 = 1 shl 30
@ -27,10 +43,13 @@ inline fun <reified T : Any> validatePowerOfTwoCapacity(caller: T,
value
}
else -> {
val logger = KotlinLogging.logger(caller.javaClass.name)
logger.warn {
"${caller.javaClass.simpleName} capacity should be a power of two, but ($value) requested. Using the next available: ${roundToNextPowerOfTwo(value)}."
val logger = if (T::class.isCompanion) {
LoggerFactory.getLogger(caller::class.java.enclosingClass.name)
} else {
LoggerFactory.getLogger(caller.javaClass.name)
}
logger.warn("${caller.javaClass.simpleName} capacity should be a power of two, but ($value) requested. Using the next available: ${roundToNextPowerOfTwo(value)}.")
roundToNextPowerOfTwo(value)
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021 dorkbox, llc
* Copyright 2024 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,38 +15,39 @@
*/
package dorkbox.vaadin.util
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
import dorkbox.fsm.DoubleArrayStringTrie
import org.slf4j.Logger
import java.net.URL
import java.net.URLClassLoader
/**
*
* Used to get classloader data, but using a trie to store the lookups
*/
class TrieClassLoader(
private val urlTrie: DoubleArrayTrie<URL>,
private val stringTrie: DoubleArrayTrie<String>,
urls: Array<URL>, parent: ClassLoader,
private val debug: Boolean = false): URLClassLoader(urls, parent) {
private val diskTrie: DoubleArrayStringTrie<URL>,
private val jarTrie: DoubleArrayStringTrie<String>,
jarResourceDirs: Array<URL>, parentClassloader: ClassLoader,
private val logger: Logger,
private val loggerDisk: Logger,
private val loggerJar: Logger,
): URLClassLoader(jarResourceDirs, parentClassloader) {
override fun getResource(name: String): URL? {
if (debug) {
println(" URL Classloader: $name")
}
logger.trace(name)
// check disk first
val diskResourcePath: URL? = urlTrie[name]
val diskResourcePath: URL? = diskTrie[name]
if (diskResourcePath != null) {
if (debug) {
println("TRIE: $diskResourcePath")
}
loggerDisk.trace(diskResourcePath.file)
return diskResourcePath
}
val jarResourcePath: String? = stringTrie[name]
val jarResourcePath: String? = jarTrie[name]
if (jarResourcePath != null) {
if (debug) {
println("TRIE: $jarResourcePath")
}
loggerJar.trace(jarResourcePath)
return super.getResource(jarResourcePath)
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021 dorkbox, llc
* Copyright 2023 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,16 +18,38 @@ package dorkbox.vaadin.util
import io.undertow.Undertow
import io.undertow.Undertow.ListenerBuilder
import io.undertow.connector.ByteBufferPool
import io.undertow.security.api.AuthenticationMechanism
import io.undertow.security.api.AuthenticationMode
import io.undertow.security.handlers.AuthenticationCallHandler
import io.undertow.security.handlers.AuthenticationConstraintHandler
import io.undertow.security.handlers.AuthenticationMechanismsHandler
import io.undertow.security.handlers.SecurityInitialHandler
import io.undertow.security.idm.IdentityManager
import io.undertow.security.impl.BasicAuthenticationMechanism
import io.undertow.server.HttpHandler
import org.xnio.Option
import org.xnio.XnioWorker
import java.util.concurrent.Executor
import java.util.concurrent.*
import javax.net.ssl.KeyManager
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
@Suppress("unused")
class UndertowBuilder() {
companion object {
fun addSecurity(toWrap: HttpHandler, identityManager: IdentityManager): HttpHandler {
var handler = toWrap
handler = AuthenticationCallHandler(handler)
handler = AuthenticationConstraintHandler(handler)
val mechanisms = listOf<AuthenticationMechanism>(BasicAuthenticationMechanism("My Realm"))
handler = AuthenticationMechanismsHandler(handler, mechanisms)
handler = SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, identityManager, handler)
return handler
}
}
private val builder = Undertow.builder()
// the LAST http (or https) listener will recorded as the listener used.
@ -153,7 +175,6 @@ class UndertowBuilder() {
* to the various worker-related configuration (ioThreads, workerThreads, workerOptions)
* when [Undertow.start] is called.
* Additionally, this newly created worker will be shutdown when [Undertow.stop] is called.
* <br></br>
*
*
* When non-null, the provided [XnioWorker] will be reused instead of creating a new [XnioWorker]
@ -162,7 +183,7 @@ class UndertowBuilder() {
* Essentially, the lifecycle of the provided worker must be maintained outside of the [Undertow] instance.
*/
fun setWorker(worker: XnioWorker?): UndertowBuilder {
builder.setWorker<Any>(worker)
builder.setWorker(worker)
return this
}
@ -172,7 +193,7 @@ class UndertowBuilder() {
}
fun setByteBufferPool(byteBufferPool: ByteBufferPool): UndertowBuilder {
builder.setByteBufferPool<Any>(byteBufferPool)
builder.setByteBufferPool(byteBufferPool)
return this
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2021 dorkbox, llc
* Copyright 2023 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,27 +28,37 @@ import java.io.File
*/
class VaadinConfig(runningAsJar: Boolean, tempDir: File) {
companion object {
// this must match the version information in the build.gradle.kts file
const val VAADIN_VERSION = "14.7.6"
// these are string parameters that are passed in via compile options
val EXTRACT_JAR = "extract.jar"
val EXTRACT_JAR_OVERWRITE = "extract.jar.overwrite"
val DEBUG = "debug"
val STATS_URL = "stats_url"
}
val tokenFileName: String
val devMode: Boolean
val pNpmEnabled: Boolean
// option to extract files or to load from jar only. This is a performance option.
/**
* option to extract files or to load from jar only. This is a performance option.
*/
val extractFromJar: Boolean
// option to force files to be overwritten when `extractFromJar` is true
/**
* option to force files to be overwritten when `extractFromJar` is true
*/
val forceExtractOverwrite: Boolean
// option to permit us to fully debug the vaadin application. This must be set during the token compile phase
/**
* option to permit us to fully debug the vaadin application. This must be set during the token compile phase
*/
val debug: Boolean
/**
* This option allows us to customize how, and where the stats.json file is loaded - specifically via HTTP/s requests from a different (or the same) server. For example: "VAADIN/config/stats.json"
*/
var statsUrl: String = ""
init {
// find the config/stats.json to see what mode (PRODUCTION or DEV) we should run in.
// we COULD just check the existence of this file...
@ -64,11 +74,8 @@ class VaadinConfig(runningAsJar: Boolean, tempDir: File) {
// this file from the jar to the temp location.
File(tempDir, defaultTokenFile).absolutePath
} else {
if (tokenInJar.path.startsWith("/")) {
tokenInJar.path.substring(1)
} else {
tokenInJar.path
}
// loading as a FILE, so we must make sure
tokenInJar.path
}
tokenJson = JsonUtil.parse(tokenInJar.readText(Charsets.UTF_8)) as JsonObject?
@ -87,15 +94,20 @@ class VaadinConfig(runningAsJar: Boolean, tempDir: File) {
if (tokenFileName.isEmpty() || tokenJson == null || !tokenJson.hasKey(InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE)) {
// this is a problem! we must configure the system first via gradle!
throw java.lang.RuntimeException("Unable to continue! Error reading token!" +
"You must FIRST compile the vaadin resources for DEV or PRODUCTION mode!")
throw java.lang.RuntimeException("Unable to continue! Error reading token! " +
"You must FIRST compile the vaadin resources for Development or Production mode!")
}
devMode = !tokenJson.getBoolean(InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE)
pNpmEnabled = tokenJson.getBoolean(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM)
extractFromJar = getBoolean(tokenJson, EXTRACT_JAR)
forceExtractOverwrite = getBoolean(tokenJson, EXTRACT_JAR_OVERWRITE, false)
debug = getBoolean(tokenJson, EXTRACT_JAR_OVERWRITE, false)
debug = getBoolean(tokenJson, DEBUG, false)
statsUrl = getString(tokenJson, STATS_URL, "")
if (devMode && runningAsJar) {
throw RuntimeException("Invalid run configuration. It is not possible to run DEV MODE from a deployed jar.\n" +
@ -116,6 +128,10 @@ class VaadinConfig(runningAsJar: Boolean, tempDir: File) {
return if (tokenJson.hasKey(tokenName)) tokenJson.getBoolean(tokenName) else defaultValue
}
fun getString(tokenJson: JsonObject, tokenName: String, defaultValue: String = ""): String {
return if (tokenJson.hasKey(tokenName)) tokenJson.getString(tokenName) else defaultValue
}
fun addServletInitParameters(servlet: ServletInfo) {
servlet
.addInitParam("productionMode", (!devMode).toString()) // this is set via the gradle build
@ -124,13 +140,22 @@ class VaadinConfig(runningAsJar: Boolean, tempDir: File) {
.addInitParam(FrontendUtils.PARAM_TOKEN_FILE, tokenFileName)
}
/**
* loads the stats file as a URL
*/
fun setupStatsJsonUrl(servlet: ServletInfo, url: String) {
// load the stats file as a URL, NOT via the classloader!
// this is because in java9+, the classpath/module "mess" complicates the accessibility of this file.
// the stats.json http request will be coming from the local box (so everything is local. Only when on a specific IP should that specific IP be used)
servlet
.addInitParam(InitParameters.EXTERNAL_STATS_FILE, "true")
.addInitParam(InitParameters.EXTERNAL_STATS_URL, "$url")
.addInitParam(InitParameters.EXTERNAL_STATS_URL, url)
}
/**
* loads the stats file via the classloader.
*/
fun setupStatsJsonClassloader(servlet: ServletInfo, statsFile: String) {
// the relative path on disk. The default is invalid because we have custom vaadin compile logic,
// so the file is located in a different location
servlet.addInitParam(InitParameters.SERVLET_PARAMETER_STATISTICS_JSON, statsFile)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,202 +0,0 @@
/*
* AhoCorasickDoubleArrayTrie Project
* https://github.com/hankcs/AhoCorasickDoubleArrayTrie
*
* Copyright 2008-2018 hankcs <me@hankcs.com>
* You may modify and redistribute as long as this attribution remains.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.vaadin.util.ahoCorasick
import java.util.*
/**
*
*
* A state has the following functions
*
*
*
*
* * success; successfully transferred to another state
* * failure; if you cannot jump along the string, jump to a shallow node
* * emits; hit a pattern string
*
*
*
*
*
* The root node is slightly different. The root node has no failure function. Its "failure" refers to moving to the next state according to the string path. Other nodes have a failure state.
*
*
* @author Robert Bor
*/
class State
/**
* Construct a node with a depth of depth
*/
@JvmOverloads constructor(
/**
* The length of the pattern string is also the depth of this state
*/
/**
* Get node depth
*/
val depth: Int = 0) {
/**
* The fail function, if there is no match, jumps to this state.
*/
private var failure: State? = null
/**
* Record mode string as long as this state is reachable
*/
private var emits: MutableSet<Int>? = null
/**
* The goto table, also known as the transfer function. Move to the next state based on the next character of the string
*/
private val success = TreeMap<Char, State>()
/**
* Corresponding subscript in double array
*/
var index: Int = 0
/**
* Get the largest value
*/
val largestValueId: Int?
get() = if (emits == null || emits!!.size == 0) {
null
}
else emits!!.iterator().next()
/**
* Whether it is the termination status
*/
val isAcceptable: Boolean
get() = this.depth > 0 && this.emits != null
val states: Collection<State>
get() = this.success.values
val transitions: Collection<Char>
get() = this.success.keys
/**
* Add a matching pattern string (this state corresponds to this pattern string)
*/
fun addEmit(keyword: Int) {
if (this.emits == null) {
this.emits = TreeSet(Collections.reverseOrder())
}
this.emits!!.add(keyword)
}
/**
* Add some matching pattern strings
*/
fun addEmit(emits: Collection<Int>) {
for (emit in emits) {
addEmit(emit)
}
}
/**
* Get the pattern string represented by this node (we)
*/
fun emit(): Collection<Int> {
return this.emits ?: emptyList()
}
/**
* Get the failure status
*/
fun failure(): State? {
return this.failure
}
/**
* Set the failure status
*/
fun setFailure(failState: State,
fail: IntArray) {
this.failure = failState
fail[index] = failState.index
}
/**
* Move to the next state
*
* @param character wants to transfer by this character
* @param ignoreRootState Whether to ignore the root node, it should be true if the root node calls itself, otherwise it is false
*
* @return transfer result
*/
private fun nextState(character: Char,
ignoreRootState: Boolean): State? {
var nextState: State? = this.success[character]
if (!ignoreRootState && nextState == null && this.depth == 0) {
nextState = this
}
return nextState
}
/**
* According to the character transfer, the root node transfer failure will return itself (never return null)
*/
fun nextState(character: Char): State? {
return nextState(character, false)
}
/**
* According to character transfer, any node transfer failure will return null
*/
fun nextStateIgnoreRootState(character: Char): State? {
return nextState(character, true)
}
fun addState(character: Char): State {
var nextState = nextStateIgnoreRootState(character)
if (nextState == null) {
nextState = State(this.depth + 1)
this.success[character] = nextState
}
return nextState
}
override fun toString(): String {
val sb = StringBuilder("State{")
sb.append("depth=").append(depth)
sb.append(", ID=").append(index)
sb.append(", emits=").append(emits)
sb.append(", success=").append(success.keys)
sb.append(", failureID=").append(if (failure == null) "-1" else failure!!.index)
sb.append(", failure=").append(failure)
sb.append('}')
return sb.toString()
}
/**
* Get goto table
*/
fun getSuccess(): Map<Char, State> {
return success
}
}
/**
* Construct a node with a depth of 0
*/

View File

@ -2,74 +2,73 @@ module dorkbox.vaadin {
exports dorkbox.vaadin;
exports dorkbox.vaadin.util;
requires dorkbox.updates;
requires org.slf4j;
requires transitive dorkbox.updates;
requires transitive org.slf4j;
// requires static ch.qos.logback.classic;
// requires vaadin;
//Vaadin UI components used
requires flow.data;
requires flow.server;
requires vaadin.accordion.flow;
requires vaadin.accordion;
requires vaadin.app.layout.flow;
requires vaadin.app.layout;
requires vaadin.button.flow;
requires vaadin.button;
requires vaadin.charts.flow;
requires vaadin.charts;
requires vaadin.checkbox.flow;
requires vaadin.checkbox;
requires vaadin.combo.box.flow;
requires vaadin.combo.box;
requires vaadin.confirm.dialog.flow;
requires vaadin.confirm.dialog;
requires vaadin.context.menu.flow;
requires vaadin.context.menu;
requires vaadin.control.state.mixin;
requires vaadin.cookie.consent.flow;
requires vaadin.cookie.consent;
requires vaadin.core;
requires vaadin.custom.field.flow;
requires vaadin.custom.field;
requires vaadin.date.picker.flow;
requires vaadin.date.picker;
requires vaadin.details.flow;
requires vaadin.details;
requires vaadin.development.mode.detector;
requires vaadin.dialog.flow;
requires vaadin.dialog;
requires vaadin.element.mixin;
requires vaadin.grid.flow;
requires vaadin.list.box.flow;
requires vaadin.list.box;
requires vaadin.list.mixin;
requires vaadin.login.flow;
requires vaadin.login;
requires vaadin.lumo.theme;
requires vaadin.ordered.layout.flow;
requires vaadin.ordered.layout;
requires vaadin.progress.bar.flow;
requires vaadin.progress.bar;
requires vaadin.radio.button.flow;
requires vaadin.radio.button;
requires vaadin.text.field.flow;
requires vaadin.text.field;
requires vaadin.upload.flow;
requires vaadin.upload;
requires vaadin.usage.statistics;
requires transitive flow.data;
requires transitive flow.server;
requires transitive vaadin.accordion.flow;
requires transitive vaadin.accordion;
requires transitive vaadin.app.layout.flow;
requires transitive vaadin.app.layout;
requires transitive vaadin.button.flow;
requires transitive vaadin.button;
requires transitive vaadin.charts.flow;
requires transitive vaadin.charts;
requires transitive vaadin.checkbox.flow;
requires transitive vaadin.checkbox;
requires transitive vaadin.combo.box.flow;
requires transitive vaadin.combo.box;
requires transitive vaadin.confirm.dialog.flow;
requires transitive vaadin.confirm.dialog;
requires transitive vaadin.context.menu.flow;
requires transitive vaadin.context.menu;
requires transitive vaadin.control.state.mixin;
requires transitive vaadin.cookie.consent.flow;
requires transitive vaadin.cookie.consent;
requires transitive vaadin.core;
requires transitive vaadin.custom.field.flow;
requires transitive vaadin.custom.field;
requires transitive vaadin.date.picker.flow;
requires transitive vaadin.date.picker;
requires transitive vaadin.details.flow;
requires transitive vaadin.details;
requires transitive vaadin.development.mode.detector;
requires transitive vaadin.dialog.flow;
requires transitive vaadin.dialog;
requires transitive vaadin.element.mixin;
requires transitive vaadin.grid.flow;
requires transitive vaadin.list.box.flow;
requires transitive vaadin.list.box;
requires transitive vaadin.list.mixin;
requires transitive vaadin.login.flow;
requires transitive vaadin.login;
requires transitive vaadin.lumo.theme;
requires transitive vaadin.ordered.layout.flow;
requires transitive vaadin.ordered.layout;
requires transitive vaadin.progress.bar.flow;
requires transitive vaadin.progress.bar;
requires transitive vaadin.radio.button.flow;
requires transitive vaadin.radio.button;
requires transitive vaadin.text.field.flow;
requires transitive vaadin.text.field;
requires transitive vaadin.upload.flow;
requires transitive vaadin.upload;
requires transitive vaadin.usage.statistics;
requires xnio.api;
requires transitive xnio.api;
// requires com.conversantmedia.disruptor;
requires io.github.classgraph;
requires transitive io.github.classgraph;
requires undertow.core;
requires undertow.servlet;
requires undertow.websockets.jsr;
requires transitive undertow.core;
requires transitive undertow.servlet;
requires transitive undertow.websockets.jsr;
requires kotlin.stdlib;
requires java.base;
requires transitive kotlin.stdlib;
}