/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.core.license;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import com.rapidminer.license.AlreadyRegisteredException;
import com.rapidminer.license.InvalidProductException;
import com.rapidminer.license.License;
import com.rapidminer.license.LicenseConstants;
import com.rapidminer.license.LicenseManager;
import com.rapidminer.license.LicenseManagerListener;
import com.rapidminer.license.LicenseManagerRegistry;
import com.rapidminer.license.LicenseStatus;
import com.rapidminer.license.LicenseValidationException;
import com.rapidminer.license.StudioLicenseConstants;
import com.rapidminer.license.UnknownProductException;
import com.rapidminer.license.location.FileLicenseLocation;
import com.rapidminer.license.location.LicenseLoadingException;
import com.rapidminer.license.location.LicenseLocation;
import com.rapidminer.license.location.LicenseStoringException;
import com.rapidminer.license.product.Constraint;
import com.rapidminer.license.product.DefaultProduct;
import com.rapidminer.license.product.NumericalConstraint;
import com.rapidminer.license.product.Product;
import com.rapidminer.license.utils.Pair;
import com.rapidminer.license.violation.LicenseViolation;
import com.rapidminer.operator.Operator;
import com.rapidminer.tools.FileSystemService;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
/**
* This class handles the interaction with the {@link LicenseManager} for RapidMiner. It installs
* the {@link Product} that will be used to retrieve licenses. Furthermore it has convenience
* methods to check RapidMiner constraints.
*
* @author Nils Woehler
*
*/
public enum ProductConstraintManager {
INSTANCE;
/**
* folder name for licenses
*/
private static final String LICENSES_FOLDER_NAME = "licenses";
/**
* the signature for the RapidMiner Studio product used for verification of our default product
*/
private static final String RAPIDMINER_STUDIO_PRODUCT_SIGNATURE = "Rmfgu6rDLgqPCIBl/WzEWmVW4O8cPHF2yPMQvTTAWZGDIwhMadeRmMK6e3V/VW+VOrdKKPHCHB3PtzNQAVGWHrKsv3tmKivQGNIQOSG8192araFXSGHpapQhWFf+8gjsDlf1Dbbt2ZRSf/Gmiinb2JcoT6x+NQiZfkXUFVeOEGyAJLUufKCAdvTu2bzkbexdfcJAvTSzqn2VwgFThg4zRzLxoO2hElT6DHWmr3pi2iLnzVgcM0ifJYdTYsTnAk0fhSijpVv3jMbL81ehUh8iJSQlXoutVcxYFAviMhlBlKb/3dgLhBlG8F12epF20WNSyewCRM8ysANZbzP9qcOf+w==";
private static final Product DEFAULT_PRODUCT = new DefaultProduct(StudioLicenseConstants.PRODUCT_ID,
StudioLicenseConstants.VERSION, false, RAPIDMINER_STUDIO_PRODUCT_SIGNATURE, LicenseConstants.DATA_ROW_CONSTRAINT,
LicenseConstants.LOGICAL_PROCESSOR_CONSTRAINT, LicenseConstants.MEMORY_LIMIT_CONSTRAINT,
LicenseConstants.WEB_SERVICE_LIMIT_CONSTRAINT);
private static final Path MAIN_LICENSE_PATH = Paths
.get(new File(FileSystemService.getUserRapidMinerDir(), LICENSES_FOLDER_NAME).toURI());
/** The product that is used to retrieve RM licenses */
private Product registeredProduct;
private NumericalConstraint logicalProcessorConstraint;
private NumericalConstraint dataRowConstraint;
private NumericalConstraint memoryLimitConstraint;
private NumericalConstraint webServiceLimitConstraint;
private AtomicBoolean initialized = new AtomicBoolean(false);
/**
*
* Initialized the {@link LicenseManager} with the provided {@link LicenseLocation}. Furthermore
* the provided product is registered. The provided product must contain all constraints defined
* in {@link RMConstraints}.
*/
public synchronized void initialize(LicenseLocation licenseLocation, Product product)
throws IllegalAccessException, AlreadyRegisteredException, LicenseLoadingException, InvalidProductException {
if (initialized.get()) {
throw new UnsupportedOperationException("Cannot initialize the ProductConstraintManager twice");
}
// Install open-source license manager in case no available
if (LicenseManagerRegistry.INSTANCE.get() == null) {
LogService.getRoot().info("com.rapidminer.license.ConstraintManager.using_open_source_license_manager");
LicenseManagerRegistry.INSTANCE.set(new OpenSourceLicenseManager());
}
LogService.getRoot().info("com.rapidminer.license.ConstraintManager.initializing_constraint_manager");
if (licenseLocation == null) {
LogService.getRoot().info("com.rapidminer.license.ConstraintManager.using_default_license_location");
Path installationLicenseFolder = null;
try {
installationLicenseFolder = FileSystemService.getRapidMinerHome().toPath().resolve(LICENSES_FOLDER_NAME);
} catch (IOException e) {
LogService.getRoot()
.info("com.rapidminer.license.ConstraintManager.cannot_use_installation_folder_licenses");
}
// Files.isDirectory follows symbolic links
if (installationLicenseFolder != null && Files.isDirectory(installationLicenseFolder)) {
licenseLocation = new FileLicenseLocation(MAIN_LICENSE_PATH, installationLicenseFolder);
LogService.getRoot().info("com.rapidminer.license.ConstraintManager.found_installation_folder_licenses");
} else {
licenseLocation = new FileLicenseLocation(MAIN_LICENSE_PATH);
}
}
LicenseManagerRegistry.INSTANCE.get().setLicenseLocation(licenseLocation);
if (product == null) {
product = DEFAULT_PRODUCT;
LogService.getRoot().info("com.rapidminer.license.ConstraintManager.using_default_product");
}
// make sure that extensions CANNOT be used to init the RM Studio product constraints
if (product.isExtension()) {
throw new InvalidProductException("Cannot init RapidMiner Studio with extension product!",
product.getProductId());
}
// make sure only core product ID is used
if (!product.getProductId().matches(Product.RM_REGEX)) {
throw new InvalidProductException(
"Cannot init RapidMiner Studio. Only core product IDs (matching " + Product.RM_REGEX + ") are allowed!",
product.getProductId());
}
// check if all default Studio constraint are defined for the product that should be
// installed
for (Constraint<?, ?> rmConstr : LicenseConstants.getDefaultConstraints()) {
// get constraint from product
String constraintId = rmConstr.getKey();
Constraint<?, ?> productConstraint = product.findConstraint(constraintId);
// check if product contains constraint
if (productConstraint == null) {
throw new RuntimeException(I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.license.ConstraintManager.no_constraint_defined", rmConstr.getKey()));
}
// also check if constraint is of correct type
if (!productConstraint.getClass().isAssignableFrom(rmConstr.getClass())) {
throw new RuntimeException("Constraint with constraintId " + rmConstr.getKey()
+ " is of wrong type. Expected class: " + rmConstr.getClass());
}
// remember constraints in class variables
switch (constraintId) {
case LicenseConstants.DATA_ROW_CONSTRAINT_ID:
dataRowConstraint = (NumericalConstraint) productConstraint;
break;
case LicenseConstants.LOGICAL_PROCESSORS_CONSTRAINT_ID:
logicalProcessorConstraint = (NumericalConstraint) productConstraint;
break;
case LicenseConstants.MEMORY_LIMIT_CONSTRAINT_ID:
memoryLimitConstraint = (NumericalConstraint) productConstraint;
break;
case LicenseConstants.WEB_SERVICE_LIMIT_CONSTRAINT_ID:
webServiceLimitConstraint = (NumericalConstraint) productConstraint;
break;
default:
throw new RuntimeException("Unknown constraint " + rmConstr.getKey());
}
}
// register product to license manager
LicenseManagerRegistry.INSTANCE.get().registerProduct(product);
// remember registered product
registeredProduct = product;
// set initialized
initialized.set(true);
}
/**
* @return <code>true</code> if the {@link ProductConstraintManager} has been initialized
*/
public boolean isInitialized() {
return initialized.get();
}
/**
* @return the currently registered {@link Product}
*/
public Product getProduct() {
return registeredProduct;
}
/**
* @return if a trial license is contained in the list of licenses <code>true</code> is
* returned.
*/
public boolean wasTrialActivated() {
List<License> licenses = LicenseManagerRegistry.INSTANCE.get().getLicenses(getProduct());
for (License lic : licenses) {
if (lic.getProductEdition().equals(StudioLicenseConstants.TRIAL_EDITION)) {
return true;
}
}
return false;
}
/**
* Checks if there ever was a license activated which is better than a Free edition. Examples
* are either a trial edition or any other paid edition.
*
* @return <code>true</code> if at least one trial or any other editions except basic was
* activated; <code>false</code> otherwise
*/
public boolean wasHigherThanFreeActivated() {
List<License> licenses = LicenseManagerRegistry.INSTANCE.get().getLicenses(getProduct());
for (License lic : licenses) {
if (!lic.getProductEdition().equals(LicenseConstants.STARTER_EDITION)) {
return true;
}
}
return false;
}
/**
* Checks if there ever was any license activated.
*
* @return <code>true</code> if at least one license was activated; <code>false</code> otherwise
*/
public boolean wasAnyLicenseActivated() {
return LicenseManagerRegistry.INSTANCE.get().getLicenses(getProduct()).size() > 0;
}
/**
* @return returns <code>true</code> if it reasonable to offer trial to the user, based on
* locally available information. This method does not ask the server whether trial is
* still available. It checks if a trial license was installed and if trial is better
* than the currently active license.
*/
public boolean shouldTrialBeOffered() {
return !wasTrialActivated() && getActiveLicense().getPrecedence() < StudioLicenseConstants.TRIAL_LICENSE_PRECEDENCE;
}
/**
* @return returns <code>true</code> if the current active license is a trial license
*/
public boolean isTrialLicense() {
return StudioLicenseConstants.TRIAL_EDITION
.equals(LicenseManagerRegistry.INSTANCE.get().getActiveLicense(getProduct()).getProductEdition());
}
/**
* @return the current active license for the {@link Product} installed to the
* {@link ProductConstraintManager}.
*/
public License getActiveLicense() {
return LicenseManagerRegistry.INSTANCE.get().getActiveLicense(getProduct());
}
/**
* Checks whether the provided license text is valid for the {@link Product} installed.
*
* @param licenseText
* the text of the license which should be validated
* @return <code>true</code> in case the license text is valid, <code>false</code> otherwise
*/
public boolean isLicenseValid(String licenseText) {
Pair<Product, License> validateLicense = null;
try {
validateLicense = validateLicense(licenseText);
} catch (LicenseValidationException | UnknownProductException e) {
return false;
}
LicenseStatus status = validateLicense.getSecond().getStatus();
return status == LicenseStatus.VALID || status == LicenseStatus.STARTS_IN_FUTURE;
}
/**
* Install a new license to the {@link LicenseManager}. Should only be called if
* {@link #isLicenseValid(String)} returns <code>true</code>.
*
* @param licenseText
* the text if the license that should be installed
*
* @return the freshly installed license.
*/
public License installNewLicense(String licenseText)
throws LicenseStoringException, UnknownProductException, LicenseValidationException {
return LicenseManagerRegistry.INSTANCE.get().storeNewLicense(licenseText);
}
/**
* @param l
* the license manager listener
*/
public void registerLicenseManagerListener(LicenseManagerListener l) {
LicenseManagerRegistry.INSTANCE.get().registerLicenseManagerListener(l);
}
/**
* @param l
* removes the provided license manager listener from the license manager
*/
public void removeLicenseManagerListener(LicenseManagerListener l) {
LicenseManagerRegistry.INSTANCE.get().removeLicenseManagerListener(l);
}
/**
* @return the upcoming license for the product installed to the
* {@link ProductConstraintManager}
*/
public License getUpcomingLicense() {
return LicenseManagerRegistry.INSTANCE.get().getUpcomingLicense(getProduct());
}
/**
* @param enteredLicenseKey
* the key that was entered and should be validated
* @return
* @throws UnknownProductException
* if the licenses belongs to an unknown product
* @throws LicenseValidationException
* if something goes wrong during validation this exception is thrown. <br/>
* CAUTION: the returned license can still have an invalid license status even
* though no exception was thrown.
*/
public Pair<Product, License> validateLicense(String enteredLicenseKey)
throws LicenseValidationException, UnknownProductException {
// LicenseManager accepts unknown(null) products
return LicenseManagerRegistry.INSTANCE.get().validateLicense(null, enteredLicenseKey);
}
/**
* See {@link LicenseManager#checkAnnotationViolations(Object, boolean)} for more details.
*/
public List<LicenseViolation> checkAnnotationViolations(Operator op, boolean informListeners) {
return LicenseManagerRegistry.INSTANCE.get().checkAnnotationViolations(op, informListeners);
}
/**
* See {@link LicenseManager#isAllowedByAnnotations(Object)} for more details.
*/
public boolean isAllowedByAnnotations(Operator op) {
return LicenseManagerRegistry.INSTANCE.get().isAllowedByAnnotations(op);
}
/**
* Checks if free license or a higher license is installed.
*
* @return {@code true} if at least a free license is installed
*/
public boolean isFreeFeatureAllowed() {
return !LicenseManagerRegistry.INSTANCE.get().getActiveLicense(ProductConstraintManager.INSTANCE.getProduct())
.isStarterLicense();
}
/**
* @return the data row constraint object
*/
public NumericalConstraint getDataRowConstraint() {
return dataRowConstraint;
}
/**
* @return the logical processor constraint object
*/
public NumericalConstraint getLogicalProcessorConstraint() {
return logicalProcessorConstraint;
}
/**
* @return the memory limit constraint object
*/
public NumericalConstraint getMemoryLimitConstraint() {
return memoryLimitConstraint;
}
/**
* @return the web service limit constraint object
*/
public NumericalConstraint getWebServiceLimitConstraint() {
return webServiceLimitConstraint;
}
}