001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.plugins.transfer; 019 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Constructor; 022import java.lang.reflect.InvocationTargetException; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.logging.Level; 026import java.util.logging.Logger; 027 028import org.syncany.config.Config; 029import org.syncany.plugins.transfer.features.ReadAfterWriteConsistent; 030import org.syncany.plugins.transfer.features.Feature; 031import org.syncany.plugins.transfer.features.FeatureTransferManager; 032import org.syncany.plugins.transfer.features.PathAware; 033import org.syncany.plugins.transfer.features.Retriable; 034import org.syncany.plugins.transfer.features.TransactionAware; 035import org.syncany.util.ReflectionUtil; 036import com.google.common.collect.ImmutableList; 037import com.google.common.collect.Sets; 038 039/** 040 * This factory class creates a {@link TransferManager} from a 041 * {@link Config} object, and wraps it into the requested {@link Feature}(s). 042 * 043 * <p>Depending on the {@link Feature}s that the original transfer manager is 044 * annotated with, the factory will wrap it into the corresponding feature 045 * specific transfer managers. 046 * 047 * <p>The class uses the builder pattern. It can be used like this: 048 * 049 * <pre> 050 * TransactionAwareFeatureTransferManager txAwareTM = TransferManagerFactory 051 * .build(config) 052 * .withFeature(Retriable.class) 053 * .withFeature(PathAware.class) 054 * .withFeature(TransactionAware.class) 055 * .as(TransactionAware.class); 056 * </pre> 057 * 058 * @see Feature 059 * @see FeatureTransferManager 060 * @see TransferManager 061 * @author Christian Roth (christian.roth@port17.de) 062 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 063 */ 064public class TransferManagerFactory { 065 private static final Logger logger = Logger.getLogger(TransferManagerFactory.class.getSimpleName()); 066 067 private static final String FEATURE_TRANSFER_MANAGER_FORMAT = Feature.class.getPackage().getName() + ".%s" + FeatureTransferManager.class.getSimpleName(); 068 private static final List<Class<? extends Annotation>> FEATURE_LIST = ImmutableList.<Class<? extends Annotation>> builder() 069 .add(TransactionAware.class) 070 .add(Retriable.class) 071 .add(PathAware.class) 072 .add(ReadAfterWriteConsistent.class) 073 .build(); 074 075 /** 076 * Creates the transfer manager factory builder from the {@link Config} 077 * using the configured {@link TransferPlugin}. Using this builder, the 078 * feature-wrapped transfer manager can be built. 079 * 080 * @see TransferManagerBuilder 081 * @param config Local folder configuration with transfer plugin settings 082 * @return Transfer manager builder 083 */ 084 public static TransferManagerBuilder build(Config config) throws StorageException { 085 TransferManager transferManager = config.getTransferPlugin().createTransferManager(config.getConnection(), config); 086 logger.log(Level.INFO, "Building " + transferManager.getClass().getSimpleName() + " from config '" + config.getLocalDir().getName() + "' ..."); 087 088 return new TransferManagerBuilder(config, transferManager); 089 } 090 091 /** 092 * The transfer manager builder takes an original {@link TransferManager}, and 093 * wraps it with feature-specific transfer managers, if the original transfer 094 * manager is annotated with a {@link Feature} annotation. 095 * 096 * <p>The class uses the builder pattern. Its usage is described in the 097 * {@link TransferManagerFactory}. The two main methods of this class are 098 * {@link #withFeature(Class)} and {@link #as(Class)}. 099 * 100 * @see Feature 101 * @see TransferManagerFactory 102 */ 103 public static class TransferManagerBuilder { 104 private List<Class<? extends Annotation>> features; 105 private Config config; 106 private TransferManager originalTransferManager; 107 private TransferManager wrappedTransferManager; 108 109 private TransferManagerBuilder(Config config, TransferManager transferManager) { 110 this.config = config; 111 this.originalTransferManager = transferManager; 112 this.wrappedTransferManager = transferManager; 113 this.features = new ArrayList<>(); 114 } 115 116 /** 117 * This method requests the original transfer manager to be wrapped in the corresponding 118 * feature transfer manager. 119 * 120 * <p><b>Note:</b> Calling this method does not automatically wrap the transfer manager. 121 * It will only be wrapped if the original transfer manager is annotated with the feature 122 * annotation. 123 * 124 * <p>If the requested {@link Feature} is required (as per its definition), but the original 125 * transfer manager is not annotated with this feature, the creation of the transfer manager 126 * will fail. 127 * 128 * @param featureAnnotation Annotation representing the feature (see features.* package) 129 * @return Returns this builder class (for more features to be requested) 130 */ 131 public TransferManagerBuilder withFeature(Class<? extends Annotation> featureAnnotation) { 132 logger.log(Level.INFO, "- With feature " + featureAnnotation.getSimpleName()); 133 134 features.add(featureAnnotation); 135 return this; 136 } 137 138 /** 139 * Wraps of the previously requested feature transfer managers and casts the result to the requested class. 140 * If no specific class is requested, {@link #asDefault()} can be used instead. 141 * 142 * @param featureAnnotation Feature annotation corresponding to the requested transfer manager 143 * @return {@link TransferManager} casted to the feature lasted wrapped (and requested by this method) 144 */ 145 @SuppressWarnings("unchecked") 146 public <T extends TransferManager> T as(Class<? extends Annotation> featureAnnotation) { 147 Class<T> implementingTransferManagerClass = (Class<T>) getFeatureTransferManagerClass(featureAnnotation); 148 return wrap(implementingTransferManagerClass); 149 } 150 151 /** 152 * Wraps of the previously requested feature transfer managers and returns a standard transfer manager. 153 * @return {@link TransferManager} wrapped with the requested features 154 */ 155 public TransferManager asDefault() { 156 return wrap(TransferManager.class); 157 } 158 159 private <T extends TransferManager> T wrap(Class<T> desiredTransferManagerClass) { 160 checkIfAllFeaturesSupported(); 161 checkDuplicateFeatures(); 162 checkRequiredFeatures(); 163 164 applyFeatures(); 165 166 return castToDesiredTransferManager(desiredTransferManagerClass); 167 } 168 169 private void applyFeatures() { 170 try { 171 for (Class<? extends Annotation> featureAnnotation : features) { 172 boolean isFeatureSupported = ReflectionUtil.isAnnotationPresentInHierarchy(originalTransferManager.getClass(), featureAnnotation); 173 174 if (isFeatureSupported) { 175 Class<? extends TransferManager> featureTransferManagerClass = getFeatureTransferManagerClass(featureAnnotation); 176 wrappedTransferManager = apply(wrappedTransferManager, featureTransferManagerClass, featureAnnotation); 177 } 178 else { 179 logger.log(Level.INFO, "- SKIPPING unsupported optional feature " + featureAnnotation.getSimpleName()); 180 } 181 } 182 } 183 catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { 184 throw new RuntimeException("Unable to annotate TransferManager with feature.", e); 185 } 186 } 187 188 private <T extends TransferManager> T castToDesiredTransferManager(Class<T> desiredTransferManagerClass) { 189 try { 190 return desiredTransferManagerClass.cast(wrappedTransferManager); 191 } 192 catch (ClassCastException e) { 193 throw new RuntimeException("Unable to wrap TransferManager in " + desiredTransferManagerClass.getSimpleName() 194 + " because feature does not seem to be supported", e); 195 } 196 } 197 198 private TransferManager apply(TransferManager underlyingTransferManager, Class<? extends TransferManager> featureTransferManagerClass, 199 Class<? extends Annotation> featureAnnotationClass) throws IllegalAccessException, InvocationTargetException, InstantiationException { 200 201 logger.log(Level.FINE, 202 "- Wrapping TransferManager " + underlyingTransferManager.getClass().getSimpleName() + " in " + featureTransferManagerClass.getSimpleName()); 203 204 Annotation concreteFeatureAnnotation = ReflectionUtil.getAnnotationInHierarchy(originalTransferManager.getClass(), featureAnnotationClass); 205 Constructor<?> transferManagerConstructor = ReflectionUtil.getMatchingConstructorForClass(featureTransferManagerClass, TransferManager.class, TransferManager.class, Config.class, featureAnnotationClass); 206 207 if (transferManagerConstructor == null) { 208 throw new RuntimeException("Invalid TransferManager class detected: Unable to find constructor."); 209 } 210 211 Annotation featureAnnotation = featureAnnotationClass.cast(concreteFeatureAnnotation); 212 return (TransferManager) transferManagerConstructor.newInstance(originalTransferManager, underlyingTransferManager, config, featureAnnotation); 213 } 214 215 private static Class<? extends TransferManager> getFeatureTransferManagerClass(Class<? extends Annotation> featureAnnotation) { 216 String featureTransferManagerClassName = String.format(FEATURE_TRANSFER_MANAGER_FORMAT, featureAnnotation.getSimpleName()); 217 218 try { 219 return (Class<? extends TransferManager>) Class.forName(featureTransferManagerClassName).asSubclass(TransferManager.class); 220 } 221 catch (Exception e) { 222 throw new RuntimeException("Unable to find class with feature " + featureAnnotation.getSimpleName() + ". Tried " + featureTransferManagerClassName, e); 223 } 224 } 225 226 private void checkRequiredFeatures() { 227 // TODO [low] Instead of a feature list, all available @Feature annotations should be listed with reflection 228 229 for (Class<? extends Annotation> concreteFeatureAnnotation : FEATURE_LIST) { 230 Feature featureAnnotation = ReflectionUtil.getAnnotationInHierarchy(concreteFeatureAnnotation, Feature.class); 231 232 if (featureAnnotation.required()) { 233 logger.log(Level.FINE, "- Checking required feature " + concreteFeatureAnnotation.getSimpleName() + " in " + originalTransferManager.getClass().getSimpleName() + " ..."); 234 boolean requiredFeaturePresent = ReflectionUtil.isAnnotationPresentInHierarchy(originalTransferManager.getClass(), concreteFeatureAnnotation); 235 236 if (!requiredFeaturePresent) { 237 throw new RuntimeException("Required feature " + concreteFeatureAnnotation.getSimpleName() + " is not present in " + originalTransferManager.getClass().getSimpleName()); 238 } 239 } 240 } 241 } 242 243 private void checkDuplicateFeatures() { 244 logger.log(Level.FINE, "- Checking for duplicate features ..."); 245 246 int listSize = features.size(); 247 int setSize = Sets.newHashSet(features).size(); 248 249 if (listSize != setSize) { 250 throw new IllegalArgumentException("There are duplicates in feature set: " + features); 251 } 252 } 253 254 private void checkIfAllFeaturesSupported() { 255 logger.log(Level.FINE, "- Checking if selected features supported ..."); 256 257 for (Class<? extends Annotation> featureAnnotation : features) { 258 if (!featureAnnotation.isAnnotationPresent(Feature.class)) { 259 throw new IllegalArgumentException("Feature " + featureAnnotation + " is unknown"); 260 } 261 } 262 } 263 } 264}