diff options
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() { |