Skip to content

Commit 9ddc511

Browse files
mcollovatimshabarov
authored andcommitted
feat: add base license checker init listener (#21969)
* feat: add base license checker init listener Provides a basic VaadinServiceInitListener implementation to perform license checking of a specific product at startup. License checking is executed only in development mode. In case of missing license keys it will either delegate the check to the Vaadin Dev Server, or open a browser to allow sign in and download a key if the dev server is disabled. * register license check index html listener only once * cleanup * cleanup
1 parent 0008674 commit 9ddc511

File tree

4 files changed

+416
-3
lines changed

4 files changed

+416
-3
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package com.vaadin.flow.server.startup;
18+
19+
import java.util.LinkedHashSet;
20+
import java.util.Set;
21+
22+
import org.jsoup.nodes.DataNode;
23+
import org.jsoup.nodes.Document;
24+
import org.jsoup.nodes.Element;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
28+
import com.vaadin.flow.internal.Pair;
29+
import com.vaadin.flow.server.ServiceInitEvent;
30+
import com.vaadin.flow.server.VaadinServiceInitListener;
31+
import com.vaadin.flow.server.communication.IndexHtmlRequestListener;
32+
import com.vaadin.flow.server.communication.IndexHtmlResponse;
33+
import com.vaadin.pro.licensechecker.BuildType;
34+
import com.vaadin.pro.licensechecker.LicenseChecker;
35+
import com.vaadin.pro.licensechecker.MissingLicenseKeyException;
36+
37+
/**
38+
* Abstract base class implementing the {@link VaadinServiceInitListener} for
39+
* initializing a license checking mechanism during the service initialization.
40+
* <p>
41+
* This class handles the validation of the license for a specific product and
42+
* its version during the initialization of the Vaadin service. The license
43+
* checking mechanism is performed only in development mode and in different
44+
* modes depending on the availability of Vaadin dev tools.
45+
* <p>
46+
* With Vaadin dev tools enabled, if the license check fails because of missing
47+
* license keys, handling is delegated to the Vaadin Dev Server so it can, for
48+
* example, display the pre-trial splash screen. However, if dev tools are
49+
* disabled, the License Checker will open the vaadin.com "Validate license"
50+
* page in a browser window to let the user log in or register and then try to
51+
* download a valid license.
52+
* <p>
53+
* Subclasses are expected to provide the product name and version required for
54+
* the license validation by invoking the constructor of this class and to
55+
* properly register the implementation for runtime discovery.
56+
* <p>
57+
* For internal use only. May be renamed or removed in a future release.
58+
*
59+
* @see VaadinServiceInitListener
60+
*/
61+
public abstract class BaseLicenseCheckerServiceInitListener
62+
implements VaadinServiceInitListener {
63+
64+
private static final Logger LOGGER = LoggerFactory
65+
.getLogger(BaseLicenseCheckerServiceInitListener.class);
66+
67+
private final String productName;
68+
private final String productVersion;
69+
70+
protected BaseLicenseCheckerServiceInitListener(String productName,
71+
String productVersion) {
72+
this.productName = productName;
73+
this.productVersion = productVersion;
74+
}
75+
76+
@Override
77+
public void serviceInit(ServiceInitEvent event) {
78+
var service = event.getSource();
79+
var configuration = service.getDeploymentConfiguration();
80+
if (!configuration.isProductionMode()) {
81+
// Using a null BuildType to allow trial licensing builds,
82+
// The variable is defined to avoid method signature ambiguity
83+
BuildType buildType = null;
84+
85+
if (configuration.isDevToolsEnabled()) {
86+
// If dev tools are enabled, do a check and then delegate
87+
// handling of a missing key to dev tools to show the splash
88+
// screen
89+
// Any other license error results in an immediate failure
90+
try {
91+
LicenseChecker.checkLicense(productName, productVersion,
92+
buildType, url -> {
93+
// this callback is called only if no local
94+
// license keys are detected
95+
throw new MissingLicenseKeyException(
96+
"No license key present");
97+
}, 0);
98+
} catch (MissingLicenseKeyException ex) {
99+
LOGGER.debug(
100+
"Missing license key for {} {} will be handled by Vaadin Dev Server",
101+
productName, productVersion);
102+
// instruct dev tools to check license at runtime
103+
CheckProductLicense.register(event, productName,
104+
productVersion);
105+
}
106+
} else {
107+
// Fallback to online validation waiting for license key
108+
// download
109+
LicenseChecker.checkLicense(productName, productVersion,
110+
buildType);
111+
}
112+
}
113+
}
114+
115+
/**
116+
* A {@link IndexHtmlRequestListener} implementation used to modify the
117+
* Vaadin runtime generated {@literal index.html} to inject additional
118+
* product-specific license information for Vaadin Dev Tools license
119+
* checking.
120+
*/
121+
private static final class CheckProductLicense
122+
implements IndexHtmlRequestListener {
123+
124+
private final Set<Pair<String, String>> products = new LinkedHashSet<>();
125+
126+
private CheckProductLicense() {
127+
}
128+
129+
static void register(ServiceInitEvent event, String productName,
130+
String productVersion) {
131+
event.getAddedIndexHtmlRequestListeners()
132+
.filter(CheckProductLicense.class::isInstance)
133+
.map(CheckProductLicense.class::cast).findFirst()
134+
.orElseGet(() -> {
135+
var listener = new CheckProductLicense();
136+
event.addIndexHtmlRequestListener(listener);
137+
return listener;
138+
}).addProduct(productName, productVersion);
139+
}
140+
141+
private void addProduct(String productName, String productVersion) {
142+
this.products.add(new Pair<>(productName, productVersion));
143+
}
144+
145+
@Override
146+
public void modifyIndexHtmlResponse(
147+
IndexHtmlResponse indexHtmlResponse) {
148+
149+
StringBuilder script = new StringBuilder(
150+
"""
151+
window.Vaadin = window.Vaadin || {};
152+
window.Vaadin.devTools = window.Vaadin.devTools || {};
153+
window.Vaadin.devTools.createdCvdlElements =
154+
window.Vaadin.devTools.createdCvdlElements || [];
155+
156+
const registerProduct = function(productName,productVersion) {
157+
const product = {};
158+
product.constructor['cvdlName'] = productName;
159+
product.constructor['version'] = productVersion
160+
product.tagName = `--${productName}`;
161+
window.Vaadin.devTools.createdCvdlElements.push(product);
162+
};
163+
""");
164+
products.forEach(product -> script.append(
165+
"registerProduct('%s','%s');".formatted(product.getFirst(),
166+
product.getSecond())));
167+
Document document = indexHtmlResponse.getDocument();
168+
Element elm = new Element("script");
169+
elm.attr("initial", "");
170+
elm.appendChild(new DataNode(script.toString()));
171+
document.head().insertChildren(0, elm);
172+
}
173+
}
174+
175+
}

0 commit comments

Comments
 (0)