com.vaadin.tools.CvalChecker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin-client-compiler Show documentation
Show all versions of vaadin-client-compiler Show documentation
Vaadin is a web application framework for Rich Internet Applications (RIA).
Vaadin enables easy development and maintenance of fast and
secure rich web
applications with a stunning look and feel and a wide browser support.
It features a server-side architecture with the majority of the logic
running
on the server. Ajax technology is used at the browser-side to ensure a
rich
and interactive user experience.
/*
* Copyright 2000-2014 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 com.vaadin.tools;
import static java.lang.Integer.parseInt;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.prefs.Preferences;
import org.apache.commons.io.IOUtils;
import elemental.json.JsonException;
import elemental.json.JsonNull;
import elemental.json.JsonObject;
import elemental.json.impl.JsonUtil;
/**
* This class is able to validate the vaadin CVAL license.
*
* It reads the developer license file and asks the server to validate the
* licenseKey. If the license is invalid it throws an exception with the
* information about the problem and the server response.
*
* @since 7.3
*/
public final class CvalChecker {
/*
* Class used for binding the JSON gotten from server.
*
* It is not in a separate f le, so as it is easier to copy into any product
* which does not depend on vaadin core.
*
* We are using elemental.json in order not to use additional dependency
* like auto-beans, gson, etc.
*/
public static class CvalInfo {
public static class Product {
private JsonObject o;
public Product(JsonObject o) {
this.o = o;
}
public String getName() {
return get(o, "name", String.class);
}
public Integer getVersion() {
return get(o, "version", Integer.class);
}
}
@SuppressWarnings("unchecked")
private static T get(JsonObject o, String k, Class clz) {
Object ret = null;
try {
if (o == null || o.get(k) == null
|| o.get(k) instanceof JsonNull) {
return null;
}
if (clz == String.class) {
ret = o.getString(k);
} else if (clz == JsonObject.class) {
ret = o.getObject(k);
} else if (clz == Integer.class) {
ret = Integer.valueOf((int) o.getNumber(k));
} else if (clz == Date.class) {
ret = new Date((long) o.getNumber(k));
} else if (clz == Boolean.class) {
ret = o.getBoolean(k);
}
} catch (JsonException e) {
}
return (T) ret;
}
private JsonObject o;
private Product product;
public CvalInfo(JsonObject o) {
this.o = o;
product = new Product(get(o, "product", JsonObject.class));
}
public Boolean getExpired() {
return get(o, "expired", Boolean.class);
}
public Date getExpiredEpoch() {
return get(o, "expiredEpoch", Date.class);
}
public String getLicensee() {
return get(o, "licensee", String.class);
}
public String getLicenseKey() {
return get(o, "licenseKey", String.class);
}
public String getMessage() {
return get(o, "message", String.class);
}
public Product getProduct() {
return product;
}
public String getType() {
return get(o, "type", String.class);
}
public void setExpiredEpoch(Date expiredEpoch) {
o.put("expiredEpoch", expiredEpoch.getTime());
}
public void setMessage(String msg) {
o.put("message", msg);
}
@Override
public String toString() {
return o.toString();
}
public boolean isLicenseExpired() {
return (getExpired() != null && getExpired())
|| (getExpiredEpoch() != null && getExpiredEpoch().before(
new Date()));
}
public boolean isValidVersion(int majorVersion) {
return getProduct().getVersion() == null
|| getProduct().getVersion() >= majorVersion;
}
private boolean isValidInfo(String name, String key) {
return getProduct() != null && getProduct().getName() != null
&& getLicenseKey() != null
&& getProduct().getName().equals(name)
&& getLicenseKey().equals(key);
}
}
/*
* The class with the method for getting json from server side. It is here
* and protected just for replacing it in tests.
*/
public static class CvalServer {
protected String licenseUrl = LICENSE_URL_PROD;
String askServer(String productName, String productKey, int timeoutMs)
throws IOException {
String url = licenseUrl + productKey;
URLConnection con;
try {
// Send some additional info in the User-Agent string.
String ua = "Cval " + productName + " " + productKey + " "
+ getFirstLaunch();
for (String prop : Arrays.asList("java.vendor.url",
"java.version", "os.name", "os.version", "os.arch")) {
ua += " " + System.getProperty(prop, "-").replace(" ", "_");
}
con = new URL(url).openConnection();
con.setRequestProperty("User-Agent", ua);
con.setConnectTimeout(timeoutMs);
con.setReadTimeout(timeoutMs);
String r = IOUtils.toString(con.getInputStream());
return r;
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
/*
* Get the GWT firstLaunch timestamp.
*/
String getFirstLaunch() {
try {
Class> clz = Class
.forName("com.google.gwt.dev.shell.CheckForUpdates");
return Preferences.userNodeForPackage(clz).get("firstLaunch",
"-");
} catch (ClassNotFoundException e) {
return "-";
}
}
}
/**
* Exception thrown when the user does not have a valid cval license.
*/
public static class InvalidCvalException extends Exception {
private static final long serialVersionUID = 1L;
public final CvalInfo info;
public final String name;
public final String key;
public final String version;
public final String title;
public InvalidCvalException(String name, String version, String title,
String key, CvalInfo info) {
super(composeMessage(title, version, key, info));
this.info = info;
this.name = name;
this.key = key;
this.version = version;
this.title = title;
}
static String composeMessage(String title, String version, String key,
CvalInfo info) {
String msg = "";
int majorVers = computeMajorVersion(version);
if (info != null && !info.isValidVersion(majorVers)) {
msg = getErrorMessage("invalid", title, majorVers);
} else if (info != null && info.getMessage() != null) {
msg = info.getMessage().replace("\\n", "\n");
} else if (info != null && info.isLicenseExpired()) {
String type = "evaluation".equals(info.getType()) ? "Evaluation license"
: "License";
msg = getErrorMessage("expired", title, majorVers, type);
} else if (key == null) {
msg = getErrorMessage("none", title, majorVers);
} else {
msg = getErrorMessage("invalid", title, majorVers);
}
return msg;
}
}
/**
* Exception thrown when the license server is unreachable
*/
public static class UnreachableCvalServerException extends Exception {
private static final long serialVersionUID = 1L;
public final String name;
public UnreachableCvalServerException(String name, Exception e) {
super(e);
this.name = name;
}
}
public static final String LINE = "----------------------------------------------------------------------------------------------------------------------";
static final int GRACE_DAYS_MSECS = 2 * 24 * 60 * 60 * 1000;
private static final String LICENSE_URL_PROD = "https://tools.vaadin.com/vaadin-license-server/licenses/";
/*
* used in tests
*/
static void cacheLicenseInfo(CvalInfo info) {
if (info != null) {
Preferences p = Preferences.userNodeForPackage(CvalInfo.class);
if (info.toString().length() > Preferences.MAX_VALUE_LENGTH) {
// This should never happen since MAX_VALUE_LENGTH is big
// enough.
// But server could eventually send a very big message, so we
// discard it in cache and would use hard-coded messages.
info.setMessage(null);
}
p.put(info.getProduct().getName(), info.toString());
}
}
/*
* used in tests
*/
static void deleteCache(String productName) {
Preferences p = Preferences.userNodeForPackage(CvalInfo.class);
p.remove(productName);
}
/**
* Given a product name returns the name of the file with the license key.
*
* Traditionally we have delivered license keys with a name like
* 'vaadin.touchkit.developer.license' but our database product name is
* 'vaadin-touchkit' so we have to replace '-' by '.' to maintain
* compatibility.
*/
static final String computeLicenseName(String productName) {
return productName.replace("-", ".") + ".developer.license";
}
static final int computeMajorVersion(String productVersion) {
return productVersion == null || productVersion.isEmpty() ? 0
: parseInt(productVersion.replaceFirst("[^\\d]+.*$", ""));
}
/*
* used in tests
*/
static CvalInfo parseJson(String json) {
if (json == null) {
return null;
}
try {
JsonObject o = JsonUtil.parse(json);
return new CvalInfo(o);
} catch (JsonException e) {
return null;
}
}
private CvalServer provider;
/**
* The constructor.
*/
public CvalChecker() {
setLicenseProvider(new CvalServer());
}
/**
* Validate whether there is a valid license key for a product.
*
* @param productName
* for example vaadin-touchkit
* @param productVersion
* for instance 4.0.1
* @return CvalInfo Server response or cache response if server is offline
* @throws InvalidCvalException
* when there is no a valid license for the product
* @throws UnreachableCvalServerException
* when we have license key but server is unreachable
*/
public CvalInfo validateProduct(String productName, String productVersion,
String productTitle) throws InvalidCvalException,
UnreachableCvalServerException {
String key = getDeveloperLicenseKey(productName, productVersion,
productTitle);
CvalInfo info = null;
if (key != null && !key.isEmpty()) {
info = getCachedLicenseInfo(productName);
if (info != null && !info.isValidInfo(productName, key)) {
deleteCache(productName);
info = null;
}
info = askLicenseServer(productName, key, productVersion, info);
if (info != null && info.isValidInfo(productName, key)
&& info.isValidVersion(computeMajorVersion(productVersion))
&& !info.isLicenseExpired()) {
return info;
}
}
throw new InvalidCvalException(productName, productVersion,
productTitle, key, info);
}
/*
* Change the license provider, only used in tests.
*/
final CvalChecker setLicenseProvider(CvalServer p) {
provider = p;
return this;
}
private CvalInfo askLicenseServer(String productName, String productKey,
String productVersion, CvalInfo info)
throws UnreachableCvalServerException {
int majorVersion = computeMajorVersion(productVersion);
// If we have a valid license info here, it means that we got it from
// cache.
// We add a grace time when so as if the server is unreachable
// we allow the user to use the product.
if (info != null && info.getExpiredEpoch() != null
&& !"evaluation".equals(info.getType())) {
long ts = info.getExpiredEpoch().getTime() + GRACE_DAYS_MSECS;
info.setExpiredEpoch(new Date(ts));
}
boolean validCache = info != null
&& info.isValidInfo(productName, productKey)
&& info.isValidVersion(majorVersion)
&& !info.isLicenseExpired();
// if we have a validCache we set the timeout smaller
int timeout = validCache ? 2000 : 10000;
try {
CvalInfo srvinfo = parseJson(provider.askServer(productName + "-"
+ productVersion, productKey, timeout));
if (srvinfo != null && srvinfo.isValidInfo(productName, productKey)
&& srvinfo.isValidVersion(majorVersion)) {
// We always cache the info if it is valid although it is
// expired
cacheLicenseInfo(srvinfo);
info = srvinfo;
}
} catch (FileNotFoundException e) {
// 404
return null;
} catch (Exception e) {
if (info == null) {
throw new UnreachableCvalServerException(productName, e);
}
}
return info;
}
private CvalInfo getCachedLicenseInfo(String productName) {
Preferences p = Preferences.userNodeForPackage(CvalInfo.class);
String json = p.get(productName, "");
if (!json.isEmpty()) {
CvalInfo info = parseJson(json);
if (info != null) {
return info;
}
}
return null;
}
private String getDeveloperLicenseKey(String productName,
String productVersion, String productTitle)
throws InvalidCvalException {
String licenseName = computeLicenseName(productName);
String key = System.getProperty(licenseName);
if (key != null && !key.isEmpty()) {
return key;
}
try {
String dotLicenseName = "." + licenseName;
String userHome = System.getProperty("user.home");
for (URL url : new URL[] {
new File(userHome, dotLicenseName).toURI().toURL(),
new File(userHome, licenseName).toURI().toURL(),
URL.class.getResource("/" + dotLicenseName),
URL.class.getResource("/" + licenseName) }) {
if (url != null) {
try {
key = readKeyFromFile(url,
computeMajorVersion(productVersion));
if (key != null && !(key = key.trim()).isEmpty()) {
return key;
}
} catch (IOException ignored) {
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
throw new InvalidCvalException(productName, productVersion,
productTitle, null, null);
}
String readKeyFromFile(URL url, int majorVersion) throws IOException {
String majorVersionStr = String.valueOf(majorVersion);
List lines = IOUtils.readLines(url.openStream());
String defaultKey = null;
for (String line : lines) {
String[] parts = line.split("\\s*=\\s*");
if (parts.length < 2) {
defaultKey = parts[0].trim();
}
if (parts[0].equals(majorVersionStr)) {
return parts[1].trim();
}
}
return defaultKey;
}
static String getErrorMessage(String key, Object... pars) {
Locale loc = Locale.getDefault();
ResourceBundle res = ResourceBundle.getBundle(
CvalChecker.class.getName(), loc);
String msg = res.getString(key);
return new MessageFormat(msg, loc).format(pars);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy