Compare commits
32 Commits
Version_14
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
7572d4f407 | ||
|
f1c97a983c | ||
|
a72efb6ed1 | ||
|
947947ea2e | ||
|
6703932dd8 | ||
|
3cb5a8fb20 | ||
|
1f2efa40e9 | ||
|
11bb2330e6 | ||
|
d9b88c4759 | ||
|
20559c8240 | ||
|
fd2e926fbb | ||
|
0538b93480 | ||
|
0462bf600d | ||
|
572f97d58f | ||
|
b7d41d4da3 | ||
|
0bc08c6de2 | ||
|
3bb263e549 | ||
|
bc4512eab7 | ||
|
ce507b5897 | ||
|
ff41833f4a | ||
|
1cf792fb0a | ||
|
cbc3ceb25e | ||
|
d89eda0bfd | ||
|
de5fac04bc | ||
|
3220aa761e | ||
|
198775ea51 | ||
|
792e35d1a7 | ||
|
3f3c4823a2 | ||
|
70379ef0c5 | ||
|
24c9928b8a | ||
|
334dc34b9b | ||
|
fdb5af2123 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -61,6 +61,7 @@ fabric.properties
|
|||||||
# From https://github.com/github/gitignore/blob/master/Gradle.gitignore
|
# From https://github.com/github/gitignore/blob/master/Gradle.gitignore
|
||||||
.gradle
|
.gradle
|
||||||
/build/
|
/build/
|
||||||
|
/frontend/generated/
|
||||||
|
|
||||||
# Ignore Gradle GUI config
|
# Ignore Gradle GUI config
|
||||||
gradle-app.setting
|
gradle-app.setting
|
||||||
|
79
LICENSE
79
LICENSE
@ -1,7 +1,7 @@
|
|||||||
- VaadinUndertow - Vaadin support for the Undertow web server
|
- VaadinUndertow - Vaadin support for the Undertow web server
|
||||||
[The Apache Software License, Version 2.0]
|
[The Apache Software License, Version 2.0]
|
||||||
https://git.dorkbox.com/dorkbox/VaadinUndertow
|
https://git.dorkbox.com/dorkbox/VaadinUndertow
|
||||||
Copyright 2022
|
Copyright 2023
|
||||||
Dorkbox LLC
|
Dorkbox LLC
|
||||||
|
|
||||||
Extra license information
|
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
|
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
|
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 2022
|
|
||||||
Vaadin Ltd.
|
|
||||||
|
|
||||||
- Logback - Logback is a logging framework for Java applications
|
|
||||||
[The Apache Software License, Version 2.0]
|
|
||||||
http://logback.qos.ch
|
|
||||||
Copyright 2022
|
|
||||||
QOS.ch
|
|
||||||
|
|
||||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||||
[The Apache Software License, Version 2.0]
|
[The Apache Software License, Version 2.0]
|
||||||
https://github.com/Kotlin/kotlinx.coroutines
|
https://github.com/Kotlin/kotlinx.coroutines
|
||||||
Copyright 2022
|
Copyright 2023
|
||||||
JetBrains s.r.o.
|
JetBrains s.r.o.
|
||||||
|
|
||||||
- Undertow - High performance non-blocking webserver
|
- Undertow - High performance non-blocking webserver
|
||||||
[The Apache Software License, Version 2.0]
|
[The Apache Software License, Version 2.0]
|
||||||
https://github.com/undertow-io/undertow
|
https://github.com/undertow-io/undertow
|
||||||
Copyright 2022
|
Copyright 2023
|
||||||
JBoss
|
JBoss
|
||||||
Red Hat, Inc.
|
Red Hat, Inc.
|
||||||
Individual contributors as listed in files
|
Individual contributors as listed in files
|
||||||
@ -64,27 +52,40 @@
|
|||||||
- ClassGraph - An uber-fast parallelized Java classpath scanner and module scanner
|
- ClassGraph - An uber-fast parallelized Java classpath scanner and module scanner
|
||||||
[The Apache Software License, Version 2.0]
|
[The Apache Software License, Version 2.0]
|
||||||
https://github.com/classgraph/classgraph
|
https://github.com/classgraph/classgraph
|
||||||
Copyright 2022
|
Copyright 2023
|
||||||
Luke Hutchison
|
Luke Hutchison
|
||||||
|
|
||||||
- kotlin-logging - Lightweight logging framework for Kotlin
|
- kotlin-logging - Lightweight logging framework for Kotlin
|
||||||
[The Apache Software License, Version 2.0]
|
[The Apache Software License, Version 2.0]
|
||||||
https://github.com/MicroUtils/kotlin-logging
|
https://github.com/MicroUtils/kotlin-logging
|
||||||
Copyright 2022
|
Copyright 2023
|
||||||
Ohad Shai
|
Ohad Shai
|
||||||
|
|
||||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||||
[MIT License]
|
[MIT License]
|
||||||
http://www.slf4j.org
|
https://www.slf4j.org
|
||||||
Copyright 2022
|
Copyright 2023
|
||||||
QOS.ch
|
QOS.ch
|
||||||
|
|
||||||
- JUL to SLF4J - Java Util Logging implemented over SLF4J
|
- JUL to SLF4J - Java Util Logging implemented over SLF4J
|
||||||
[MIT License]
|
[MIT License]
|
||||||
http://www.slf4j.org
|
https://www.slf4j.org
|
||||||
Copyright 2022
|
Copyright 2023
|
||||||
QOS.ch
|
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
|
- Updates - Software Update Management
|
||||||
[The Apache Software License, Version 2.0]
|
[The Apache Software License, Version 2.0]
|
||||||
https://git.dorkbox.com/dorkbox/Updates
|
https://git.dorkbox.com/dorkbox/Updates
|
||||||
@ -99,3 +100,39 @@
|
|||||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
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
|
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
|
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
347
LICENSE.GPLv2_CP
Normal 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.
|
@ -23,7 +23,7 @@ Maven Info
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.dorkbox</groupId>
|
<groupId>com.dorkbox</groupId>
|
||||||
<artifactId>VaadinUndertow</artifactId>
|
<artifactId>VaadinUndertow</artifactId>
|
||||||
<version>14.7.4</version>
|
<version>14.10</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
```
|
```
|
||||||
@ -33,7 +33,7 @@ Gradle Info
|
|||||||
```
|
```
|
||||||
dependencies {
|
dependencies {
|
||||||
...
|
...
|
||||||
implementation "com.dorkbox:VaadinUndertow:14.7.4"
|
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
|
This project is © 2020 dorkbox llc, and is distributed under the terms of the Apache v2.0 License. See file "LICENSE" for further
|
||||||
references.
|
references.
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2021 dorkbox, llc
|
* Copyright 2023 dorkbox, llc
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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'>
|
////// 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 {
|
plugins {
|
||||||
id("com.dorkbox.GradleUtils") version "2.16"
|
id("com.dorkbox.GradleUtils") version "3.18"
|
||||||
id("com.dorkbox.Licensing") version "2.12"
|
id("com.dorkbox.Licensing") version "2.28"
|
||||||
id("com.dorkbox.VersionUpdate") version "2.4"
|
id("com.dorkbox.VersionUpdate") version "2.8"
|
||||||
id("com.dorkbox.GradlePublish") version "1.12"
|
id("com.dorkbox.GradlePublish") version "1.20"
|
||||||
|
|
||||||
kotlin("jvm") version "1.6.10"
|
kotlin("jvm") version "1.9.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
object Extras {
|
object Extras {
|
||||||
@ -39,19 +34,17 @@ object Extras {
|
|||||||
const val group = "com.dorkbox"
|
const val group = "com.dorkbox"
|
||||||
const val name = "VaadinUndertow"
|
const val name = "VaadinUndertow"
|
||||||
const val id = "VaadinUndertow"
|
const val id = "VaadinUndertow"
|
||||||
const val version = "14.7.4"
|
|
||||||
|
const val version = "14.10"
|
||||||
|
|
||||||
const val vendor = "Dorkbox LLC"
|
const val vendor = "Dorkbox LLC"
|
||||||
const val vendorUrl = "https://dorkbox.com"
|
const val vendorUrl = "https://dorkbox.com"
|
||||||
const val url = "https://git.dorkbox.com/dorkbox/VaadinUndertow"
|
const val url = "https://git.dorkbox.com/dorkbox/VaadinUndertow"
|
||||||
|
|
||||||
val buildDate = Instant.now().toString()
|
|
||||||
|
|
||||||
const val coroutineVer = "1.6.0"
|
// 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"
|
||||||
// these BOTH must match the version information in the VaadinApplication.kt file (this is automatically passed into the plugin)
|
const val undertowVer = "2.2.22.Final"
|
||||||
const val vaadinVer = "14.7.8"
|
|
||||||
const val undertowVer = "2.2.16.Final"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
@ -59,8 +52,8 @@ object Extras {
|
|||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
GradleUtils.load("$projectDir/../../gradle.properties", Extras)
|
GradleUtils.load("$projectDir/../../gradle.properties", Extras)
|
||||||
GradleUtils.defaults()
|
GradleUtils.defaults()
|
||||||
GradleUtils.compileConfiguration(JavaVersion.VERSION_1_8)
|
GradleUtils.compileConfiguration(JavaVersion.VERSION_11)
|
||||||
GradleUtils.jpms(JavaVersion.VERSION_1_9)
|
//GradleUtils.jpms(JavaVersion.VERSION_1_9) vaadin doesn't support jpms yet
|
||||||
|
|
||||||
|
|
||||||
licensing {
|
licensing {
|
||||||
@ -99,7 +92,7 @@ licensing {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api("org.jetbrains.kotlin:kotlin-reflect")
|
api("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Extras.coroutineVer}")
|
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
|
||||||
|
|
||||||
compileOnly("com.vaadin:vaadin:${Extras.vaadinVer}")
|
compileOnly("com.vaadin:vaadin:${Extras.vaadinVer}")
|
||||||
|
|
||||||
@ -108,29 +101,30 @@ dependencies {
|
|||||||
api("io.undertow:undertow-servlet:${Extras.undertowVer}")
|
api("io.undertow:undertow-servlet:${Extras.undertowVer}")
|
||||||
api("io.undertow:undertow-websockets-jsr:${Extras.undertowVer}")
|
api("io.undertow:undertow-websockets-jsr:${Extras.undertowVer}")
|
||||||
|
|
||||||
|
|
||||||
// Uber-fast, ultra-lightweight Java classpath and module path scanner
|
// Uber-fast, ultra-lightweight Java classpath and module path scanner
|
||||||
api("io.github.classgraph:classgraph:4.8.141")
|
api("io.github.classgraph:classgraph:4.8.160")
|
||||||
|
|
||||||
api("com.dorkbox:Updates:1.1")
|
api("com.dorkbox:Updates:1.1")
|
||||||
|
api("com.dorkbox:FSM:1.0")
|
||||||
|
|
||||||
// implementation("com.conversantmedia:disruptor:1.2.19")
|
// implementation("com.conversantmedia:disruptor:1.2.19")
|
||||||
|
|
||||||
// awesome logging framework for kotlin.
|
// awesome logging framework for kotlin.
|
||||||
// https://www.reddit.com/r/Kotlin/comments/8gbiul/slf4j_loggers_in_3_ways/
|
// https://www.reddit.com/r/Kotlin/comments/8gbiul/slf4j_loggers_in_3_ways/
|
||||||
// https://github.com/MicroUtils/kotlin-logging
|
// https://github.com/MicroUtils/kotlin-logging
|
||||||
api("io.github.microutils:kotlin-logging:2.1.21")
|
api("io.github.microutils:kotlin-logging:3.0.5")
|
||||||
|
|
||||||
// 1.8.0-beta4 supports jpms
|
// 1.8.0-beta4 supports jpms
|
||||||
api("org.slf4j:slf4j-api:1.8.0-beta4")
|
api("org.slf4j:slf4j-api:2.0.7")
|
||||||
api("org.slf4j:jul-to-slf4j:1.8.0-beta4")
|
api("org.slf4j:jul-to-slf4j:2.0.7")
|
||||||
|
|
||||||
|
|
||||||
api("ch.qos.logback:logback-core:1.3.0-alpha4")
|
// api("ch.qos.logback:logback-core:1.4.5")
|
||||||
compileOnly("ch.qos.logback:logback-classic:1.3.0-alpha4")
|
// compileOnly("ch.qos.logback:logback-classic:1.4.5")
|
||||||
|
//
|
||||||
|
//
|
||||||
testImplementation("junit:junit:4.13.2")
|
// testImplementation("com.vaadin:vaadin:${Extras.vaadinVer}")
|
||||||
|
// testImplementation("ch.qos.logback:logback-classic:1.4.5")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.jar.get().apply {
|
tasks.jar.get().apply {
|
||||||
@ -143,7 +137,7 @@ tasks.jar.get().apply {
|
|||||||
attributes["Specification-Vendor"] = Extras.vendor
|
attributes["Specification-Vendor"] = Extras.vendor
|
||||||
|
|
||||||
attributes["Implementation-Title"] = "${Extras.group}.${Extras.id}"
|
attributes["Implementation-Title"] = "${Extras.group}.${Extras.id}"
|
||||||
attributes["Implementation-Version"] = Extras.buildDate
|
attributes["Implementation-Version"] = GradleUtils.now()
|
||||||
attributes["Implementation-Vendor"] = Extras.vendor
|
attributes["Implementation-Vendor"] = Extras.vendor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
36
frontend/index.html
Normal file
36
frontend/index.html
Normal 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>
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -13,3 +13,4 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
rootProject.name = "VaadinUndertow"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2021 dorkbox, llc
|
* Copyright 2023 dorkbox, llc
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -17,13 +17,12 @@ package dorkbox.vaadin
|
|||||||
|
|
||||||
import com.vaadin.flow.server.VaadinContext
|
import com.vaadin.flow.server.VaadinContext
|
||||||
import com.vaadin.flow.server.frontend.FrontendUtils
|
import com.vaadin.flow.server.frontend.FrontendUtils
|
||||||
import dorkbox.vaadin.devMode.DevModeInitializer
|
import dorkbox.fsm.DoubleArrayStringTrie
|
||||||
import dorkbox.vaadin.undertow.*
|
import dorkbox.vaadin.undertow.*
|
||||||
import dorkbox.vaadin.util.CallingClass
|
import dorkbox.vaadin.util.CallingClass
|
||||||
import dorkbox.vaadin.util.TrieClassLoader
|
import dorkbox.vaadin.util.TrieClassLoader
|
||||||
import dorkbox.vaadin.util.UndertowBuilder
|
import dorkbox.vaadin.util.UndertowBuilder
|
||||||
import dorkbox.vaadin.util.VaadinConfig
|
import dorkbox.vaadin.util.VaadinConfig
|
||||||
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
|
|
||||||
import io.github.classgraph.ClassGraph
|
import io.github.classgraph.ClassGraph
|
||||||
import io.github.classgraph.ScanResult
|
import io.github.classgraph.ScanResult
|
||||||
import io.undertow.Undertow
|
import io.undertow.Undertow
|
||||||
@ -45,11 +44,13 @@ import java.io.File
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.*
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.atomic.*
|
||||||
import java.util.concurrent.TimeUnit
|
import javax.servlet.Servlet
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import javax.servlet.ServletContainerInitializer
|
||||||
import javax.servlet.*
|
import javax.servlet.ServletRequest
|
||||||
|
import javax.servlet.ServletResponse
|
||||||
|
import javax.servlet.SessionTrackingMode
|
||||||
import javax.servlet.annotation.HandlesTypes
|
import javax.servlet.annotation.HandlesTypes
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -63,11 +64,17 @@ class VaadinApplication : ExceptionHandler {
|
|||||||
/**
|
/**
|
||||||
* Gets the version number.
|
* Gets the version number.
|
||||||
*/
|
*/
|
||||||
const val version = "14.7.4"
|
const val version = "14.10"
|
||||||
|
|
||||||
// this must match the version information in the build.gradle.kts file (this is automatically passed into the plugin)
|
// this must match the version information in the build.gradle.kts file (this is automatically passed into the plugin)
|
||||||
const val vaadinVersion = "14.7.8"
|
const val vaadinVersion = "14.10.1"
|
||||||
const val undertowVersion = "2.2.14.Final"
|
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 {
|
init {
|
||||||
// Add this project to the updates system, which verifies this class + UUID + version information
|
// Add this project to the updates system, which verifies this class + UUID + version information
|
||||||
@ -102,9 +109,9 @@ class VaadinApplication : ExceptionHandler {
|
|||||||
private lateinit var servletHttpHandler: HttpHandler
|
private lateinit var servletHttpHandler: HttpHandler
|
||||||
private lateinit var servletManager: DeploymentManager
|
private lateinit var servletManager: DeploymentManager
|
||||||
|
|
||||||
private lateinit var jarStringTrie: DoubleArrayTrie<String>
|
private lateinit var jarStringTrie: DoubleArrayStringTrie<String>
|
||||||
private lateinit var jarUrlTrie: DoubleArrayTrie<URL>
|
private lateinit var jarUrlTrie: DoubleArrayStringTrie<URL>
|
||||||
private lateinit var diskTrie: DoubleArrayTrie<URL>
|
private lateinit var diskTrie: DoubleArrayStringTrie<URL>
|
||||||
|
|
||||||
private lateinit var servletBuilder: DeploymentInfo
|
private lateinit var servletBuilder: DeploymentInfo
|
||||||
private lateinit var serverBuilder: UndertowBuilder
|
private lateinit var serverBuilder: UndertowBuilder
|
||||||
@ -359,16 +366,14 @@ class VaadinApplication : ExceptionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EVERYTHING IS ACCESSED VIA TRIE, NOT VIA HASHMAP! (it's faster this way)
|
// EVERYTHING IS ACCESSED VIA TRIE, NOT VIA HASHMAP! (it's faster this way)
|
||||||
jarStringTrie = DoubleArrayTrie(jarStringResourceRequestMap)
|
jarStringTrie = DoubleArrayStringTrie(jarStringResourceRequestMap)
|
||||||
jarUrlTrie = DoubleArrayTrie(jarUrlResourceRequestMap)
|
jarUrlTrie = DoubleArrayStringTrie(jarUrlResourceRequestMap)
|
||||||
diskTrie = DoubleArrayTrie(diskResourceRequestMap)
|
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)
|
// 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!
|
// 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!
|
// 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()
|
val toTypedArray = jarLocations.map { it.resourceDir }.toSet().toTypedArray()
|
||||||
this.trieClassLoader = TrieClassLoader(diskTrie, jarStringTrie, toTypedArray, this.javaClass.classLoader, logger)
|
this.trieClassLoader = TrieClassLoader(diskTrie, jarStringTrie, toTypedArray, this.javaClass.classLoader, logger)
|
||||||
@ -584,7 +589,7 @@ class VaadinApplication : ExceptionHandler {
|
|||||||
// instead of the default, we load **OUR** dev-mode initializer.
|
// instead of the default, we load **OUR** dev-mode initializer.
|
||||||
// The vaadin one is super buggy for custom environments
|
// The vaadin one is super buggy for custom environments
|
||||||
servletBuilder.addServletContainerInitializer(
|
servletBuilder.addServletContainerInitializer(
|
||||||
ServletContainerInitializerInfo(DevModeInitializer::class.java, classSet))
|
ServletContainerInitializerInfo(com.vaadin.flow.server.startup.DevModeInitializer::class.java, classSet))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// do not load the dev-mode initializer for production mode
|
// do not load the dev-mode initializer for production mode
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,170 +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.onFailure
|
|
||||||
import kotlinx.coroutines.channels.sendBlocking
|
|
||||||
import kotlinx.coroutines.channels.trySendBlocking
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("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.trySendBlocking(exchange)
|
|
||||||
.onFailure { t: Throwable? -> logger.error("Error sending data.", t) }
|
|
||||||
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package dorkbox.vaadin.undertow
|
package dorkbox.vaadin.undertow
|
||||||
|
|
||||||
|
import dorkbox.vaadin.util.logger
|
||||||
import io.undertow.UndertowLogger
|
import io.undertow.UndertowLogger
|
||||||
import io.undertow.io.IoCallback
|
import io.undertow.io.IoCallback
|
||||||
import io.undertow.predicate.Predicate
|
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.encoding.ContentEncodedResourceManager
|
||||||
import io.undertow.server.handlers.resource.*
|
import io.undertow.server.handlers.resource.*
|
||||||
import io.undertow.util.*
|
import io.undertow.util.*
|
||||||
|
import org.slf4j.Logger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.*
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
/**
|
/**
|
||||||
@ -49,6 +50,7 @@ import java.util.concurrent.TimeUnit
|
|||||||
*/
|
*/
|
||||||
class DirectResourceHandler(@Volatile private var resourceManager: ResourceManager?,
|
class DirectResourceHandler(@Volatile private var resourceManager: ResourceManager?,
|
||||||
@Volatile private var resourceSupplier: ResourceSupplier = DefaultResourceSupplier(resourceManager),
|
@Volatile private var resourceSupplier: ResourceSupplier = DefaultResourceSupplier(resourceManager),
|
||||||
|
val logger: Logger,
|
||||||
private val next: HttpHandler = ResponseCodeHandler.HANDLE_404) : HttpHandler {
|
private val next: HttpHandler = ResponseCodeHandler.HANDLE_404) : HttpHandler {
|
||||||
|
|
||||||
private val welcomeFiles = CopyOnWriteArrayList(arrayOf("index.html", "index.htm", "default.html", "default.htm"))
|
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 class Wrapper(private val location: String,
|
||||||
private val allowDirectoryListing: Boolean) : HandlerWrapper {
|
private val allowDirectoryListing: Boolean) : HandlerWrapper {
|
||||||
|
|
||||||
override fun wrap(handler: HttpHandler): HttpHandler {
|
override fun wrap(handler: HttpHandler): HttpHandler {
|
||||||
val rm = PathResourceManager(Paths.get(location), 1024)
|
val resourceManager = PathResourceManager(Paths.get(location), 1024)
|
||||||
val resourceHandler = DirectResourceHandler(rm)
|
// use a default logger
|
||||||
|
val resourceHandler = DirectResourceHandler(resourceManager, DefaultResourceSupplier(resourceManager), logger())
|
||||||
resourceHandler.setDirectoryListingEnabled(allowDirectoryListing)
|
resourceHandler.setDirectoryListingEnabled(allowDirectoryListing)
|
||||||
return resourceHandler
|
return resourceHandler
|
||||||
}
|
}
|
||||||
|
21
src/dorkbox/vaadin/undertow/HandleBuilder.kt
Normal file
21
src/dorkbox/vaadin/undertow/HandleBuilder.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
* Copyright 2012-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
@ -15,13 +31,13 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.vaadin.undertow
|
package dorkbox.vaadin.undertow
|
||||||
|
|
||||||
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
|
import dorkbox.fsm.DoubleArrayStringTrie
|
||||||
import io.undertow.UndertowMessages
|
import io.undertow.UndertowMessages
|
||||||
import io.undertow.server.handlers.resource.Resource
|
import io.undertow.server.handlers.resource.Resource
|
||||||
import io.undertow.server.handlers.resource.ResourceChangeListener
|
import io.undertow.server.handlers.resource.ResourceChangeListener
|
||||||
import io.undertow.server.handlers.resource.ResourceManager
|
import io.undertow.server.handlers.resource.ResourceManager
|
||||||
import io.undertow.server.handlers.resource.URLResource
|
import io.undertow.server.handlers.resource.URLResource
|
||||||
import mu.KLogger
|
import org.slf4j.Logger
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
@ -32,20 +48,25 @@ import java.net.URL
|
|||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @author Dorkbox LLC
|
* @author Dorkbox LLC
|
||||||
*/
|
*/
|
||||||
internal class JarResourceManager(val name: String, val trie: DoubleArrayTrie<URL>, val logger: KLogger) : ResourceManager {
|
internal class JarResourceManager(val name: String,
|
||||||
|
private val trie: DoubleArrayStringTrie<URL>,
|
||||||
|
private val logger: Logger) : ResourceManager {
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun getResource(path: String): Resource? {
|
override fun getResource(path: String): Resource? {
|
||||||
logger.trace { "REQUEST jar: $path" }
|
val url = trie[path]
|
||||||
|
if (url === null) {
|
||||||
val url = trie[path] ?: return null
|
logger.trace("REQUEST not found for PATH: {}", path)
|
||||||
logger.trace { "TRIE: $url" }
|
|
||||||
|
|
||||||
val resource = URLResource(url, path)
|
|
||||||
if (path.isNotBlank() && path != "/" && resource.contentLength < 0) {
|
|
||||||
return null
|
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
|
return resource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
25
src/dorkbox/vaadin/undertow/Redirect.kt
Normal file
25
src/dorkbox/vaadin/undertow/Redirect.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 Dorkbox LLC
|
* Copyright 2024 dorkbox, llc
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,14 +16,14 @@
|
|||||||
|
|
||||||
package dorkbox.vaadin.undertow
|
package dorkbox.vaadin.undertow
|
||||||
|
|
||||||
|
import dorkbox.vaadin.util.logger
|
||||||
import io.undertow.UndertowMessages
|
import io.undertow.UndertowMessages
|
||||||
import io.undertow.server.handlers.resource.Resource
|
import io.undertow.server.handlers.resource.Resource
|
||||||
import io.undertow.server.handlers.resource.ResourceChangeListener
|
import io.undertow.server.handlers.resource.ResourceChangeListener
|
||||||
import io.undertow.server.handlers.resource.ResourceManager
|
import io.undertow.server.handlers.resource.ResourceManager
|
||||||
import mu.KotlinLogging
|
|
||||||
|
|
||||||
class ResourceCollectionManager(private val resources: List<ResourceManager>) : ResourceManager {
|
class ResourceCollectionManager(private val resources: List<ResourceManager>) : ResourceManager {
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = logger()
|
||||||
|
|
||||||
private val changeListenerSupported : Boolean by lazy {
|
private val changeListenerSupported : Boolean by lazy {
|
||||||
var supported = true
|
var supported = true
|
||||||
@ -77,7 +77,7 @@ class ResourceCollectionManager(private val resources: List<ResourceManager>) :
|
|||||||
try {
|
try {
|
||||||
it.close()
|
it.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.error(e) { "Error closing resourceManager" }
|
logger.error("Error closing resourceManager", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,71 +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 mu.KLogger
|
|
||||||
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 logger: KLogger) : io.undertow.server.handlers.resource.FileResourceManager(File(".")) {
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun getResource(path: String): Resource? {
|
|
||||||
logger.trace { "REQUEST static: $path" }
|
|
||||||
|
|
||||||
val url = trie[path] ?: return null
|
|
||||||
|
|
||||||
logger.trace { "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)"
|
|
||||||
}
|
|
||||||
}
|
|
116
src/dorkbox/vaadin/undertow/TrieFileResourceManager.kt
Normal file
116
src/dorkbox/vaadin/undertow/TrieFileResourceManager.kt
Normal 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)"
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2021 dorkbox, llc
|
* Copyright 2024 dorkbox, llc
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
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')"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2021 dorkbox, llc
|
* Copyright 2024 dorkbox, llc
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
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')"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2021 dorkbox, llc
|
* Copyright 2024 dorkbox, llc
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,7 +16,7 @@
|
|||||||
package dorkbox.vaadin.util
|
package dorkbox.vaadin.util
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* This class is overridden by the multi-source compile for java 9+
|
||||||
*/
|
*/
|
||||||
object CallingClass {
|
object CallingClass {
|
||||||
fun get(): Class<*> {
|
fun get(): Class<*> {
|
||||||
|
@ -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
|
* Copyright 2017 Pronghorn Technology LLC
|
||||||
*
|
*
|
||||||
@ -16,16 +32,20 @@
|
|||||||
|
|
||||||
package dorkbox.vaadin.util
|
package dorkbox.vaadin.util
|
||||||
|
|
||||||
import mu.KLogger
|
import org.slf4j.Logger
|
||||||
import mu.KotlinLogging
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
|
|
||||||
inline fun <reified T> T.logger(): KLogger {
|
inline fun <reified T> T.logger(): Logger {
|
||||||
if (T::class.isCompanion) {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
* Copyright 2017 Pronghorn Technology LLC
|
||||||
*
|
*
|
||||||
@ -16,7 +32,7 @@
|
|||||||
|
|
||||||
package dorkbox.vaadin.util
|
package dorkbox.vaadin.util
|
||||||
|
|
||||||
import mu.KotlinLogging
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
private const val MAX_POW2 = 1 shl 30
|
private const val MAX_POW2 = 1 shl 30
|
||||||
|
|
||||||
@ -27,10 +43,13 @@ inline fun <reified T : Any> validatePowerOfTwoCapacity(caller: T,
|
|||||||
value
|
value
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val logger = KotlinLogging.logger(caller.javaClass.name)
|
val logger = if (T::class.isCompanion) {
|
||||||
logger.warn {
|
LoggerFactory.getLogger(caller::class.java.enclosingClass.name)
|
||||||
"${caller.javaClass.simpleName} capacity should be a power of two, but ($value) requested. Using the next available: ${roundToNextPowerOfTwo(value)}."
|
} 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)
|
roundToNextPowerOfTwo(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2021 dorkbox, llc
|
* Copyright 2024 dorkbox, llc
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,33 +15,39 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.vaadin.util
|
package dorkbox.vaadin.util
|
||||||
|
|
||||||
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
|
import dorkbox.fsm.DoubleArrayStringTrie
|
||||||
import mu.KLogger
|
import org.slf4j.Logger
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Used to get classloader data, but using a trie to store the lookups
|
||||||
*/
|
*/
|
||||||
class TrieClassLoader(
|
class TrieClassLoader(
|
||||||
private val urlTrie: DoubleArrayTrie<URL>,
|
private val diskTrie: DoubleArrayStringTrie<URL>,
|
||||||
private val stringTrie: DoubleArrayTrie<String>,
|
private val jarTrie: DoubleArrayStringTrie<String>,
|
||||||
urls: Array<URL>, parent: ClassLoader,
|
|
||||||
private val logger: KLogger): URLClassLoader(urls, parent) {
|
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? {
|
override fun getResource(name: String): URL? {
|
||||||
logger.trace { "URL Classloader: $name" }
|
logger.trace(name)
|
||||||
|
|
||||||
// check disk first
|
// check disk first
|
||||||
val diskResourcePath: URL? = urlTrie[name]
|
val diskResourcePath: URL? = diskTrie[name]
|
||||||
if (diskResourcePath != null) {
|
if (diskResourcePath != null) {
|
||||||
logger.trace { "TRIE: $diskResourcePath" }
|
loggerDisk.trace(diskResourcePath.file)
|
||||||
return diskResourcePath
|
return diskResourcePath
|
||||||
}
|
}
|
||||||
|
|
||||||
val jarResourcePath: String? = stringTrie[name]
|
val jarResourcePath: String? = jarTrie[name]
|
||||||
if (jarResourcePath != null) {
|
if (jarResourcePath != null) {
|
||||||
logger.trace { "TRIE: $jarResourcePath" }
|
loggerJar.trace(jarResourcePath)
|
||||||
return super.getResource(jarResourcePath)
|
return super.getResource(jarResourcePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2021 dorkbox, llc
|
* Copyright 2023 dorkbox, llc
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
||||||
import io.undertow.Undertow.ListenerBuilder
|
import io.undertow.Undertow.ListenerBuilder
|
||||||
import io.undertow.connector.ByteBufferPool
|
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 io.undertow.server.HttpHandler
|
||||||
import org.xnio.Option
|
import org.xnio.Option
|
||||||
import org.xnio.XnioWorker
|
import org.xnio.XnioWorker
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.*
|
||||||
import javax.net.ssl.KeyManager
|
import javax.net.ssl.KeyManager
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
import javax.net.ssl.TrustManager
|
import javax.net.ssl.TrustManager
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class UndertowBuilder() {
|
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()
|
private val builder = Undertow.builder()
|
||||||
|
|
||||||
// the LAST http (or https) listener will recorded as the listener used.
|
// 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)
|
* to the various worker-related configuration (ioThreads, workerThreads, workerOptions)
|
||||||
* when [Undertow.start] is called.
|
* when [Undertow.start] is called.
|
||||||
* Additionally, this newly created worker will be shutdown when [Undertow.stop] 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]
|
* When non-null, the provided [XnioWorker] will be reused instead of creating a new [XnioWorker]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2021 dorkbox, llc
|
* Copyright 2023 dorkbox, llc
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -74,11 +74,8 @@ class VaadinConfig(runningAsJar: Boolean, tempDir: File) {
|
|||||||
// this file from the jar to the temp location.
|
// this file from the jar to the temp location.
|
||||||
File(tempDir, defaultTokenFile).absolutePath
|
File(tempDir, defaultTokenFile).absolutePath
|
||||||
} else {
|
} else {
|
||||||
if (tokenInJar.path.startsWith("/")) {
|
// loading as a FILE, so we must make sure
|
||||||
tokenInJar.path.substring(1)
|
tokenInJar.path
|
||||||
} else {
|
|
||||||
tokenInJar.path
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenJson = JsonUtil.parse(tokenInJar.readText(Charsets.UTF_8)) as JsonObject?
|
tokenJson = JsonUtil.parse(tokenInJar.readText(Charsets.UTF_8)) as JsonObject?
|
||||||
@ -97,8 +94,8 @@ class VaadinConfig(runningAsJar: Boolean, tempDir: File) {
|
|||||||
|
|
||||||
if (tokenFileName.isEmpty() || tokenJson == null || !tokenJson.hasKey(InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE)) {
|
if (tokenFileName.isEmpty() || tokenJson == null || !tokenJson.hasKey(InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE)) {
|
||||||
// this is a problem! we must configure the system first via gradle!
|
// this is a problem! we must configure the system first via gradle!
|
||||||
throw java.lang.RuntimeException("Unable to continue! Error reading token!" +
|
throw java.lang.RuntimeException("Unable to continue! Error reading token! " +
|
||||||
"You must FIRST compile the vaadin resources for DEV or PRODUCTION mode!")
|
"You must FIRST compile the vaadin resources for Development or Production mode!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -150,7 +147,7 @@ class VaadinConfig(runningAsJar: Boolean, tempDir: 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)
|
// 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
|
servlet
|
||||||
.addInitParam(InitParameters.EXTERNAL_STATS_FILE, "true")
|
.addInitParam(InitParameters.EXTERNAL_STATS_FILE, "true")
|
||||||
.addInitParam(InitParameters.EXTERNAL_STATS_URL, "$url")
|
.addInitParam(InitParameters.EXTERNAL_STATS_URL, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
|
||||||
*/
|
|
@ -2,74 +2,73 @@ module dorkbox.vaadin {
|
|||||||
exports dorkbox.vaadin;
|
exports dorkbox.vaadin;
|
||||||
exports dorkbox.vaadin.util;
|
exports dorkbox.vaadin.util;
|
||||||
|
|
||||||
requires dorkbox.updates;
|
requires transitive dorkbox.updates;
|
||||||
requires org.slf4j;
|
requires transitive org.slf4j;
|
||||||
|
|
||||||
// requires static ch.qos.logback.classic;
|
// requires static ch.qos.logback.classic;
|
||||||
|
|
||||||
// requires vaadin;
|
// requires vaadin;
|
||||||
|
|
||||||
//Vaadin UI components used
|
//Vaadin UI components used
|
||||||
requires flow.data;
|
requires transitive flow.data;
|
||||||
requires flow.server;
|
requires transitive flow.server;
|
||||||
requires vaadin.accordion.flow;
|
requires transitive vaadin.accordion.flow;
|
||||||
requires vaadin.accordion;
|
requires transitive vaadin.accordion;
|
||||||
requires vaadin.app.layout.flow;
|
requires transitive vaadin.app.layout.flow;
|
||||||
requires vaadin.app.layout;
|
requires transitive vaadin.app.layout;
|
||||||
requires vaadin.button.flow;
|
requires transitive vaadin.button.flow;
|
||||||
requires vaadin.button;
|
requires transitive vaadin.button;
|
||||||
requires vaadin.charts.flow;
|
requires transitive vaadin.charts.flow;
|
||||||
requires vaadin.charts;
|
requires transitive vaadin.charts;
|
||||||
requires vaadin.checkbox.flow;
|
requires transitive vaadin.checkbox.flow;
|
||||||
requires vaadin.checkbox;
|
requires transitive vaadin.checkbox;
|
||||||
requires vaadin.combo.box.flow;
|
requires transitive vaadin.combo.box.flow;
|
||||||
requires vaadin.combo.box;
|
requires transitive vaadin.combo.box;
|
||||||
requires vaadin.confirm.dialog.flow;
|
requires transitive vaadin.confirm.dialog.flow;
|
||||||
requires vaadin.confirm.dialog;
|
requires transitive vaadin.confirm.dialog;
|
||||||
requires vaadin.context.menu.flow;
|
requires transitive vaadin.context.menu.flow;
|
||||||
requires vaadin.context.menu;
|
requires transitive vaadin.context.menu;
|
||||||
requires vaadin.control.state.mixin;
|
requires transitive vaadin.control.state.mixin;
|
||||||
requires vaadin.cookie.consent.flow;
|
requires transitive vaadin.cookie.consent.flow;
|
||||||
requires vaadin.cookie.consent;
|
requires transitive vaadin.cookie.consent;
|
||||||
requires vaadin.core;
|
requires transitive vaadin.core;
|
||||||
requires vaadin.custom.field.flow;
|
requires transitive vaadin.custom.field.flow;
|
||||||
requires vaadin.custom.field;
|
requires transitive vaadin.custom.field;
|
||||||
requires vaadin.date.picker.flow;
|
requires transitive vaadin.date.picker.flow;
|
||||||
requires vaadin.date.picker;
|
requires transitive vaadin.date.picker;
|
||||||
requires vaadin.details.flow;
|
requires transitive vaadin.details.flow;
|
||||||
requires vaadin.details;
|
requires transitive vaadin.details;
|
||||||
requires vaadin.development.mode.detector;
|
requires transitive vaadin.development.mode.detector;
|
||||||
requires vaadin.dialog.flow;
|
requires transitive vaadin.dialog.flow;
|
||||||
requires vaadin.dialog;
|
requires transitive vaadin.dialog;
|
||||||
requires vaadin.element.mixin;
|
requires transitive vaadin.element.mixin;
|
||||||
requires vaadin.grid.flow;
|
requires transitive vaadin.grid.flow;
|
||||||
requires vaadin.list.box.flow;
|
requires transitive vaadin.list.box.flow;
|
||||||
requires vaadin.list.box;
|
requires transitive vaadin.list.box;
|
||||||
requires vaadin.list.mixin;
|
requires transitive vaadin.list.mixin;
|
||||||
requires vaadin.login.flow;
|
requires transitive vaadin.login.flow;
|
||||||
requires vaadin.login;
|
requires transitive vaadin.login;
|
||||||
requires vaadin.lumo.theme;
|
requires transitive vaadin.lumo.theme;
|
||||||
requires vaadin.ordered.layout.flow;
|
requires transitive vaadin.ordered.layout.flow;
|
||||||
requires vaadin.ordered.layout;
|
requires transitive vaadin.ordered.layout;
|
||||||
requires vaadin.progress.bar.flow;
|
requires transitive vaadin.progress.bar.flow;
|
||||||
requires vaadin.progress.bar;
|
requires transitive vaadin.progress.bar;
|
||||||
requires vaadin.radio.button.flow;
|
requires transitive vaadin.radio.button.flow;
|
||||||
requires vaadin.radio.button;
|
requires transitive vaadin.radio.button;
|
||||||
requires vaadin.text.field.flow;
|
requires transitive vaadin.text.field.flow;
|
||||||
requires vaadin.text.field;
|
requires transitive vaadin.text.field;
|
||||||
requires vaadin.upload.flow;
|
requires transitive vaadin.upload.flow;
|
||||||
requires vaadin.upload;
|
requires transitive vaadin.upload;
|
||||||
requires vaadin.usage.statistics;
|
requires transitive vaadin.usage.statistics;
|
||||||
|
|
||||||
requires xnio.api;
|
requires transitive xnio.api;
|
||||||
|
|
||||||
// requires com.conversantmedia.disruptor;
|
// requires com.conversantmedia.disruptor;
|
||||||
requires io.github.classgraph;
|
requires transitive io.github.classgraph;
|
||||||
|
|
||||||
requires undertow.core;
|
requires transitive undertow.core;
|
||||||
requires undertow.servlet;
|
requires transitive undertow.servlet;
|
||||||
requires undertow.websockets.jsr;
|
requires transitive undertow.websockets.jsr;
|
||||||
|
|
||||||
requires kotlin.stdlib;
|
requires transitive kotlin.stdlib;
|
||||||
requires java.base;
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user