summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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() {