Compare commits
No commits in common. "master" and "Version_14.7.5" have entirely different histories.
master
...
Version_14
1
.gitignore
vendored
1
.gitignore
vendored
@ -61,7 +61,6 @@ fabric.properties
|
||||
# From https://github.com/github/gitignore/blob/master/Gradle.gitignore
|
||||
.gradle
|
||||
/build/
|
||||
/frontend/generated/
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
69
LICENSE
69
LICENSE
@ -1,7 +1,7 @@
|
||||
- VaadinUndertow - Vaadin support for the Undertow web server
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/VaadinUndertow
|
||||
Copyright 2023
|
||||
Copyright 2022
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
@ -38,13 +38,13 @@
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines
|
||||
Copyright 2023
|
||||
Copyright 2022
|
||||
JetBrains s.r.o.
|
||||
|
||||
- Undertow - High performance non-blocking webserver
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/undertow-io/undertow
|
||||
Copyright 2023
|
||||
Copyright 2022
|
||||
JBoss
|
||||
Red Hat, Inc.
|
||||
Individual contributors as listed in files
|
||||
@ -52,40 +52,39 @@
|
||||
- ClassGraph - An uber-fast parallelized Java classpath scanner and module scanner
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/classgraph/classgraph
|
||||
Copyright 2023
|
||||
Copyright 2022
|
||||
Luke Hutchison
|
||||
|
||||
- kotlin-logging - Lightweight logging framework for Kotlin
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/MicroUtils/kotlin-logging
|
||||
Copyright 2023
|
||||
Copyright 2022
|
||||
Ohad Shai
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
https://www.slf4j.org
|
||||
Copyright 2023
|
||||
http://www.slf4j.org
|
||||
Copyright 2022
|
||||
QOS.ch
|
||||
|
||||
- JUL to SLF4J - Java Util Logging implemented over SLF4J
|
||||
[MIT License]
|
||||
https://www.slf4j.org
|
||||
Copyright 2023
|
||||
http://www.slf4j.org
|
||||
Copyright 2022
|
||||
QOS.ch
|
||||
|
||||
- Logback - Logback is a logging framework for Java applications
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://logback.qos.ch
|
||||
Copyright 2022
|
||||
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
|
||||
Copyright 2022
|
||||
Vaadin Ltd.
|
||||
|
||||
- Jakarta Servlet - Jakarta Servlet™ is a standard technology for interacting with the web on the Jakarta EE platform.
|
||||
[GNU General Public License, version 2, with the Classpath Exception]
|
||||
https://projects.eclipse.org/projects/ee4j.servlet
|
||||
Copyright 2023
|
||||
Yamini K B
|
||||
ee4j-pmc@eclipse.org
|
||||
|
||||
- Updates - Software Update Management
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Updates
|
||||
@ -100,39 +99,3 @@
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- FiniteStateMachine - Finite State Machine using the AhoCorasick implementation using a Double Array Trie, java 8+
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/FSM
|
||||
Copyright 2023
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- AhoCorasickDoubleArrayTrie - An extremely fast implementation of Aho Corasick algorithm based on Double Array Trie structure.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/hankcs/AhoCorasickDoubleArrayTrie
|
||||
Copyright 2018
|
||||
hankcs <me@hankcs.com>
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- Updates - Software Update Management
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Updates
|
||||
Copyright 2021
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 2020
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
347
LICENSE.GPLv2_CP
347
LICENSE.GPLv2_CP
@ -1,347 +0,0 @@
|
||||
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>
|
||||
<groupId>com.dorkbox</groupId>
|
||||
<artifactId>VaadinUndertow</artifactId>
|
||||
<version>14.10</version>
|
||||
<version>14.8</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
@ -33,7 +33,7 @@ Gradle Info
|
||||
```
|
||||
dependencies {
|
||||
...
|
||||
implementation "com.dorkbox:VaadinUndertow:14.10"
|
||||
implementation "com.dorkbox:VaadinUndertow:14.8"
|
||||
}
|
||||
````
|
||||
|
||||
@ -41,3 +41,4 @@ License
|
||||
---------
|
||||
This project is © 2020 dorkbox llc, and is distributed under the terms of the Apache v2.0 License. See file "LICENSE" for further
|
||||
references.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -20,13 +20,18 @@
|
||||
////// RELEASE : (to sonatype/maven central), <'publish and release' - 'publishToSonatypeAndRelease'>
|
||||
///////////////////////////////
|
||||
|
||||
plugins {
|
||||
id("com.dorkbox.GradleUtils") version "3.18"
|
||||
id("com.dorkbox.Licensing") version "2.28"
|
||||
id("com.dorkbox.VersionUpdate") version "2.8"
|
||||
id("com.dorkbox.GradlePublish") version "1.20"
|
||||
import dorkbox.gradle.kotlin
|
||||
import java.time.Instant
|
||||
|
||||
kotlin("jvm") version "1.9.0"
|
||||
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace!
|
||||
|
||||
plugins {
|
||||
id("com.dorkbox.GradleUtils") version "2.16"
|
||||
id("com.dorkbox.Licensing") version "2.12"
|
||||
id("com.dorkbox.VersionUpdate") version "2.4"
|
||||
id("com.dorkbox.GradlePublish") version "1.12"
|
||||
|
||||
kotlin("jvm") version "1.6.10"
|
||||
}
|
||||
|
||||
object Extras {
|
||||
@ -34,17 +39,21 @@ object Extras {
|
||||
const val group = "com.dorkbox"
|
||||
const val name = "VaadinUndertow"
|
||||
const val id = "VaadinUndertow"
|
||||
|
||||
const val version = "14.10"
|
||||
const val version = "14.7.5"
|
||||
|
||||
const val vendor = "Dorkbox LLC"
|
||||
const val vendorUrl = "https://dorkbox.com"
|
||||
const val url = "https://git.dorkbox.com/dorkbox/VaadinUndertow"
|
||||
|
||||
val buildDate = Instant.now().toString()
|
||||
|
||||
// These MUST be in lock-step with what the GradleVaadin (other project) + gradle.build.kts + VaadinApplication.kt define, otherwise horrific errors can occur.
|
||||
const val vaadinVer = "14.10.1"
|
||||
const val undertowVer = "2.2.22.Final"
|
||||
const val coroutineVer = "1.6.0"
|
||||
|
||||
// these BOTH must match the version information in the VaadinApplication.kt file (this is automatically passed into the plugin)
|
||||
const val vaadinVer = "14.7.8"
|
||||
|
||||
// These MUST be in lock-step with what the GradleVaadin launcher defines, otherwise horrific errors can occur.
|
||||
const val undertowVer = "2.2.16.Final"
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
@ -52,8 +61,8 @@ object Extras {
|
||||
///////////////////////////////
|
||||
GradleUtils.load("$projectDir/../../gradle.properties", Extras)
|
||||
GradleUtils.defaults()
|
||||
GradleUtils.compileConfiguration(JavaVersion.VERSION_11)
|
||||
//GradleUtils.jpms(JavaVersion.VERSION_1_9) vaadin doesn't support jpms yet
|
||||
GradleUtils.compileConfiguration(JavaVersion.VERSION_1_8)
|
||||
//GradleUtils.jpms(JavaVersion.VERSION_1_9)
|
||||
|
||||
|
||||
licensing {
|
||||
@ -92,7 +101,7 @@ licensing {
|
||||
|
||||
dependencies {
|
||||
api("org.jetbrains.kotlin:kotlin-reflect")
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Extras.coroutineVer}")
|
||||
|
||||
compileOnly("com.vaadin:vaadin:${Extras.vaadinVer}")
|
||||
|
||||
@ -101,30 +110,29 @@ dependencies {
|
||||
api("io.undertow:undertow-servlet:${Extras.undertowVer}")
|
||||
api("io.undertow:undertow-websockets-jsr:${Extras.undertowVer}")
|
||||
|
||||
|
||||
// Uber-fast, ultra-lightweight Java classpath and module path scanner
|
||||
api("io.github.classgraph:classgraph:4.8.160")
|
||||
api("io.github.classgraph:classgraph:4.8.141")
|
||||
|
||||
api("com.dorkbox:Updates:1.1")
|
||||
api("com.dorkbox:FSM:1.0")
|
||||
|
||||
// implementation("com.conversantmedia:disruptor:1.2.19")
|
||||
|
||||
// awesome logging framework for kotlin.
|
||||
// https://www.reddit.com/r/Kotlin/comments/8gbiul/slf4j_loggers_in_3_ways/
|
||||
// https://github.com/MicroUtils/kotlin-logging
|
||||
api("io.github.microutils:kotlin-logging:3.0.5")
|
||||
api("io.github.microutils:kotlin-logging:2.1.21")
|
||||
|
||||
// 1.8.0-beta4 supports jpms
|
||||
api("org.slf4j:slf4j-api:2.0.7")
|
||||
api("org.slf4j:jul-to-slf4j:2.0.7")
|
||||
api("org.slf4j:slf4j-api:1.8.0-beta4")
|
||||
api("org.slf4j:jul-to-slf4j:1.8.0-beta4")
|
||||
|
||||
|
||||
// api("ch.qos.logback:logback-core:1.4.5")
|
||||
// compileOnly("ch.qos.logback:logback-classic:1.4.5")
|
||||
//
|
||||
//
|
||||
// testImplementation("com.vaadin:vaadin:${Extras.vaadinVer}")
|
||||
// testImplementation("ch.qos.logback:logback-classic:1.4.5")
|
||||
api("ch.qos.logback:logback-core:1.3.0-alpha4")
|
||||
compileOnly("ch.qos.logback:logback-classic:1.3.0-alpha4")
|
||||
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
}
|
||||
|
||||
tasks.jar.get().apply {
|
||||
@ -137,7 +145,7 @@ tasks.jar.get().apply {
|
||||
attributes["Specification-Vendor"] = Extras.vendor
|
||||
|
||||
attributes["Implementation-Title"] = "${Extras.group}.${Extras.id}"
|
||||
attributes["Implementation-Version"] = GradleUtils.now()
|
||||
attributes["Implementation-Version"] = Extras.buildDate
|
||||
attributes["Implementation-Vendor"] = Extras.vendor
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
<!--
|
||||
~ 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
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -13,4 +13,3 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
rootProject.name = "VaadinUndertow"
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,12 +17,13 @@ package dorkbox.vaadin
|
||||
|
||||
import com.vaadin.flow.server.VaadinContext
|
||||
import com.vaadin.flow.server.frontend.FrontendUtils
|
||||
import dorkbox.fsm.DoubleArrayStringTrie
|
||||
import dorkbox.vaadin.devMode.DevModeInitializer
|
||||
import dorkbox.vaadin.undertow.*
|
||||
import dorkbox.vaadin.util.CallingClass
|
||||
import dorkbox.vaadin.util.TrieClassLoader
|
||||
import dorkbox.vaadin.util.UndertowBuilder
|
||||
import dorkbox.vaadin.util.VaadinConfig
|
||||
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
|
||||
import io.github.classgraph.ClassGraph
|
||||
import io.github.classgraph.ScanResult
|
||||
import io.undertow.Undertow
|
||||
@ -44,13 +45,11 @@ import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.atomic.*
|
||||
import javax.servlet.Servlet
|
||||
import javax.servlet.ServletContainerInitializer
|
||||
import javax.servlet.ServletRequest
|
||||
import javax.servlet.ServletResponse
|
||||
import javax.servlet.SessionTrackingMode
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.servlet.*
|
||||
import javax.servlet.annotation.HandlesTypes
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@ -64,17 +63,11 @@ class VaadinApplication : ExceptionHandler {
|
||||
/**
|
||||
* Gets the version number.
|
||||
*/
|
||||
const val version = "14.10"
|
||||
const val version = "14.7.4"
|
||||
|
||||
// this must match the version information in the build.gradle.kts file (this is automatically passed into the plugin)
|
||||
const val vaadinVersion = "14.10.1"
|
||||
const val undertowVersion = "2.2.21.Final"
|
||||
|
||||
// Vaadin 14.9 changed how license checking works, and doesn't include this.
|
||||
const val oshiVersion = "6.4.0"
|
||||
|
||||
// license checker requires JNA
|
||||
const val jnaVersion = "5.12.1" //5.13 isn't properly published? It cannot be found.
|
||||
const val vaadinVersion = "14.7.8"
|
||||
const val undertowVersion = "2.2.14.Final"
|
||||
|
||||
init {
|
||||
// Add this project to the updates system, which verifies this class + UUID + version information
|
||||
@ -109,9 +102,9 @@ class VaadinApplication : ExceptionHandler {
|
||||
private lateinit var servletHttpHandler: HttpHandler
|
||||
private lateinit var servletManager: DeploymentManager
|
||||
|
||||
private lateinit var jarStringTrie: DoubleArrayStringTrie<String>
|
||||
private lateinit var jarUrlTrie: DoubleArrayStringTrie<URL>
|
||||
private lateinit var diskTrie: DoubleArrayStringTrie<URL>
|
||||
private lateinit var jarStringTrie: DoubleArrayTrie<String>
|
||||
private lateinit var jarUrlTrie: DoubleArrayTrie<URL>
|
||||
private lateinit var diskTrie: DoubleArrayTrie<URL>
|
||||
|
||||
private lateinit var servletBuilder: DeploymentInfo
|
||||
private lateinit var serverBuilder: UndertowBuilder
|
||||
@ -366,14 +359,16 @@ class VaadinApplication : ExceptionHandler {
|
||||
}
|
||||
|
||||
// EVERYTHING IS ACCESSED VIA TRIE, NOT VIA HASHMAP! (it's faster this way)
|
||||
jarStringTrie = DoubleArrayStringTrie(jarStringResourceRequestMap)
|
||||
jarUrlTrie = DoubleArrayStringTrie(jarUrlResourceRequestMap)
|
||||
diskTrie = DoubleArrayStringTrie(diskResourceRequestMap)
|
||||
jarStringTrie = DoubleArrayTrie(jarStringResourceRequestMap)
|
||||
jarUrlTrie = DoubleArrayTrie(jarUrlResourceRequestMap)
|
||||
diskTrie = DoubleArrayTrie(diskResourceRequestMap)
|
||||
|
||||
|
||||
// so we can use the undertow cache to serve resources, instead of the vaadin servlet (which doesn't cache, and is really slow)
|
||||
// NOTE: this will load the stats.json file!
|
||||
|
||||
val debug = vaadinConfig.debug
|
||||
|
||||
// for our classloader, we have to make sure that we are BY DIRECTORY, not by file, for the resource array!
|
||||
val toTypedArray = jarLocations.map { it.resourceDir }.toSet().toTypedArray()
|
||||
this.trieClassLoader = TrieClassLoader(diskTrie, jarStringTrie, toTypedArray, this.javaClass.classLoader, logger)
|
||||
@ -589,7 +584,7 @@ class VaadinApplication : ExceptionHandler {
|
||||
// instead of the default, we load **OUR** dev-mode initializer.
|
||||
// The vaadin one is super buggy for custom environments
|
||||
servletBuilder.addServletContainerInitializer(
|
||||
ServletContainerInitializerInfo(com.vaadin.flow.server.startup.DevModeInitializer::class.java, classSet))
|
||||
ServletContainerInitializerInfo(DevModeInitializer::class.java, classSet))
|
||||
}
|
||||
} else {
|
||||
// do not load the dev-mode initializer for production mode
|
||||
|
45
src/dorkbox/vaadin/devMode/DevModeClassFinder.kt
Normal file
45
src/dorkbox/vaadin/devMode/DevModeClassFinder.kt
Normal file
@ -0,0 +1,45 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
933
src/dorkbox/vaadin/devMode/DevModeHandler.kt
Normal file
933
src/dorkbox/vaadin/devMode/DevModeHandler.kt
Normal file
@ -0,0 +1,933 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
508
src/dorkbox/vaadin/devMode/DevModeInitializer.kt
Normal file
508
src/dorkbox/vaadin/devMode/DevModeInitializer.kt
Normal file
@ -0,0 +1,508 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
131
src/dorkbox/vaadin/devMode/DevServerWatchDog.kt
Normal file
131
src/dorkbox/vaadin/devMode/DevServerWatchDog.kt
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
212
src/dorkbox/vaadin/devMode/HandlerHelper.kt
Normal file
212
src/dorkbox/vaadin/devMode/HandlerHelper.kt
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
}
|
170
src/dorkbox/vaadin/undertow/CoroutineHttpWrapper.kt
Normal file
170
src/dorkbox/vaadin/undertow/CoroutineHttpWrapper.kt
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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,7 +18,6 @@
|
||||
|
||||
package dorkbox.vaadin.undertow
|
||||
|
||||
import dorkbox.vaadin.util.logger
|
||||
import io.undertow.UndertowLogger
|
||||
import io.undertow.io.IoCallback
|
||||
import io.undertow.predicate.Predicate
|
||||
@ -32,12 +31,12 @@ import io.undertow.server.handlers.cache.ResponseCache
|
||||
import io.undertow.server.handlers.encoding.ContentEncodedResourceManager
|
||||
import io.undertow.server.handlers.resource.*
|
||||
import io.undertow.util.*
|
||||
import org.slf4j.Logger
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Suppress("unused")
|
||||
/**
|
||||
@ -50,7 +49,6 @@ import java.util.concurrent.*
|
||||
*/
|
||||
class DirectResourceHandler(@Volatile private var resourceManager: ResourceManager?,
|
||||
@Volatile private var resourceSupplier: ResourceSupplier = DefaultResourceSupplier(resourceManager),
|
||||
val logger: Logger,
|
||||
private val next: HttpHandler = ResponseCodeHandler.HANDLE_404) : HttpHandler {
|
||||
|
||||
private val welcomeFiles = CopyOnWriteArrayList(arrayOf("index.html", "index.htm", "default.html", "default.htm"))
|
||||
@ -453,13 +451,12 @@ class DirectResourceHandler(@Volatile private var resourceManager: ResourceManag
|
||||
|
||||
}
|
||||
|
||||
private class Wrapper(private val location: String,
|
||||
private val allowDirectoryListing: Boolean) : HandlerWrapper {
|
||||
private class Wrapper internal constructor(private val location: String,
|
||||
private val allowDirectoryListing: Boolean) : HandlerWrapper {
|
||||
|
||||
override fun wrap(handler: HttpHandler): HttpHandler {
|
||||
val resourceManager = PathResourceManager(Paths.get(location), 1024)
|
||||
// use a default logger
|
||||
val resourceHandler = DirectResourceHandler(resourceManager, DefaultResourceSupplier(resourceManager), logger())
|
||||
val rm = PathResourceManager(Paths.get(location), 1024)
|
||||
val resourceHandler = DirectResourceHandler(rm)
|
||||
resourceHandler.setDirectoryListingEnabled(allowDirectoryListing)
|
||||
return resourceHandler
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
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,19 +1,3 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
@ -31,13 +15,13 @@
|
||||
*/
|
||||
package dorkbox.vaadin.undertow
|
||||
|
||||
import dorkbox.fsm.DoubleArrayStringTrie
|
||||
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
|
||||
import io.undertow.UndertowMessages
|
||||
import io.undertow.server.handlers.resource.Resource
|
||||
import io.undertow.server.handlers.resource.ResourceChangeListener
|
||||
import io.undertow.server.handlers.resource.ResourceManager
|
||||
import io.undertow.server.handlers.resource.URLResource
|
||||
import org.slf4j.Logger
|
||||
import mu.KLogger
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
|
||||
@ -48,25 +32,20 @@ import java.net.URL
|
||||
* @author Andy Wilkinson
|
||||
* @author Dorkbox LLC
|
||||
*/
|
||||
internal class JarResourceManager(val name: String,
|
||||
private val trie: DoubleArrayStringTrie<URL>,
|
||||
private val logger: Logger) : ResourceManager {
|
||||
internal class JarResourceManager(val name: String, val trie: DoubleArrayTrie<URL>, val logger: KLogger) : ResourceManager {
|
||||
|
||||
@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
|
||||
}
|
||||
logger.trace { "REQUEST jar: $path" }
|
||||
|
||||
val url = trie[path] ?: return null
|
||||
logger.trace { "TRIE: $url" }
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -1,25 +0,0 @@
|
||||
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 2024 dorkbox, llc
|
||||
* 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.
|
||||
@ -16,14 +16,14 @@
|
||||
|
||||
package dorkbox.vaadin.undertow
|
||||
|
||||
import dorkbox.vaadin.util.logger
|
||||
import io.undertow.UndertowMessages
|
||||
import io.undertow.server.handlers.resource.Resource
|
||||
import io.undertow.server.handlers.resource.ResourceChangeListener
|
||||
import io.undertow.server.handlers.resource.ResourceManager
|
||||
import mu.KotlinLogging
|
||||
|
||||
class ResourceCollectionManager(private val resources: List<ResourceManager>) : ResourceManager {
|
||||
private val logger = logger()
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
private val changeListenerSupported : Boolean by lazy {
|
||||
var supported = true
|
||||
@ -77,7 +77,7 @@ class ResourceCollectionManager(private val resources: List<ResourceManager>) :
|
||||
try {
|
||||
it.close()
|
||||
} catch (e: Exception) {
|
||||
logger.error("Error closing resourceManager", e)
|
||||
logger.error(e) { "Error closing resourceManager" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
71
src/dorkbox/vaadin/undertow/StrictFileResourceManager.kt
Normal file
71
src/dorkbox/vaadin/undertow/StrictFileResourceManager.kt
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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)"
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
/*
|
||||
* 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 2024 dorkbox, llc
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,22 +17,4 @@ package dorkbox.vaadin.undertow
|
||||
|
||||
import java.net.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')"
|
||||
}
|
||||
}
|
||||
data class WebResource(val requestPath: String, val resourcePath: URL)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 dorkbox, llc
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,25 +17,4 @@ package dorkbox.vaadin.undertow
|
||||
|
||||
import java.net.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')"
|
||||
}
|
||||
}
|
||||
data class WebResourceString(val requestPath: String, val resourcePath: URL, val relativeResourcePath: String, val resourceDir: URL)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 dorkbox, llc
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,7 +16,7 @@
|
||||
package dorkbox.vaadin.util
|
||||
|
||||
/**
|
||||
* This class is overridden by the multi-source compile for java 9+
|
||||
*
|
||||
*/
|
||||
object CallingClass {
|
||||
fun get(): Class<*> {
|
||||
|
@ -1,19 +1,3 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
@ -32,20 +16,16 @@
|
||||
|
||||
package dorkbox.vaadin.util
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import mu.KLogger
|
||||
import mu.KotlinLogging
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
inline fun <reified T> T.logger(): Logger {
|
||||
inline fun <reified T> T.logger(): KLogger {
|
||||
if (T::class.isCompanion) {
|
||||
return LoggerFactory.getLogger(T::class.java.enclosingClass.name)
|
||||
return KotlinLogging.logger(T::class.java.enclosingClass.name)
|
||||
}
|
||||
return LoggerFactory.getLogger(T::class.java.name)
|
||||
}
|
||||
|
||||
inline fun <reified T> T.logger(name: String): Logger {
|
||||
return LoggerFactory.getLogger(name)
|
||||
return KotlinLogging.logger(T::class.java.name)
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,19 +1,3 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
@ -32,7 +16,7 @@
|
||||
|
||||
package dorkbox.vaadin.util
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import mu.KotlinLogging
|
||||
|
||||
private const val MAX_POW2 = 1 shl 30
|
||||
|
||||
@ -43,13 +27,10 @@ inline fun <reified T : Any> validatePowerOfTwoCapacity(caller: T,
|
||||
value
|
||||
}
|
||||
else -> {
|
||||
val logger = if (T::class.isCompanion) {
|
||||
LoggerFactory.getLogger(caller::class.java.enclosingClass.name)
|
||||
} else {
|
||||
LoggerFactory.getLogger(caller.javaClass.name)
|
||||
val logger = KotlinLogging.logger(caller.javaClass.name)
|
||||
logger.warn {
|
||||
"${caller.javaClass.simpleName} capacity should be a power of two, but ($value) requested. Using the next available: ${roundToNextPowerOfTwo(value)}."
|
||||
}
|
||||
|
||||
logger.warn("${caller.javaClass.simpleName} capacity should be a power of two, but ($value) requested. Using the next available: ${roundToNextPowerOfTwo(value)}.")
|
||||
roundToNextPowerOfTwo(value)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 dorkbox, llc
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -15,39 +15,33 @@
|
||||
*/
|
||||
package dorkbox.vaadin.util
|
||||
|
||||
import dorkbox.fsm.DoubleArrayStringTrie
|
||||
import org.slf4j.Logger
|
||||
import dorkbox.vaadin.util.ahoCorasick.DoubleArrayTrie
|
||||
import mu.KLogger
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
|
||||
/**
|
||||
* Used to get classloader data, but using a trie to store the lookups
|
||||
*
|
||||
*/
|
||||
class TrieClassLoader(
|
||||
private val diskTrie: DoubleArrayStringTrie<URL>,
|
||||
private val jarTrie: DoubleArrayStringTrie<String>,
|
||||
|
||||
jarResourceDirs: Array<URL>, parentClassloader: ClassLoader,
|
||||
|
||||
private val logger: Logger,
|
||||
private val loggerDisk: Logger,
|
||||
private val loggerJar: Logger,
|
||||
|
||||
): URLClassLoader(jarResourceDirs, parentClassloader) {
|
||||
private val urlTrie: DoubleArrayTrie<URL>,
|
||||
private val stringTrie: DoubleArrayTrie<String>,
|
||||
urls: Array<URL>, parent: ClassLoader,
|
||||
private val logger: KLogger): URLClassLoader(urls, parent) {
|
||||
|
||||
override fun getResource(name: String): URL? {
|
||||
logger.trace(name)
|
||||
logger.trace { "URL Classloader: $name" }
|
||||
|
||||
// check disk first
|
||||
val diskResourcePath: URL? = diskTrie[name]
|
||||
val diskResourcePath: URL? = urlTrie[name]
|
||||
if (diskResourcePath != null) {
|
||||
loggerDisk.trace(diskResourcePath.file)
|
||||
logger.trace { "TRIE: $diskResourcePath" }
|
||||
return diskResourcePath
|
||||
}
|
||||
|
||||
val jarResourcePath: String? = jarTrie[name]
|
||||
val jarResourcePath: String? = stringTrie[name]
|
||||
if (jarResourcePath != null) {
|
||||
loggerJar.trace(jarResourcePath)
|
||||
logger.trace { "TRIE: $jarResourcePath" }
|
||||
return super.getResource(jarResourcePath)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -18,38 +18,16 @@ package dorkbox.vaadin.util
|
||||
import io.undertow.Undertow
|
||||
import io.undertow.Undertow.ListenerBuilder
|
||||
import io.undertow.connector.ByteBufferPool
|
||||
import io.undertow.security.api.AuthenticationMechanism
|
||||
import io.undertow.security.api.AuthenticationMode
|
||||
import io.undertow.security.handlers.AuthenticationCallHandler
|
||||
import io.undertow.security.handlers.AuthenticationConstraintHandler
|
||||
import io.undertow.security.handlers.AuthenticationMechanismsHandler
|
||||
import io.undertow.security.handlers.SecurityInitialHandler
|
||||
import io.undertow.security.idm.IdentityManager
|
||||
import io.undertow.security.impl.BasicAuthenticationMechanism
|
||||
import io.undertow.server.HttpHandler
|
||||
import org.xnio.Option
|
||||
import org.xnio.XnioWorker
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.Executor
|
||||
import javax.net.ssl.KeyManager
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
|
||||
@Suppress("unused")
|
||||
class UndertowBuilder() {
|
||||
companion object {
|
||||
fun addSecurity(toWrap: HttpHandler, identityManager: IdentityManager): HttpHandler {
|
||||
var handler = toWrap
|
||||
handler = AuthenticationCallHandler(handler)
|
||||
handler = AuthenticationConstraintHandler(handler)
|
||||
|
||||
val mechanisms = listOf<AuthenticationMechanism>(BasicAuthenticationMechanism("My Realm"))
|
||||
handler = AuthenticationMechanismsHandler(handler, mechanisms)
|
||||
handler = SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, identityManager, handler)
|
||||
|
||||
return handler
|
||||
}
|
||||
}
|
||||
|
||||
private val builder = Undertow.builder()
|
||||
|
||||
// the LAST http (or https) listener will recorded as the listener used.
|
||||
@ -175,6 +153,7 @@ class UndertowBuilder() {
|
||||
* to the various worker-related configuration (ioThreads, workerThreads, workerOptions)
|
||||
* when [Undertow.start] is called.
|
||||
* Additionally, this newly created worker will be shutdown when [Undertow.stop] is called.
|
||||
* <br></br>
|
||||
*
|
||||
*
|
||||
* When non-null, the provided [XnioWorker] will be reused instead of creating a new [XnioWorker]
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023 dorkbox, llc
|
||||
* Copyright 2021 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -74,8 +74,11 @@ class VaadinConfig(runningAsJar: Boolean, tempDir: File) {
|
||||
// this file from the jar to the temp location.
|
||||
File(tempDir, defaultTokenFile).absolutePath
|
||||
} else {
|
||||
// loading as a FILE, so we must make sure
|
||||
tokenInJar.path
|
||||
if (tokenInJar.path.startsWith("/")) {
|
||||
tokenInJar.path.substring(1)
|
||||
} else {
|
||||
tokenInJar.path
|
||||
}
|
||||
}
|
||||
|
||||
tokenJson = JsonUtil.parse(tokenInJar.readText(Charsets.UTF_8)) as JsonObject?
|
||||
@ -94,8 +97,8 @@ class VaadinConfig(runningAsJar: Boolean, tempDir: File) {
|
||||
|
||||
if (tokenFileName.isEmpty() || tokenJson == null || !tokenJson.hasKey(InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE)) {
|
||||
// this is a problem! we must configure the system first via gradle!
|
||||
throw java.lang.RuntimeException("Unable to continue! Error reading token! " +
|
||||
"You must FIRST compile the vaadin resources for Development or Production mode!")
|
||||
throw java.lang.RuntimeException("Unable to continue! Error reading token!" +
|
||||
"You must FIRST compile the vaadin resources for DEV or PRODUCTION mode!")
|
||||
}
|
||||
|
||||
|
||||
@ -147,7 +150,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)
|
||||
servlet
|
||||
.addInitParam(InitParameters.EXTERNAL_STATS_FILE, "true")
|
||||
.addInitParam(InitParameters.EXTERNAL_STATS_URL, url)
|
||||
.addInitParam(InitParameters.EXTERNAL_STATS_URL, "$url")
|
||||
}
|
||||
|
||||
/**
|
||||
|
1036
src/dorkbox/vaadin/util/ahoCorasick/DoubleArrayTrie.kt
Normal file
1036
src/dorkbox/vaadin/util/ahoCorasick/DoubleArrayTrie.kt
Normal file
File diff suppressed because it is too large
Load Diff
202
src/dorkbox/vaadin/util/ahoCorasick/State.kt
Normal file
202
src/dorkbox/vaadin/util/ahoCorasick/State.kt
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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,73 +2,74 @@ module dorkbox.vaadin {
|
||||
exports dorkbox.vaadin;
|
||||
exports dorkbox.vaadin.util;
|
||||
|
||||
requires transitive dorkbox.updates;
|
||||
requires transitive org.slf4j;
|
||||
requires dorkbox.updates;
|
||||
requires org.slf4j;
|
||||
|
||||
// requires static ch.qos.logback.classic;
|
||||
|
||||
// requires vaadin;
|
||||
|
||||
//Vaadin UI components used
|
||||
requires transitive flow.data;
|
||||
requires transitive flow.server;
|
||||
requires transitive vaadin.accordion.flow;
|
||||
requires transitive vaadin.accordion;
|
||||
requires transitive vaadin.app.layout.flow;
|
||||
requires transitive vaadin.app.layout;
|
||||
requires transitive vaadin.button.flow;
|
||||
requires transitive vaadin.button;
|
||||
requires transitive vaadin.charts.flow;
|
||||
requires transitive vaadin.charts;
|
||||
requires transitive vaadin.checkbox.flow;
|
||||
requires transitive vaadin.checkbox;
|
||||
requires transitive vaadin.combo.box.flow;
|
||||
requires transitive vaadin.combo.box;
|
||||
requires transitive vaadin.confirm.dialog.flow;
|
||||
requires transitive vaadin.confirm.dialog;
|
||||
requires transitive vaadin.context.menu.flow;
|
||||
requires transitive vaadin.context.menu;
|
||||
requires transitive vaadin.control.state.mixin;
|
||||
requires transitive vaadin.cookie.consent.flow;
|
||||
requires transitive vaadin.cookie.consent;
|
||||
requires transitive vaadin.core;
|
||||
requires transitive vaadin.custom.field.flow;
|
||||
requires transitive vaadin.custom.field;
|
||||
requires transitive vaadin.date.picker.flow;
|
||||
requires transitive vaadin.date.picker;
|
||||
requires transitive vaadin.details.flow;
|
||||
requires transitive vaadin.details;
|
||||
requires transitive vaadin.development.mode.detector;
|
||||
requires transitive vaadin.dialog.flow;
|
||||
requires transitive vaadin.dialog;
|
||||
requires transitive vaadin.element.mixin;
|
||||
requires transitive vaadin.grid.flow;
|
||||
requires transitive vaadin.list.box.flow;
|
||||
requires transitive vaadin.list.box;
|
||||
requires transitive vaadin.list.mixin;
|
||||
requires transitive vaadin.login.flow;
|
||||
requires transitive vaadin.login;
|
||||
requires transitive vaadin.lumo.theme;
|
||||
requires transitive vaadin.ordered.layout.flow;
|
||||
requires transitive vaadin.ordered.layout;
|
||||
requires transitive vaadin.progress.bar.flow;
|
||||
requires transitive vaadin.progress.bar;
|
||||
requires transitive vaadin.radio.button.flow;
|
||||
requires transitive vaadin.radio.button;
|
||||
requires transitive vaadin.text.field.flow;
|
||||
requires transitive vaadin.text.field;
|
||||
requires transitive vaadin.upload.flow;
|
||||
requires transitive vaadin.upload;
|
||||
requires transitive vaadin.usage.statistics;
|
||||
requires flow.data;
|
||||
requires flow.server;
|
||||
requires vaadin.accordion.flow;
|
||||
requires vaadin.accordion;
|
||||
requires vaadin.app.layout.flow;
|
||||
requires vaadin.app.layout;
|
||||
requires vaadin.button.flow;
|
||||
requires vaadin.button;
|
||||
requires vaadin.charts.flow;
|
||||
requires vaadin.charts;
|
||||
requires vaadin.checkbox.flow;
|
||||
requires vaadin.checkbox;
|
||||
requires vaadin.combo.box.flow;
|
||||
requires vaadin.combo.box;
|
||||
requires vaadin.confirm.dialog.flow;
|
||||
requires vaadin.confirm.dialog;
|
||||
requires vaadin.context.menu.flow;
|
||||
requires vaadin.context.menu;
|
||||
requires vaadin.control.state.mixin;
|
||||
requires vaadin.cookie.consent.flow;
|
||||
requires vaadin.cookie.consent;
|
||||
requires vaadin.core;
|
||||
requires vaadin.custom.field.flow;
|
||||
requires vaadin.custom.field;
|
||||
requires vaadin.date.picker.flow;
|
||||
requires vaadin.date.picker;
|
||||
requires vaadin.details.flow;
|
||||
requires vaadin.details;
|
||||
requires vaadin.development.mode.detector;
|
||||
requires vaadin.dialog.flow;
|
||||
requires vaadin.dialog;
|
||||
requires vaadin.element.mixin;
|
||||
requires vaadin.grid.flow;
|
||||
requires vaadin.list.box.flow;
|
||||
requires vaadin.list.box;
|
||||
requires vaadin.list.mixin;
|
||||
requires vaadin.login.flow;
|
||||
requires vaadin.login;
|
||||
requires vaadin.lumo.theme;
|
||||
requires vaadin.ordered.layout.flow;
|
||||
requires vaadin.ordered.layout;
|
||||
requires vaadin.progress.bar.flow;
|
||||
requires vaadin.progress.bar;
|
||||
requires vaadin.radio.button.flow;
|
||||
requires vaadin.radio.button;
|
||||
requires vaadin.text.field.flow;
|
||||
requires vaadin.text.field;
|
||||
requires vaadin.upload.flow;
|
||||
requires vaadin.upload;
|
||||
requires vaadin.usage.statistics;
|
||||
|
||||
requires transitive xnio.api;
|
||||
requires xnio.api;
|
||||
|
||||
// requires com.conversantmedia.disruptor;
|
||||
requires transitive io.github.classgraph;
|
||||
requires io.github.classgraph;
|
||||
|
||||
requires transitive undertow.core;
|
||||
requires transitive undertow.servlet;
|
||||
requires transitive undertow.websockets.jsr;
|
||||
requires undertow.core;
|
||||
requires undertow.servlet;
|
||||
requires undertow.websockets.jsr;
|
||||
|
||||
requires transitive kotlin.stdlib;
|
||||
requires kotlin.stdlib;
|
||||
requires java.base;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user