summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManolo Carrasco <manolo@vaadin.com>2014-04-30 13:26:32 +0200
committerVaadin Code Review <review@vaadin.com>2014-06-26 08:30:46 +0000
commit9a84fb14f2b7fc3c405f1446a007d5b9cd796c97 (patch)
tree40f1a0420f22fa70b91f7d59d064fe9975c2c33f
parent0ff4e15b96b91becdd6c8e38ad20d8272c5455b7 (diff)
downloadvaadin-framework-9a84fb14f2b7fc3c405f1446a007d5b9cd796c97.tar.gz
vaadin-framework-9a84fb14f2b7fc3c405f1446a007d5b9cd796c97.zip
License Checker for vaadin cval products (#13696 #13474)
- This patch includes four elements: 1.- A class able to validate a licensed product against Vaadin license server. It can be used in any vaadin product (thought for non addons like TB) just adding vaadin dependency, or copying the class. 2.- A class able to inspect all addons in the classpath and figure out, based on the MANIFEST.MF info, whether we have to check developer license. 3.- A modification to Vaadin connector generator to use the classes above and to stop compilation in case. 4.- A modification to ConnectorBundleLoader, so as when a new connector is instatiated, we check whether it is using an evaluation license and show a notice. We only show the notice once. - In addition to validating developer licenses, the checker caches the server response for using it when there are connection problems. - This stuff is in Vaadin core, so as we dont maintain license code in each addon. For checking an addon license we just add the license type to the manifest when packaging the artefact. - It checks expiration time, product name and major version. Fixes: #13696 In some-way related with: #13474 Change-Id: Ib61b1c2e9c3cacd463a1ce5db02c2cfbc06851c2
-rw-r--r--.classpath1
-rw-r--r--client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java44
-rw-r--r--client-compiler/src/com/vaadin/tools/CvalAddonsChecker.java191
-rw-r--r--client-compiler/src/com/vaadin/tools/CvalChecker.java488
-rw-r--r--client-compiler/src/com/vaadin/tools/CvalChecker.properties13
-rw-r--r--client-compiler/tests/src/com/vaadin/tools/CvalAddonsCheckerTest.java184
-rw-r--r--client-compiler/tests/src/com/vaadin/tools/CvalAddonstCheckerUseCasesTest.java217
-rw-r--r--client-compiler/tests/src/com/vaadin/tools/CvalCheckerTest.java342
-rw-r--r--client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java51
-rw-r--r--client/src/com/vaadin/client/metadata/Type.java17
10 files changed, 1534 insertions, 14 deletions
diff --git a/.classpath b/.classpath
index ccbd35cffb..62c06df6c8 100644
--- a/.classpath
+++ b/.classpath
@@ -6,6 +6,7 @@
<classpathentry kind="src" path="client/src"/>
<classpathentry kind="src" path="server/src"/>
<classpathentry kind="src" path="client-compiler/src"/>
+ <classpathentry kind="src" path="client-compiler/tests/src"/>
<classpathentry kind="src" path="uitest/src"/>
<classpathentry kind="src" path="buildhelpers/src"/>
<classpathentry kind="src" path="shared/src"/>
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java b/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java
index cc1841ec05..5519dd1aae 100644
--- a/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java
+++ b/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -48,6 +48,7 @@ import com.vaadin.client.JsArrayObject;
import com.vaadin.client.ServerConnector;
import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.metadata.ConnectorBundleLoader;
+import com.vaadin.client.metadata.ConnectorBundleLoader.CValUiInfo;
import com.vaadin.client.metadata.InvokationHandler;
import com.vaadin.client.metadata.OnStateChangeMethod;
import com.vaadin.client.metadata.ProxyHandler;
@@ -70,6 +71,9 @@ import com.vaadin.shared.communication.ClientRpc;
import com.vaadin.shared.communication.ServerRpc;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.tools.CvalAddonsChecker;
+import com.vaadin.tools.CvalChecker;
+import com.vaadin.tools.CvalChecker.InvalidCvalException;
public class ConnectorBundleLoaderFactory extends Generator {
/**
@@ -211,6 +215,8 @@ public class ConnectorBundleLoaderFactory extends Generator {
}
+ private CvalAddonsChecker cvalChecker = new CvalAddonsChecker();
+
@Override
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
@@ -231,7 +237,6 @@ public class ConnectorBundleLoaderFactory extends Generator {
logger.log(Type.ERROR, getClass() + " failed", e);
throw new UnableToCompleteException();
}
-
}
private void generateClass(TreeLogger logger, GeneratorContext context,
@@ -243,6 +248,23 @@ public class ConnectorBundleLoaderFactory extends Generator {
return;
}
+ List<CValUiInfo> cvalInfos = null;
+ try {
+ if (cvalChecker != null) {
+ cvalInfos = cvalChecker.run();
+ // Don't run twice
+ cvalChecker = null;
+ }
+ } catch (InvalidCvalException e) {
+ System.err.println("\n\n\n\n" + CvalChecker.LINE);
+ for (String line : e.getMessage().split("\n")) {
+ System.err.println(line);
+ }
+ System.err.println(CvalChecker.LINE + "\n\n\n\n");
+ System.exit(1);
+ throw new UnableToCompleteException();
+ }
+
List<ConnectorBundle> bundles = buildBundles(logger,
context.getTypeOracle());
@@ -364,6 +386,18 @@ public class ConnectorBundleLoaderFactory extends Generator {
w.println("});");
}
+ if (cvalInfos != null && !cvalInfos.isEmpty()) {
+ w.println("{");
+ for (CValUiInfo c : cvalInfos) {
+ if ("evaluation".equals(c.type)) {
+ w.println("cvals.add(new CValUiInfo(\"" + c.product
+ + "\", \"" + c.version + "\", \"" + c.widgetset
+ + "\", null));");
+ }
+ }
+ w.println("}");
+ }
+
w.outdent();
w.println("}");
@@ -1101,7 +1135,7 @@ public class ConnectorBundleLoaderFactory extends Generator {
* {@link ServerConnector} that have a @{@link Connect} annotation. It also
* checks that multiple connectors aren't connected to the same server-side
* class.
- *
+ *
* @param logger
* the logger to which information can be logged
* @param typeOracle
diff --git a/client-compiler/src/com/vaadin/tools/CvalAddonsChecker.java b/client-compiler/src/com/vaadin/tools/CvalAddonsChecker.java
new file mode 100644
index 0000000000..6780b1bc75
--- /dev/null
+++ b/client-compiler/src/com/vaadin/tools/CvalAddonsChecker.java
@@ -0,0 +1,191 @@
+/*
+ * 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 com.vaadin.tools.CvalChecker.LINE;
+import static com.vaadin.tools.CvalChecker.computeMajorVersion;
+import static com.vaadin.tools.CvalChecker.getErrorMessage;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import com.vaadin.client.metadata.ConnectorBundleLoader.CValUiInfo;
+import com.vaadin.tools.CvalChecker.CvalInfo;
+import com.vaadin.tools.CvalChecker.CvalServer;
+import com.vaadin.tools.CvalChecker.InvalidCvalException;
+import com.vaadin.tools.CvalChecker.UnreachableCvalServerException;
+
+/**
+ * This class is able to visit all MANIFEST.MF files present in the classpath,
+ * filter by name, and check if the user has a valid license.
+ *
+ * Manifest files should have a few attributes indicating the license type of
+ * the addon:
+ * <ul>
+ * <li>Implementation-Version: 4.x.x
+ * <li>AdVaaName: addon_name
+ * <li>AdVaaLicen: cval, agpl, empty
+ * <li>AdVaaPkg: package of the widgets in this addon
+ * </ul>
+ *
+ * The class also have a method to check just one product.
+ */
+public final class CvalAddonsChecker {
+
+ // Manifest attributes
+ public static final String VAADIN_ADDON_LICENSE = "AdVaaLicen";
+ public static final String VAADIN_ADDON_NAME = "AdVaaName";
+ public static final String VAADIN_ADDON_WIDGETSET = "Vaadin-Widgetsets";
+ public static final String VAADIN_ADDON_VERSION = "Implementation-Version";
+ public static final String VAADIN_ADDON_TITLE = "Implementation-Title";
+
+ // License types
+ public static final String VAADIN_AGPL = "agpl";
+ public static final String VAADIN_CVAL = "cval";
+
+ private CvalChecker cvalChecker = new CvalChecker();
+ private String filterPattern;
+
+ /**
+ * The constructor.
+ */
+ public CvalAddonsChecker() {
+ setLicenseProvider(new CvalServer());
+ setFilter(".*vaadin.*");
+ }
+
+ /**
+ * Visit all MANIFEST.MF files in the classpath validating licenses.
+ *
+ * Return a list of Cval licensed products in order to have enough info to
+ * generate nag messages in the UI.
+ */
+ public List<CValUiInfo> run() throws InvalidCvalException {
+ List<CValUiInfo> ret = new ArrayList<CValUiInfo>();
+ try {
+ // Visit all MANIFEST in our classpath
+ Enumeration<URL> manifests = Thread.currentThread()
+ .getContextClassLoader()
+ .getResources(JarFile.MANIFEST_NAME);
+ while (manifests.hasMoreElements()) {
+ try {
+ URL url = manifests.nextElement();
+ // Discard manifests whose name does not match the filter
+ // pattern
+ if (!url.getPath().matches(filterPattern)) {
+ continue;
+ }
+ InputStream is = url.openStream();
+ // Should never happen, but we don't want a NPE here
+ if (is == null) {
+ continue;
+ }
+ // Read manifest attributes
+ Manifest manifest = new Manifest(is);
+ Attributes attribs = manifest.getMainAttributes();
+ String license = attribs.getValue(VAADIN_ADDON_LICENSE);
+ String name = attribs.getValue(VAADIN_ADDON_NAME);
+ String vers = attribs.getValue(VAADIN_ADDON_VERSION) == null ? ""
+ : attribs.getValue(VAADIN_ADDON_VERSION);
+ String title = attribs.getValue(VAADIN_ADDON_TITLE) == null ? name
+ : attribs.getValue(VAADIN_ADDON_TITLE);
+
+ String widgetsets = attribs
+ .getValue(VAADIN_ADDON_WIDGETSET) == null ? name
+ : attribs.getValue(VAADIN_ADDON_WIDGETSET);
+
+ if (name == null || license == null) {
+ continue;
+ }
+ if (VAADIN_AGPL.equals(license)) {
+ // For agpl version we print an info message
+ printAgplLicense(title, vers);
+ } else if (VAADIN_CVAL.equals(license)) {
+ // We only check cval licensed products
+ CvalInfo info;
+ try {
+ info = cvalChecker.validateProduct(name, vers,
+ title);
+ printValidLicense(info, title, vers);
+ } catch (UnreachableCvalServerException e) {
+ info = CvalChecker.parseJson("{'product':{'name':'"
+ + name + "'}}");
+ printServerUnreachable(title, vers);
+ }
+ for (String w : widgetsets.split("[, ]+")) {
+ ret.add(new CValUiInfo(title, String
+ .valueOf(computeMajorVersion(vers)), w,
+ info.getType()));
+ }
+ }
+ } catch (IOException ignored) {
+ }
+ }
+ } catch (IOException ignored) {
+ }
+ return ret;
+ }
+
+ /**
+ * Set the filter regexp of .jar names which we have to consider.
+ *
+ * default is '.*touchkit.*'
+ */
+ public CvalAddonsChecker setFilter(String regexp) {
+ filterPattern = regexp;
+ return this;
+ }
+
+ /*
+ * Change the license provider, only used in tests.
+ */
+ protected CvalAddonsChecker setLicenseProvider(CvalServer p) {
+ cvalChecker.setLicenseProvider(p);
+ return this;
+ }
+
+ private void printAgplLicense(String name, String version) {
+ System.out.println(LINE + "\n"
+ + getErrorMessage("agpl", name, computeMajorVersion(version))
+ + "\n" + LINE);
+ }
+
+ private void printServerUnreachable(String name, String version) {
+ System.out.println(LINE
+ + "\n"
+ + getErrorMessage("unreachable", name,
+ computeMajorVersion(version)) + "\n" + LINE);
+ }
+
+ private void printValidLicense(CvalInfo info, String title, String version) {
+ String msg = info.getMessage();
+ if (msg == null) {
+ String key = "evaluation".equals(info.getType()) ? "evaluation"
+ : "valid";
+ msg = getErrorMessage(key, title, computeMajorVersion(version),
+ info.getLicensee());
+ }
+ System.out.println("\n" + LINE + "\n" + msg + "\n" + LINE + "\n");
+ }
+}
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);
+ }
+}
diff --git a/client-compiler/src/com/vaadin/tools/CvalChecker.properties b/client-compiler/src/com/vaadin/tools/CvalChecker.properties
new file mode 100644
index 0000000000..3f4fd52cb7
--- /dev/null
+++ b/client-compiler/src/com/vaadin/tools/CvalChecker.properties
@@ -0,0 +1,13 @@
+expired={2} for {0} {1} has expired. Get a valid license at vaadin.com/pro
+
+none=License for {0} {1} not found. Go to vaadin.com/pro for more details.
+
+invalid=License for {0} {1} is not valid. Get a valid license from vaadin.com/pro
+
+unreachable=License for {0} {1} has not been validated. Check your network connection.
+
+evaluation= > Using an evaluation license for {0} {1}.
+
+valid= > Using a valid license for {0} {1}.
+
+agpl=Using AGPL version of {0} {1}. Commercial licensing options available at vaadin.com/pro \ No newline at end of file
diff --git a/client-compiler/tests/src/com/vaadin/tools/CvalAddonsCheckerTest.java b/client-compiler/tests/src/com/vaadin/tools/CvalAddonsCheckerTest.java
new file mode 100644
index 0000000000..1fb9413ee4
--- /dev/null
+++ b/client-compiler/tests/src/com/vaadin/tools/CvalAddonsCheckerTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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 com.vaadin.tools.CvalAddonsChecker.VAADIN_AGPL;
+import static com.vaadin.tools.CvalAddonsChecker.VAADIN_CVAL;
+import static com.vaadin.tools.CvalChecker.GRACE_DAYS_MSECS;
+import static com.vaadin.tools.CvalChecker.computeLicenseName;
+import static com.vaadin.tools.CvalChecker.deleteCache;
+import static com.vaadin.tools.CvalCheckerTest.VALID_KEY;
+import static com.vaadin.tools.CvalCheckerTest.addLicensedJarToClasspath;
+import static com.vaadin.tools.CvalCheckerTest.cacheExists;
+import static com.vaadin.tools.CvalCheckerTest.captureSystemOut;
+import static com.vaadin.tools.CvalCheckerTest.productNameAgpl;
+import static com.vaadin.tools.CvalCheckerTest.productNameApache;
+import static com.vaadin.tools.CvalCheckerTest.productNameCval;
+import static com.vaadin.tools.CvalCheckerTest.readSystemOut;
+import static com.vaadin.tools.CvalCheckerTest.saveCache;
+import static com.vaadin.tools.CvalCheckerTest.unreachableLicenseProvider;
+import static com.vaadin.tools.CvalCheckerTest.validLicenseProvider;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.client.metadata.ConnectorBundleLoader.CValUiInfo;
+import com.vaadin.tools.CvalChecker.InvalidCvalException;
+
+/**
+ * The CvalAddonsChecker test.
+ */
+public class CvalAddonsCheckerTest {
+
+ CvalAddonsChecker addonChecker;
+ private String licenseName;
+
+ @Before
+ public void setup() {
+ addonChecker = new CvalAddonsChecker().setLicenseProvider(
+ validLicenseProvider).setFilter(".*test.*");
+ licenseName = computeLicenseName(productNameCval);
+
+ deleteCache(productNameCval);
+ System.getProperties().remove(licenseName);
+ }
+
+ @Test
+ public void testRunChecker() throws Exception {
+ // Create a product .jar with a cval license non required and add to our
+ // classpath
+ addLicensedJarToClasspath(productNameCval, VAADIN_CVAL);
+ // Remove other products in case other tests added them previously
+ addLicensedJarToClasspath(productNameAgpl, null);
+ addLicensedJarToClasspath(productNameApache, null);
+
+ // No license
+ // -> Break compilation
+ System.getProperties().remove(licenseName);
+ addonChecker.setLicenseProvider(validLicenseProvider);
+ try {
+ addonChecker.run();
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ }
+ Assert.assertFalse(cacheExists(productNameCval));
+
+ // We have a license that has never been validated from the server and
+ // we are offline
+ // -> Show a message on compile time (“Your license for TouchKit 4 has
+ // not been validated.”)
+ System.setProperty(licenseName, VALID_KEY);
+ addonChecker.setLicenseProvider(unreachableLicenseProvider);
+ captureSystemOut();
+ addonChecker.run();
+ Assert.assertTrue(readSystemOut().contains("has not been validated"));
+ Assert.assertFalse(cacheExists(productNameCval));
+
+ // Valid license has previously been validated from the server and we
+ // are offline
+ // -> Use the cached server response
+ System.setProperty(licenseName, VALID_KEY);
+ addonChecker.setLicenseProvider(validLicenseProvider);
+ captureSystemOut();
+ addonChecker.run();
+ Assert.assertTrue(cacheExists(productNameCval));
+ addonChecker.setLicenseProvider(unreachableLicenseProvider);
+ addonChecker.run();
+
+ // Expired license and we are offline
+ // -> If it has expired less than 14 days ago, just work with no nag
+ // messages
+ System.setProperty(licenseName, VALID_KEY);
+ addonChecker.setLicenseProvider(unreachableLicenseProvider);
+ setCacheFileTs(System.currentTimeMillis() - (GRACE_DAYS_MSECS / 2),
+ "normal");
+ captureSystemOut();
+ addonChecker.run();
+
+ // Expired license and we are offline
+ // -> After 14 days, interpret it as expired license
+ setCacheFileTs(System.currentTimeMillis() - (GRACE_DAYS_MSECS * 2),
+ "normal");
+ try {
+ addonChecker.run();
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ }
+
+ // Invalid evaluation license
+ // -> Fail compilation with a message
+ // "Your evaluation license for TouchKit 4 is not valid"
+ System.setProperty(licenseName, VALID_KEY);
+ addonChecker.setLicenseProvider(unreachableLicenseProvider);
+ setCacheFileTs(System.currentTimeMillis() - (GRACE_DAYS_MSECS / 2),
+ "evaluation");
+ try {
+ addonChecker.run();
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ Assert.assertTrue(expected.getMessage().contains("expired"));
+ }
+
+ // Valid evaluation license
+ // -> The choice on whether to show the message is generated in
+ // widgetset
+ // compilation phase. No license checks are done in application runtime.
+ System.setProperty(licenseName, VALID_KEY);
+ addonChecker.setLicenseProvider(unreachableLicenseProvider);
+ setCacheFileTs(System.currentTimeMillis() + GRACE_DAYS_MSECS,
+ "evaluation");
+ List<CValUiInfo> uiInfo = addonChecker.run();
+ Assert.assertEquals(1, uiInfo.size());
+ Assert.assertEquals("Test " + productNameCval, uiInfo.get(0).product);
+ Assert.assertEquals("evaluation", uiInfo.get(0).type);
+
+ // Valid real license
+ // -> Work as expected
+ // -> Show info message “Using TouchKit 4 license
+ // 312-312321-321312-3-12-312-312
+ // licensed to <licensee> (1 developer license)”
+ System.setProperty(licenseName, VALID_KEY);
+ addonChecker.setLicenseProvider(validLicenseProvider);
+ captureSystemOut();
+ addonChecker.run();
+ Assert.assertTrue(readSystemOut().contains("valid"));
+ }
+
+ @Test
+ public void validateMultipleLicenses() throws Exception {
+ addLicensedJarToClasspath(productNameCval, VAADIN_CVAL);
+ addLicensedJarToClasspath(productNameAgpl, VAADIN_AGPL);
+ addLicensedJarToClasspath(productNameApache, "apache");
+
+ // We have a valid license for all products
+ System.setProperty(licenseName, VALID_KEY);
+ captureSystemOut();
+ addonChecker.run();
+ String out = readSystemOut();
+ Assert.assertTrue(out.contains("valid"));
+ Assert.assertTrue(out.contains("AGPL"));
+ Assert.assertTrue(cacheExists(productNameCval));
+ }
+
+ private void setCacheFileTs(long expireTs, String type) {
+ saveCache(productNameCval, null, false, expireTs, type);
+ }
+
+}
diff --git a/client-compiler/tests/src/com/vaadin/tools/CvalAddonstCheckerUseCasesTest.java b/client-compiler/tests/src/com/vaadin/tools/CvalAddonstCheckerUseCasesTest.java
new file mode 100644
index 0000000000..89c8fc1f81
--- /dev/null
+++ b/client-compiler/tests/src/com/vaadin/tools/CvalAddonstCheckerUseCasesTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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 com.vaadin.tools.CvalAddonsChecker.VAADIN_AGPL;
+import static com.vaadin.tools.CvalAddonsChecker.VAADIN_CVAL;
+import static com.vaadin.tools.CvalChecker.computeLicenseName;
+import static com.vaadin.tools.CvalChecker.deleteCache;
+import static com.vaadin.tools.CvalCheckerTest.INVALID_KEY;
+import static com.vaadin.tools.CvalCheckerTest.VALID_KEY;
+import static com.vaadin.tools.CvalCheckerTest.addLicensedJarToClasspath;
+import static com.vaadin.tools.CvalCheckerTest.cachedPreferences;
+import static com.vaadin.tools.CvalCheckerTest.captureSystemOut;
+import static com.vaadin.tools.CvalCheckerTest.expiredLicenseProvider;
+import static com.vaadin.tools.CvalCheckerTest.productNameAgpl;
+import static com.vaadin.tools.CvalCheckerTest.productNameCval;
+import static com.vaadin.tools.CvalCheckerTest.readSystemOut;
+import static com.vaadin.tools.CvalCheckerTest.restoreSystemOut;
+import static com.vaadin.tools.CvalCheckerTest.saveCache;
+import static com.vaadin.tools.CvalCheckerTest.unreachableLicenseProvider;
+import static com.vaadin.tools.CvalCheckerTest.validEvaluationLicenseProvider;
+import static com.vaadin.tools.CvalCheckerTest.validLicenseProvider;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.tools.CvalChecker.CvalServer;
+
+/**
+ * Tests for Use Cases
+ */
+public class CvalAddonstCheckerUseCasesTest {
+
+ enum License {
+ NONE, EVAL, INVALID, REAL, EVAL_EXPIRED, REAL_EXPIRED
+ };
+
+ enum Version {
+ AGPL, CVAL
+ };
+
+ enum Validated {
+ YES, NO, OLD_KEY
+ };
+
+ enum Network {
+ ON, OFF
+ };
+
+ enum Compile {
+ YES, NO
+ };
+
+ enum Cached {
+ YES, NO
+ };
+
+ enum Message {
+ AGPL("AGPL"), VALID(">.* valid"), INVALID("not valid"), NO_LICENSE(
+ "not found"), NO_VALIDATED("has not been validated"), EXPIRED(
+ "has expired"), EVALUATION("evaluation");
+
+ String msg;
+
+ Message(String s) {
+ msg = s;
+ }
+ }
+
+ @Test
+ public void testUseCases() throws Exception {
+ useCase(1, License.NONE, Version.AGPL, Validated.NO, Network.OFF,
+ Compile.YES, Cached.NO, Message.AGPL);
+
+ useCase(2, License.NONE, Version.CVAL, Validated.NO, Network.ON,
+ Compile.NO, Cached.NO, Message.NO_LICENSE);
+
+ useCase(3, License.NONE, Version.CVAL, Validated.NO, Network.OFF,
+ Compile.NO, Cached.NO, Message.NO_LICENSE);
+
+ useCase(4, License.EVAL, Version.CVAL, Validated.NO, Network.ON,
+ Compile.YES, Cached.YES, Message.EVALUATION);
+
+ useCase(5, License.INVALID, Version.CVAL, Validated.NO, Network.OFF,
+ Compile.YES, Cached.NO, Message.NO_VALIDATED);
+
+ useCase(6, License.INVALID, Version.CVAL, Validated.NO, Network.ON,
+ Compile.NO, Cached.NO, Message.INVALID);
+
+ useCase(7, License.REAL, Version.CVAL, Validated.NO, Network.ON,
+ Compile.YES, Cached.YES, Message.VALID);
+
+ useCase(8, License.REAL, Version.CVAL, Validated.NO, Network.OFF,
+ Compile.YES, Cached.NO, Message.NO_VALIDATED);
+
+ useCase(9, License.REAL, Version.CVAL, Validated.YES, Network.OFF,
+ Compile.YES, Cached.YES, Message.VALID);
+
+ useCase(10, License.EVAL_EXPIRED, Version.CVAL, Validated.NO,
+ Network.ON, Compile.NO, Cached.YES, Message.EXPIRED);
+
+ useCase(11, License.EVAL_EXPIRED, Version.CVAL, Validated.YES,
+ Network.OFF, Compile.NO, Cached.YES, Message.EXPIRED);
+
+ useCase(12, License.REAL_EXPIRED, Version.CVAL, Validated.YES,
+ Network.OFF, Compile.NO, Cached.YES, Message.EXPIRED);
+
+ useCase(13, License.REAL_EXPIRED, Version.CVAL, Validated.NO,
+ Network.ON, Compile.NO, Cached.YES, Message.EXPIRED);
+
+ useCase(14, License.INVALID, Version.CVAL, Validated.OLD_KEY,
+ Network.OFF, Compile.YES, Cached.NO, Message.NO_VALIDATED);
+ }
+
+ @Test
+ public void testMultipleLicenseUseCases() throws Exception {
+ addLicensedJarToClasspath("test.foo", VAADIN_CVAL);
+ System.setProperty(computeLicenseName("test.foo"), VALID_KEY);
+
+ useCase(15, License.REAL, Version.CVAL, Validated.YES, Network.OFF,
+ Compile.YES, Cached.YES, Message.NO_VALIDATED);
+
+ useCase(16, License.REAL, Version.CVAL, Validated.YES, Network.ON,
+ Compile.NO, Cached.YES, Message.INVALID);
+ }
+
+ private void useCase(int number, License lic, Version ver, Validated val,
+ Network net, Compile res, Cached cached, Message msg)
+ throws Exception {
+
+ if (ver == Version.AGPL) {
+ addLicensedJarToClasspath(productNameAgpl, VAADIN_AGPL);
+ addLicensedJarToClasspath(productNameCval, null);
+ } else {
+ addLicensedJarToClasspath(productNameAgpl, null);
+ addLicensedJarToClasspath(productNameCval, VAADIN_CVAL);
+ }
+
+ String licenseName = computeLicenseName(productNameCval);
+
+ if (lic == License.NONE) {
+ System.getProperties().remove(licenseName);
+ } else if (lic == License.INVALID) {
+ System.setProperty(licenseName, INVALID_KEY);
+ } else {
+ System.setProperty(licenseName, VALID_KEY);
+ }
+
+ if (val == Validated.NO) {
+ deleteCache(productNameCval);
+ } else {
+ String type = lic == License.EVAL || lic == License.EVAL_EXPIRED ? "evaluation"
+ : null;
+ Boolean expired = lic == License.EVAL_EXPIRED
+ || lic == License.REAL_EXPIRED ? true : null;
+ String key = val == Validated.OLD_KEY ? "oldkey" : null;
+ saveCache(productNameCval, key, expired, null, type);
+ }
+
+ CvalServer licenseProvider = validLicenseProvider;
+ if (net == Network.OFF) {
+ licenseProvider = unreachableLicenseProvider;
+ } else if (lic == License.EVAL_EXPIRED || lic == License.REAL_EXPIRED) {
+ licenseProvider = expiredLicenseProvider;
+ } else if (lic == License.EVAL) {
+ licenseProvider = validEvaluationLicenseProvider;
+ }
+
+ CvalAddonsChecker addonChecker = new CvalAddonsChecker();
+ addonChecker.setLicenseProvider(licenseProvider).setFilter(".*test.*");
+
+ captureSystemOut();
+
+ String testNumber = "Test #" + number + " ";
+ String message;
+ try {
+ addonChecker.run();
+ message = readSystemOut();
+ if (res == Compile.NO) {
+ Assert.fail(testNumber + "Exception not thrown:" + message);
+ }
+ } catch (Exception e) {
+ restoreSystemOut();
+ message = e.getMessage();
+ if (res == Compile.YES) {
+ Assert.fail(testNumber + "Unexpected Exception: "
+ + e.getMessage());
+ }
+ }
+
+ // System.err.println("\n> " + testNumber + " " + lic + " " + ver + " "
+ // + val + " " + net + " " + res + " " + cached + "\n" + message);
+
+ Assert.assertTrue(testNumber + "Fail:\n" + message
+ + "\nDoes not match:" + msg.msg,
+ message.matches("(?s).*" + msg.msg + ".*"));
+
+ String c = cachedPreferences(productNameCval);
+ Assert.assertTrue(testNumber + "Fail: cacheExists != "
+ + (cached == Cached.YES) + "\n " + c,
+ (c != null) == (cached == Cached.YES));
+ }
+}
diff --git a/client-compiler/tests/src/com/vaadin/tools/CvalCheckerTest.java b/client-compiler/tests/src/com/vaadin/tools/CvalCheckerTest.java
new file mode 100644
index 0000000000..51b12f4c7e
--- /dev/null
+++ b/client-compiler/tests/src/com/vaadin/tools/CvalCheckerTest.java
@@ -0,0 +1,342 @@
+/*
+ * 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 com.vaadin.tools.CvalAddonsChecker.VAADIN_ADDON_LICENSE;
+import static com.vaadin.tools.CvalAddonsChecker.VAADIN_ADDON_NAME;
+import static com.vaadin.tools.CvalAddonsChecker.VAADIN_ADDON_TITLE;
+import static com.vaadin.tools.CvalAddonsChecker.VAADIN_ADDON_VERSION;
+import static com.vaadin.tools.CvalChecker.GRACE_DAYS_MSECS;
+import static com.vaadin.tools.CvalChecker.cacheLicenseInfo;
+import static com.vaadin.tools.CvalChecker.deleteCache;
+import static com.vaadin.tools.CvalChecker.parseJson;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.prefs.Preferences;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.tools.CvalChecker.CvalInfo;
+import com.vaadin.tools.CvalChecker.CvalServer;
+import com.vaadin.tools.CvalChecker.InvalidCvalException;
+import com.vaadin.tools.CvalChecker.UnreachableCvalServerException;
+
+/**
+ * The CvalChecker test.
+ */
+public class CvalCheckerTest {
+
+ static final String productNameCval = "test.cval";
+ static final String productTitleCval = "Vaadin Test";
+ static final String productNameAgpl = "test.agpl";
+ static final String productTitleAgpl = "Vaadin Test";
+ static final String productNameApache = "test.apache";
+ static final String VALID_KEY = "valid";
+ static final String INVALID_KEY = "invalid";
+
+ static final String responseJson = "{'licenseKey':'" + VALID_KEY + "',"
+ + "'licensee':'Test User','type':'normal',"
+ + "'expiredEpoch':'1893511225000'," + "'product':{'name':'"
+ + productNameCval + "', 'version': 2}}";
+
+ private static ByteArrayOutputStream outContent;
+
+ // A provider returning a valid license if productKey is valid or null if
+ // invalid
+ static final CvalServer validLicenseProvider = new CvalServer() {
+ @Override
+ String askServer(String productName, String productKey, int timeout) {
+ return VALID_KEY.equals(productKey) ? responseJson : null;
+ }
+ };
+ // A provider returning a valid evaluation license
+ static final CvalServer validEvaluationLicenseProvider = new CvalServer() {
+ @Override
+ String askServer(String productName, String productKey, int timeout) {
+ return responseJson.replace("normal", "evaluation");
+ }
+ };
+ // A provider returning an expired license with a server message
+ static final CvalServer expiredLicenseProviderWithMessage = new CvalServer() {
+ @Override
+ String askServer(String productName, String productKey, int timeout) {
+ return responseJson
+ .replace("'expired",
+ "'message':'Custom\\\\nServer\\\\nMessage','expired':true,'expired");
+ }
+ };
+ // A provider returning an expired license with a server message
+ static final CvalServer expiredLicenseProvider = new CvalServer() {
+ @Override
+ String askServer(String productName, String productKey, int timeout) {
+ return responseJson.replace("'expired", "'expired':true,'expired");
+ }
+ };
+ // A provider returning an expired epoch license
+ static final CvalServer expiredEpochLicenseProvider = new CvalServer() {
+ @Override
+ String askServer(String productName, String productKey, int timeout) {
+ long ts = System.currentTimeMillis() - GRACE_DAYS_MSECS - 1000;
+ return responseJson.replace("1893511225000", "" + ts);
+ }
+ };
+ // A provider returning an unlimited license
+ static final CvalServer unlimitedLicenseProvider = new CvalServer() {
+ @Override
+ String askServer(String productName, String productKey, int timeout) {
+ return responseJson.replaceFirst("1893511225000", "");
+ }
+ };
+ // An unreachable provider
+ static final CvalServer unreachableLicenseProvider = new CvalServer() {
+ @Override
+ String askServer(String productName, String productKey, int timeout)
+ throws IOException {
+ // Normally there is no route for this ip in public routers, so we
+ // should get a timeout.
+ licenseUrl = "http://localhost:9999/";
+ return super.askServer(productName, productKey, 1000);
+ }
+ };
+
+ private CvalChecker licenseChecker;
+ private String licenseName;
+
+ @Before
+ public void setup() {
+ licenseChecker = new CvalChecker()
+ .setLicenseProvider(validLicenseProvider);
+ licenseName = CvalChecker.computeLicenseName(productNameCval);
+ System.getProperties().remove(licenseName);
+ deleteCache(productNameCval);
+ }
+
+ @Test
+ public void testValidateProduct() throws Exception {
+ deleteCache(productNameCval);
+
+ // If the license key in our environment is null, throw an exception
+ try {
+ licenseChecker.validateProduct(productNameCval, "2.1",
+ productTitleCval);
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ Assert.assertEquals(productNameCval, expected.name);
+ }
+ Assert.assertFalse(cacheExists(productNameCval));
+
+ // If the license key is empty, throw an exception
+ System.setProperty(licenseName, "");
+ try {
+ licenseChecker.validateProduct(productNameCval, "2.1",
+ productTitleCval);
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ Assert.assertEquals(productNameCval, expected.name);
+ }
+ Assert.assertFalse(cacheExists(productNameCval));
+
+ // If license key is invalid, throw an exception
+ System.setProperty(licenseName, "invalid");
+ try {
+ licenseChecker.validateProduct(productNameCval, "2.1",
+ productTitleCval);
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ Assert.assertEquals(productNameCval, expected.name);
+ }
+ Assert.assertFalse(cacheExists(productNameCval));
+
+ // Fail if version is bigger
+ System.setProperty(licenseName, VALID_KEY);
+ try {
+ licenseChecker.validateProduct(productNameCval, "3.0",
+ productTitleCval);
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ Assert.assertEquals(productNameCval, expected.name);
+ }
+ Assert.assertFalse(cacheExists(productNameCval));
+
+ // Success if license key and version are valid
+ System.setProperty(licenseName, VALID_KEY);
+ licenseChecker
+ .validateProduct(productNameCval, "2.1", productTitleCval);
+ Assert.assertTrue(cacheExists(productNameCval));
+
+ // Success if license and cache file are valid, although the license
+ // server is offline
+ licenseChecker.setLicenseProvider(unreachableLicenseProvider);
+ licenseChecker
+ .validateProduct(productNameCval, "2.1", productTitleCval);
+ Assert.assertTrue(cacheExists(productNameCval));
+
+ // Fail if license key changes although cache file were validated
+ // previously and it is ok, we are offline
+ try {
+ System.setProperty(licenseName, INVALID_KEY);
+ licenseChecker.validateProduct(productNameCval, "2.1",
+ productTitleCval);
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ Assert.fail();
+ } catch (UnreachableCvalServerException expected) {
+ Assert.assertEquals(productNameCval, expected.name);
+ }
+ Assert.assertFalse(cacheExists(productNameCval));
+
+ // Fail with unreachable exception if license has never verified and
+ // server is offline
+ try {
+ System.setProperty(licenseName, VALID_KEY);
+ licenseChecker.validateProduct(productNameCval, "2.1",
+ productTitleCval);
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ Assert.fail();
+ } catch (UnreachableCvalServerException expected) {
+ Assert.assertEquals(productNameCval, expected.name);
+ }
+ Assert.assertFalse(cacheExists(productNameCval));
+
+ // Fail when expired flag comes in the server response, although the
+ // expired is valid.
+ deleteCache(productNameCval);
+ licenseChecker.setLicenseProvider(expiredLicenseProviderWithMessage);
+ try {
+ licenseChecker.validateProduct(productNameCval, "2.1",
+ productTitleCval);
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ Assert.assertEquals(productNameCval, expected.name);
+ // Check that we use server customized message if it comes
+ Assert.assertTrue(expected.getMessage().contains("Custom"));
+ }
+ Assert.assertTrue(cacheExists(productNameCval));
+
+ // Check an unlimited license
+ licenseChecker.setLicenseProvider(unlimitedLicenseProvider);
+ licenseChecker
+ .validateProduct(productNameCval, "2.1", productTitleCval);
+ Assert.assertTrue(cacheExists(productNameCval));
+
+ // Fail if expired flag does not come, but expired epoch is in the past
+ System.setProperty(licenseName, VALID_KEY);
+ deleteCache(productNameCval);
+ licenseChecker.setLicenseProvider(expiredEpochLicenseProvider);
+ try {
+ licenseChecker.validateProduct(productNameCval, "2.1",
+ productTitleCval);
+ Assert.fail();
+ } catch (InvalidCvalException expected) {
+ Assert.assertEquals(productNameCval, expected.name);
+ }
+ Assert.assertTrue(cacheExists(productNameCval));
+ }
+
+ /*
+ * Creates a new .jar file with a MANIFEST.MF with all vaadin license info
+ * attributes set, and add the .jar to the classpath
+ */
+ static void addLicensedJarToClasspath(String productName, String licenseType)
+ throws Exception {
+ // Create a manifest with Vaadin CVAL license
+ Manifest testManifest = new Manifest();
+ testManifest.getMainAttributes().putValue("Manifest-Version", "1.0");
+ testManifest.getMainAttributes().putValue(VAADIN_ADDON_LICENSE,
+ licenseType);
+ testManifest.getMainAttributes().putValue(VAADIN_ADDON_NAME,
+ productName);
+ testManifest.getMainAttributes().putValue(VAADIN_ADDON_TITLE,
+ "Test " + productName);
+ testManifest.getMainAttributes().putValue(VAADIN_ADDON_VERSION, "2");
+
+ // Create a temporary Jar
+ String tmpDir = System.getProperty("java.io.tmpdir");
+ File testJarFile = new File(tmpDir + "vaadin." + productName + ".jar");
+ testJarFile.deleteOnExit();
+ JarOutputStream target = new JarOutputStream(new FileOutputStream(
+ testJarFile), testManifest);
+ target.close();
+
+ // Add the new jar to our classpath (use reflection)
+ URL url = new URL("file://" + testJarFile.getAbsolutePath());
+ final Method addURL = URLClassLoader.class.getDeclaredMethod("addURL",
+ new Class[] { URL.class });
+ addURL.setAccessible(true);
+ final URLClassLoader urlClassLoader = (URLClassLoader) Thread
+ .currentThread().getContextClassLoader();
+ addURL.invoke(urlClassLoader, new Object[] { url });
+ }
+
+ static boolean cacheExists(String productName) {
+ return cachedPreferences(productName) != null;
+ }
+
+ static String cachedPreferences(String productName) {
+ // ~/Library/Preferences/com.apple.java.util.prefs.plist
+ // .java/.userPrefs/com/google/gwt/dev/shell/prefs.xml
+ // HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs
+ Preferences p = Preferences.userNodeForPackage(CvalInfo.class);
+ return p.get(productName, null);
+ }
+
+ static void saveCache(String productName, String key, Boolean expired,
+ Long expireTs, String type) {
+ String json = responseJson.replace(productNameCval, productName);
+ if (expired != null && expired) {
+ expireTs = System.currentTimeMillis() - GRACE_DAYS_MSECS - 1000;
+ }
+ if (expireTs != null) {
+ json = json.replace("1893511225000", "" + expireTs);
+ }
+ if (key != null) {
+ json = json.replace(VALID_KEY, key);
+ }
+ if (type != null) {
+ json = json.replace("normal", type);
+
+ }
+ cacheLicenseInfo(parseJson(json));
+ }
+
+ static void captureSystemOut() {
+ outContent = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(outContent));
+ }
+
+ static String readSystemOut() {
+ restoreSystemOut();
+ return outContent.toString();
+ }
+
+ static void restoreSystemOut() {
+ System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
+ }
+}
diff --git a/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java b/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java
index 7d2078061e..846bfd4671 100644
--- a/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java
+++ b/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,13 +15,35 @@
*/
package com.vaadin.client.metadata;
+import java.util.ArrayList;
import java.util.List;
import com.google.gwt.core.shared.GWT;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConfiguration;
import com.vaadin.client.FastStringMap;
import com.vaadin.client.metadata.AsyncBundleLoader.State;
+import com.vaadin.client.ui.VNotification;
+import com.vaadin.shared.Position;
public abstract class ConnectorBundleLoader {
+
+ public static class CValUiInfo {
+ public final String widgetset;
+ public final String product;
+ public final String version;
+ public final String type;
+
+ public CValUiInfo(String product, String version, String widgetset,
+ String type) {
+ this.product = product;
+ this.version = version;
+ this.widgetset = widgetset;
+ this.type = type;
+ }
+ }
+
public static final String EAGER_BUNDLE_NAME = "__eager";
public static final String DEFERRED_BUNDLE_NAME = "__deferred";
@@ -113,4 +135,27 @@ public abstract class ConnectorBundleLoader {
public abstract void init();
+ protected List<CValUiInfo> cvals = new ArrayList<CValUiInfo>();
+
+ public void cval(String typeName) {
+ if (!cvals.isEmpty()) {
+ String msg = "";
+ for (CValUiInfo c : cvals) {
+ String ns = c.widgetset.replaceFirst("\\.[^\\.]+$", "");
+ if (typeName.startsWith(ns)) {
+ cvals.remove(c);
+ msg += c.product + " " + c.version + "<br/>";
+ }
+ }
+ if (!msg.isEmpty()) {
+ // We need a widget for using VNotification, using the
+ // context-menu parent. Is there an easy way?
+ Widget w = ApplicationConfiguration.getRunningApplications()
+ .get(0).getContextMenu().getParent();
+ VNotification n = VNotification.createNotification(0, w);
+ n.setWidget(new HTML("Using Evaluation License of:<br/>" + msg));
+ n.show(Position.BOTTOM_RIGHT);
+ }
+ }
+ }
}
diff --git a/client/src/com/vaadin/client/metadata/Type.java b/client/src/com/vaadin/client/metadata/Type.java
index cc185dff96..15617ffe3c 100644
--- a/client/src/com/vaadin/client/metadata/Type.java
+++ b/client/src/com/vaadin/client/metadata/Type.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -19,6 +19,7 @@ import java.util.Collection;
import com.google.gwt.core.client.JsArrayString;
import com.vaadin.client.JsArrayObject;
+import com.vaadin.client.ServerConnector;
import com.vaadin.client.communication.JSONSerializer;
public class Type {
@@ -47,7 +48,11 @@ public class Type {
public Object createInstance() throws NoDataException {
Invoker invoker = TypeDataStore.getConstructor(this);
- return invoker.invoke(null);
+ Object ret = invoker.invoke(null);
+ if (ret instanceof ServerConnector) {
+ ConnectorBundleLoader.get().cval(name);
+ }
+ return ret;
}
public Method getMethod(String name) {
@@ -57,7 +62,7 @@ public class Type {
/**
* @return
* @throws NoDataException
- *
+ *
* @deprecated As of 7.0.1, use {@link #getPropertiesAsArray()} instead for
* improved performance
*/
@@ -96,7 +101,7 @@ public class Type {
* returned string may change without notice and should not be used for any
* other purpose than identification. The signature is currently based on
* the fully qualified name of the type.
- *
+ *
* @return the unique signature of this type
*/
public String getSignature() {