summaryrefslogtreecommitdiffstats
path: root/client-compiler/src/com/vaadin/tools/CvalChecker.java
diff options
context:
space:
mode:
Diffstat (limited to 'client-compiler/src/com/vaadin/tools/CvalChecker.java')
-rw-r--r--client-compiler/src/com/vaadin/tools/CvalChecker.java488
1 files changed, 488 insertions, 0 deletions
diff --git a/client-compiler/src/com/vaadin/tools/CvalChecker.java b/client-compiler/src/com/vaadin/tools/CvalChecker.java
new file mode 100644
index 0000000000..5ad44a70f8
--- /dev/null
+++ b/client-compiler/src/com/vaadin/tools/CvalChecker.java
@@ -0,0 +1,488 @@
+/*
+ * 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.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.Locale;
+import java.util.ResourceBundle;
+import java.util.prefs.Preferences;
+
+import org.apache.commons.io.IOUtils;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * 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.
+ */
+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 org.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> T get(JSONObject o, String k, Class<T> clz) {
+ Object ret = null;
+ try {
+ if (clz == String.class) {
+ ret = o.getString(k);
+ } else if (clz == JSONObject.class) {
+ ret = o.getJSONObject(k);
+ } else if (clz == Integer.class) {
+ ret = o.getInt(k);
+ } else if (clz == Date.class) {
+ ret = new Date(o.getLong(k));
+ } else if (clz == Boolean.class) {
+ ret = o.getBoolean(k);
+ }
+ } catch (JSONException e) {
+ }
+ return (T) ret;
+ }
+
+ private static <T> T put(JSONObject o, String k, Object v) {
+ try {
+ o.put(k, v);
+ } catch (JSONException e) {
+ }
+ return null;
+ }
+
+ 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) {
+ put(o, "expiredEpoch", expiredEpoch.getTime());
+ }
+
+ public void setMessage(String msg) {
+ put(o, "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.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 = new JSONObject(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 = "file://" + System.getProperty("user.home") + "/";
+ for (URL url : new URL[] { new URL(userHome + dotLicenseName),
+ new URL(userHome + licenseName),
+ URL.class.getResource("/" + dotLicenseName),
+ URL.class.getResource("/" + licenseName) }) {
+
+ if (url != null) {
+ try {
+ key = IOUtils.toString(url.openStream());
+ 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);
+ }
+
+ 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);
+ }
+}