/* * Syncany, www.syncany.org * Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.syncany.plugins.transfer; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.syncany.config.Config; import org.syncany.plugins.transfer.features.ReadAfterWriteConsistent; import org.syncany.plugins.transfer.features.Feature; import org.syncany.plugins.transfer.features.FeatureTransferManager; import org.syncany.plugins.transfer.features.PathAware; import org.syncany.plugins.transfer.features.Retriable; import org.syncany.plugins.transfer.features.TransactionAware; import org.syncany.util.ReflectionUtil; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; /** * This factory class creates a {@link TransferManager} from a * {@link Config} object, and wraps it into the requested {@link Feature}(s). * * <p>Depending on the {@link Feature}s that the original transfer manager is * annotated with, the factory will wrap it into the corresponding feature * specific transfer managers. * * <p>The class uses the builder pattern. It can be used like this: * * <pre> * TransactionAwareFeatureTransferManager txAwareTM = TransferManagerFactory * .build(config) * .withFeature(Retriable.class) * .withFeature(PathAware.class) * .withFeature(TransactionAware.class) * .as(TransactionAware.class); * </pre> * * @see Feature * @see FeatureTransferManager * @see TransferManager * @author Christian Roth <christian.roth@port17.de> * @author Philipp C. Heckel <philipp.heckel@gmail.com> */ public class TransferManagerFactory { private static final Logger logger = Logger.getLogger(TransferManagerFactory.class.getSimpleName()); private static final String FEATURE_TRANSFER_MANAGER_FORMAT = Feature.class.getPackage().getName() + ".%s" + FeatureTransferManager.class.getSimpleName(); private static final List<Class<? extends Annotation>> FEATURE_LIST = ImmutableList.<Class<? extends Annotation>> builder() .add(TransactionAware.class) .add(Retriable.class) .add(PathAware.class) .add(ReadAfterWriteConsistent.class) .build(); /** * Creates the transfer manager factory builder from the {@link Config} * using the configured {@link TransferPlugin}. Using this builder, the * feature-wrapped transfer manager can be built. * * @see TransferManagerBuilder * @param config Local folder configuration with transfer plugin settings * @return Transfer manager builder */ public static TransferManagerBuilder build(Config config) throws StorageException { TransferManager transferManager = config.getTransferPlugin().createTransferManager(config.getConnection(), config); logger.log(Level.INFO, "Building " + transferManager.getClass().getSimpleName() + " from config '" + config.getLocalDir().getName() + "' ..."); return new TransferManagerBuilder(config, transferManager); } /** * The transfer manager builder takes an original {@link TransferManager}, and * wraps it with feature-specific transfer managers, if the original transfer * manager is annotated with a {@link Feature} annotation. * * <p>The class uses the builder pattern. Its usage is described in the * {@link TransferManagerFactory}. The two main methods of this class are * {@link #withFeature(Class)} and {@link #as(Class)}. * * @see Feature * @see TransferManagerFactory */ public static class TransferManagerBuilder { private List<Class<? extends Annotation>> features; private Config config; private TransferManager originalTransferManager; private TransferManager wrappedTransferManager; private TransferManagerBuilder(Config config, TransferManager transferManager) { this.config = config; this.originalTransferManager = transferManager; this.wrappedTransferManager = transferManager; this.features = new ArrayList<>(); } /** * This method requests the original transfer manager to be wrapped in the corresponding * feature transfer manager. * * <p><b>Note:</b> Calling this method does not automatically wrap the transfer manager. * It will only be wrapped if the original transfer manager is annotated with the feature * annotation. * * <p>If the requested {@link Feature} is required (as per its definition), but the original * transfer manager is not annotated with this feature, the creation of the transfer manager * will fail. * * @param featureAnnotation Annotation representing the feature (see features.* package) * @return Returns this builder class (for more features to be requested) */ public TransferManagerBuilder withFeature(Class<? extends Annotation> featureAnnotation) { logger.log(Level.INFO, "- With feature " + featureAnnotation.getSimpleName()); features.add(featureAnnotation); return this; } /** * Wraps of the previously requested feature transfer managers and casts the result to the requested class. * If no specific class is requested, {@link #asDefault()} can be used instead. * * @param featureAnnotation Feature annotation corresponding to the requested transfer manager * @return {@link TransferManager} casted to the feature lasted wrapped (and requested by this method) */ @SuppressWarnings("unchecked") public <T extends TransferManager> T as(Class<? extends Annotation> featureAnnotation) { Class<T> implementingTransferManagerClass = (Class<T>) getFeatureTransferManagerClass(featureAnnotation); return wrap(implementingTransferManagerClass); } /** * Wraps of the previously requested feature transfer managers and returns a standard transfer manager. * @return {@link TransferManager} wrapped with the requested features */ public TransferManager asDefault() { return wrap(TransferManager.class); } private <T extends TransferManager> T wrap(Class<T> desiredTransferManagerClass) { checkIfAllFeaturesSupported(); checkDuplicateFeatures(); checkRequiredFeatures(); applyFeatures(); return castToDesiredTransferManager(desiredTransferManagerClass); } private void applyFeatures() { try { for (Class<? extends Annotation> featureAnnotation : features) { boolean isFeatureSupported = ReflectionUtil.isAnnotationPresentInHierarchy(originalTransferManager.getClass(), featureAnnotation); if (isFeatureSupported) { Class<? extends TransferManager> featureTransferManagerClass = getFeatureTransferManagerClass(featureAnnotation); wrappedTransferManager = apply(wrappedTransferManager, featureTransferManagerClass, featureAnnotation); } else { logger.log(Level.INFO, "- SKIPPING unsupported optional feature " + featureAnnotation.getSimpleName()); } } } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { throw new RuntimeException("Unable to annotate TransferManager with feature.", e); } } private <T extends TransferManager> T castToDesiredTransferManager(Class<T> desiredTransferManagerClass) { try { return desiredTransferManagerClass.cast(wrappedTransferManager); } catch (ClassCastException e) { throw new RuntimeException("Unable to wrap TransferManager in " + desiredTransferManagerClass.getSimpleName() + " because feature does not seem to be supported", e); } } private TransferManager apply(TransferManager underlyingTransferManager, Class<? extends TransferManager> featureTransferManagerClass, Class<? extends Annotation> featureAnnotationClass) throws IllegalAccessException, InvocationTargetException, InstantiationException { logger.log(Level.FINE, "- Wrapping TransferManager " + underlyingTransferManager.getClass().getSimpleName() + " in " + featureTransferManagerClass.getSimpleName()); Annotation concreteFeatureAnnotation = ReflectionUtil.getAnnotationInHierarchy(originalTransferManager.getClass(), featureAnnotationClass); Constructor<?> transferManagerConstructor = ReflectionUtil.getMatchingConstructorForClass(featureTransferManagerClass, TransferManager.class, TransferManager.class, Config.class, featureAnnotationClass); if (transferManagerConstructor == null) { throw new RuntimeException("Invalid TransferManager class detected: Unable to find constructor."); } Annotation featureAnnotation = featureAnnotationClass.cast(concreteFeatureAnnotation); return (TransferManager) transferManagerConstructor.newInstance(originalTransferManager, underlyingTransferManager, config, featureAnnotation); } private static Class<? extends TransferManager> getFeatureTransferManagerClass(Class<? extends Annotation> featureAnnotation) { String featureTransferManagerClassName = String.format(FEATURE_TRANSFER_MANAGER_FORMAT, featureAnnotation.getSimpleName()); try { return (Class<? extends TransferManager>) Class.forName(featureTransferManagerClassName).asSubclass(TransferManager.class); } catch (Exception e) { throw new RuntimeException("Unable to find class with feature " + featureAnnotation.getSimpleName() + ". Tried " + featureTransferManagerClassName, e); } } private void checkRequiredFeatures() { // TODO [low] Instead of a feature list, all available @Feature annotations should be listed with reflection for (Class<? extends Annotation> concreteFeatureAnnotation : FEATURE_LIST) { Feature featureAnnotation = ReflectionUtil.getAnnotationInHierarchy(concreteFeatureAnnotation, Feature.class); if (featureAnnotation.required()) { logger.log(Level.FINE, "- Checking required feature " + concreteFeatureAnnotation.getSimpleName() + " in " + originalTransferManager.getClass().getSimpleName() + " ..."); boolean requiredFeaturePresent = ReflectionUtil.isAnnotationPresentInHierarchy(originalTransferManager.getClass(), concreteFeatureAnnotation); if (!requiredFeaturePresent) { throw new RuntimeException("Required feature " + concreteFeatureAnnotation.getSimpleName() + " is not present in " + originalTransferManager.getClass().getSimpleName()); } } } } private void checkDuplicateFeatures() { logger.log(Level.FINE, "- Checking for duplicate features ..."); int listSize = features.size(); int setSize = Sets.newHashSet(features).size(); if (listSize != setSize) { throw new IllegalArgumentException("There are duplicates in feature set: " + features); } } private void checkIfAllFeaturesSupported() { logger.log(Level.FINE, "- Checking if selected features supported ..."); for (Class<? extends Annotation> featureAnnotation : features) { if (!featureAnnotation.isAnnotationPresent(Feature.class)) { throw new IllegalArgumentException("Feature " + featureAnnotation + " is unknown"); } } } } }