1   package com.trendmicro.grid.acl.ds.cache.aspects;
2   
3   import com.trendmicro.grid.acl.ds.cache.CacheAdapter;
4   import com.trendmicro.grid.acl.ds.cache.CacheDestination;
5   import com.trendmicro.grid.acl.ds.cache.CacheSource;
6   import com.trendmicro.grid.acl.ds.cache.commands.GetFromCacheCommand;
7   import com.trendmicro.grid.acl.ds.cache.commands.GetFromPJPSourceCommand;
8   import com.trendmicro.grid.acl.ds.cache.commands.RemoveCommand;
9   import com.trendmicro.grid.acl.ds.cache.translation.IdentifierToNameTranslator;
10  import com.trendmicro.grid.acl.ds.cache.translation.PackageDetailsToFileIdentifierTranslator;
11  import com.trendmicro.grid.acl.ds.cache.translation.PackageDetailsToInformationTranslator;
12  import com.trendmicro.grid.acl.ds.datatypes.SharedPackageDetails;
13  import com.trendmicro.grid.acl.ds.datatypes.SharedPackageInformation;
14  import com.trendmicro.grid.acl.l0.datatypes.*;
15  import net.sf.tinyjee.cache.config.CacheRegion;
16  import org.aspectj.lang.ProceedingJoinPoint;
17  import org.aspectj.lang.annotation.AfterReturning;
18  import org.aspectj.lang.annotation.Around;
19  import org.aspectj.lang.annotation.Aspect;
20  import org.infinispan.Cache;
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  import org.springframework.stereotype.Service;
24  
25  import java.util.Collection;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.concurrent.Callable;
29  import java.util.concurrent.atomic.AtomicLong;
30  
31  import static com.trendmicro.grid.acl.ds.cache.CacheSettings.*;
32  
33  /**
34   * Implements an Aspect that wraps implementations of PackageProvider and adds
35   * caching capabilities to PackageInformation and PackageDetails related lookup methods.
36   *
37   * @author Juergen_Kellerer, 2011-02-19
38   * @version 1.0
39   */
40  @Aspect
41  @Service
42  public class PackageDetailsCacheAspect extends AbstractCacheAspect {
43  
44  	private static final Logger log = LoggerFactory.getLogger(PackageDetailsCacheAspect.class);
45  
46  	@CacheRegion(name = CACHE_REGION_PACKAGE_DETAILS)
47  	Cache<String, SharedPackageDetails> cachedPackageDetails;
48  	@CacheRegion(name = CACHE_REGION_PACKAGE_INFORMATION)
49  	Cache<String, SharedPackageInformation> cachedPackageInformation;
50  	@CacheRegion(name = CACHE_REGION_PACKAGE_FILE_IDENTIFIERS)
51  	Cache<FileIdentifier, String> identifiersToNames;
52  
53  	@CacheRegion(name = CACHE_REGION_UNKNOWN_FILE_IDENTIFIERS)
54  	Cache<FileIdentifier, Boolean> unknownFileIdentifiersCache;
55  	@CacheRegion(name = CACHE_REGION_UNKNOWN_NAMES)
56  	Cache<String, Boolean> unknownNamesCache;
57  
58  	private CacheAdapter<String, SharedPackageDetails> detailsAdapter;
59  	private CacheSource<FileIdentifier, SharedPackageDetails> detailsByIdSource;
60  	private CacheDestination<FileIdentifier, SharedPackageDetails> detailsByIdDestination;
61  
62  	private CacheSource<String, FileIdentifier> identifierFromDetails;
63  	private CacheSource<String, SharedPackageInformation> informationFromDetails;
64  	private CacheSource<FileIdentifier, SharedPackageInformation> informationFromDetailsById;
65  
66  	private CacheAdapter<String, SharedPackageInformation> informationAdapter;
67  	private CacheSource<FileIdentifier, SharedPackageInformation> informationByIdSource;
68  	private CacheDestination<FileIdentifier, SharedPackageInformation> informationByIdDestination;
69  
70  	private CacheAdapter<FileIdentifier, Boolean> unknownFileIdentifiers;
71  	private CacheAdapter<String, Boolean> unknownNames;
72  
73  	/**
74  	 * Wraps around method calls of getPackageInformationListByName() and asks caches for answers
75  	 * before delegating to the db.
76  	 *
77  	 * @param pjp          The ProceedingJoinPoint used to call the wrapped method.
78  	 * @param packageNames The names of the packages to look for.
79  	 * @return the package information list of the given package or 'null' if
80  	 *         no packages exists under the specified arguments.
81  	 * @throws Exception In case of the cache operations failed.
82  	 */
83  	@Around(value = "execution(* com.trendmicro.grid.acl.ds.PackageProvider.getPackageInformationListByName(..)) && " +
84  			"args(packageNames) && @annotation(com.trendmicro.grid.acl.ds.RemoteCacheable)",
85  			argNames = "pjp, packageNames")
86  	public Collection<SharedPackageInformation> getPackageInformationListByName(
87  			final ProceedingJoinPoint pjp, final Collection<String> packageNames) throws Exception {
88  
89  		return call(new GetFromCacheCommand<String, SharedPackageInformation>(packageNames, informationAdapter, informationFromDetails) {
90  			@Override
91  			protected Callable<Map<String, SharedPackageInformation>>
92  			createGetFromSourceCommand(Collection<String> keys) {
93  				return new GetFromPJPSourceCommand<String, SharedPackageInformation>(getCacheStatistics().getFetchedKeyCount(), keys, pjp);
94  			}
95  		}.useDestination(informationAdapter, cacheWriteMode).useUnknownKeysLookup(unknownNames, cacheWriteMode));
96  	}
97  
98  	/**
99  	 * Wraps around method calls of getPackageInformationListById() and asks caches for answers
100 	 * before delegating to the db.
101 	 *
102 	 * @param pjp   The ProceedingJoinPoint used to call the wrapped method.
103 	 * @param files The ids of the package files to look for.
104 	 * @return the package information list of the given package or 'null' if
105 	 *         no packages exists under the specified arguments.
106 	 * @throws Exception In case of the cache operations failed.
107 	 */
108 	@Around(value = "execution(* com.trendmicro.grid.acl.ds.PackageProvider.getPackageInformationListById(..)) && " +
109 			"args(files) && @annotation(com.trendmicro.grid.acl.ds.RemoteCacheable)",
110 			argNames = "pjp, files")
111 	public Collection<SharedPackageInformation> getPackageInformationListById(
112 			final ProceedingJoinPoint pjp, final Collection<FileIdentifier> files) throws Exception {
113 
114 		return call(new GetFromCacheCommand<FileIdentifier, SharedPackageInformation>(files, informationByIdSource, informationFromDetailsById) {
115 			@Override
116 			protected Callable<Map<FileIdentifier, SharedPackageInformation>>
117 			createGetFromSourceCommand(Collection<FileIdentifier> keys) {
118 				return new GetFromPJPSourceCommand<FileIdentifier, SharedPackageInformation>(getCacheStatistics().getFetchedKeyCount(), keys, pjp);
119 			}
120 		}.useDestination(informationByIdDestination, cacheWriteMode).useUnknownKeysLookup(unknownFileIdentifiers, cacheWriteMode));
121 	}
122 
123 	/**
124 	 * Wraps around method calls of getPackageDetailsListByName() and asks caches for answers
125 	 * before delegating to the db.
126 	 *
127 	 * @param pjp          The ProceedingJoinPoint used to call the wrapped method.
128 	 * @param packageNames The names the packages to lookup.
129 	 * @return the package details on the given package names.
130 	 * @throws Exception In case of the cache operations failed.
131 	 */
132 	@Around(value = "execution(* com.trendmicro.grid.acl.ds.PackageProvider.getPackageDetailsListByName(..)) && " +
133 			"args(packageNames) && @annotation(com.trendmicro.grid.acl.ds.RemoteCacheable)",
134 			argNames = "pjp, packageNames")
135 	public Collection<SharedPackageDetails> getPackageDetailsListByName(
136 			final ProceedingJoinPoint pjp, final Collection<String> packageNames) throws Exception {
137 
138 		return call(new GetFromCacheCommand<String, SharedPackageDetails>(packageNames, detailsAdapter) {
139 			@Override
140 			protected Callable<Map<String, SharedPackageDetails>>
141 			createGetFromSourceCommand(Collection<String> keys) {
142 				return new GetFromSourceAndMapIdsCommand(getCacheStatistics().getFetchedKeyCount(), keys, pjp);
143 			}
144 		}.useDestination(detailsAdapter, cacheWriteMode).useUnknownKeysLookup(unknownNames, cacheWriteMode));
145 	}
146 
147 	/**
148 	 * Wraps around method calls of getPackageDetailsListByName() and asks caches for answers
149 	 * before delegating to the db.
150 	 *
151 	 * @param pjp   The ProceedingJoinPoint used to call the wrapped method.
152 	 * @param files The SHA1 hashes of the package files.
153 	 * @return the package details on the given package file ids.
154 	 * @throws Exception In case of the cache operations failed.
155 	 */
156 	@Around(value = "execution(* com.trendmicro.grid.acl.ds.PackageProvider.getPackageDetailsListById(..)) && " +
157 			"args(files) && @annotation(com.trendmicro.grid.acl.ds.RemoteCacheable)",
158 			argNames = "pjp, files")
159 	public Collection<SharedPackageDetails> getPackageDetailsListById(
160 			final ProceedingJoinPoint pjp, final Collection<FileIdentifier> files) throws Exception {
161 
162 		return call(new GetFromCacheCommand<FileIdentifier, SharedPackageDetails>(files, detailsByIdSource) {
163 			@Override
164 			protected Callable<Map<FileIdentifier, SharedPackageDetails>>
165 			createGetFromSourceCommand(Collection<FileIdentifier> keys) {
166 				return new GetFromPJPSourceCommand<FileIdentifier, SharedPackageDetails>(getCacheStatistics().getFetchedKeyCount(), keys, pjp);
167 			}
168 		}.useDestination(detailsByIdDestination, cacheWriteMode).useUnknownKeysLookup(unknownFileIdentifiers, cacheWriteMode));
169 	}
170 
171 	/**
172 	 * Wraps around method calls of getPackageFileIdentifiersByName() and asks caches for answers
173 	 * before delegating to the db.
174 	 *
175 	 * @param pjp          The ProceedingJoinPoint used to call the wrapped method.
176 	 * @param packageNames The names the packages to lookup.
177 	 * @return the the file ID of the file that is associated with the given package.
178 	 * @throws Exception In case of the cache operations failed.
179 	 */
180 	@Around(value = "execution(* com.trendmicro.grid.acl.ds.PackageProvider.getPackageFileIdentifiersByName(..)) && " +
181 			"args(packageNames) && @annotation(com.trendmicro.grid.acl.ds.RemoteCacheable)",
182 			argNames = "pjp, packageNames")
183 	public Collection<FileIdentifier> getPackageFileIdentifiersByName(
184 			final ProceedingJoinPoint pjp, final Collection<String> packageNames) throws Exception {
185 
186 		return call(new GetFromCacheCommand<String, FileIdentifier>(packageNames, identifierFromDetails) {
187 			@Override
188 			protected Callable<Map<String, FileIdentifier>> createGetFromSourceCommand(Collection<String> keys) {
189 				return new GetFromPJPSourceCommand<String, FileIdentifier>(getCacheStatistics().getFetchedKeyCount(), keys, pjp);
190 			}
191 		}.useUnknownKeysLookup(unknownNames, cacheWriteMode));
192 	}
193 
194 	/**
195 	 * Fires after method calls to receive(..) and invalidates caches on all newly stored entries.
196 	 *
197 	 * @param dataSets the stored datasets.
198 	 * @throws Exception In case of the cache operations failed.
199 	 */
200 	@AfterReturning(value = "execution(* com.trendmicro.grid.acl.ds.ProcessingResultReceiver.receive(..)) && " +
201 			"args(dataSets)", argNames = "dataSets")
202 	public void invalidateCachesAfterReceivingUpdates(final Collection<ProcessPackageDataSet> dataSets) throws Exception {
203 
204 		final HashMap<String, FileIdentifier> contentToRemove = new HashMap<String, FileIdentifier>(dataSets.size());
205 		for (ProcessPackageDataSet dataSet : dataSets) {
206 			PackageDetails processedPackage = dataSet.getProcessedPackage();
207 			if (processedPackage != null) {
208 				PackageInformation information = processedPackage.getPackageInformation();
209 				FileMetadata fm = processedPackage.getFileMetadata();
210 
211 				if (information != null && fm != null) {
212 					contentToRemove.put(information.getName(), fm.getIdentifier());
213 					removeNameMapping(information.getName(), fm.getIdentifier());
214 
215 					if (log.isTraceEnabled()) log.trace("Invalidated cached entries for package '{}'", information.getName());
216 				}
217 			}
218 		}
219 
220 
221 		call(new RemoveCommand<String, Object>(contentToRemove.keySet(), unknownNames, detailsAdapter, informationAdapter) {
222 			@Override
223 			protected void removeKeys() {
224 				for (Map.Entry<String, FileIdentifier> entry : contentToRemove.entrySet())
225 					removeNameMapping(entry.getKey(), entry.getValue());
226 				super.removeKeys();
227 			}
228 		}.asBatchCommand());
229 	}
230 
231 	private void removeNameMapping(String name, FileIdentifier newIdentifier) {
232 		final SharedPackageDetails details = cachedPackageDetails.get(name);
233 		final boolean hasFileMetadata = details != null && details.getFileMetadata() != null;
234 		final FileIdentifier oldIdentifier = hasFileMetadata ? details.getFileMetadata().getIdentifier() : null;
235 
236 		if (oldIdentifier != null && !oldIdentifier.equals(newIdentifier)) {
237 			for (FileIdentifier identifier : oldIdentifier.getVariants()) {
238 				identifiersToNames.remove(identifier);
239 			}
240 
241 		} else if (identifiersToNames.containsKey(newIdentifier) && !name.equals(identifiersToNames.get(newIdentifier))) {
242 			log.warn("TMACL-01610:Failed to invalidate the old file identifier to package name mapping inside the " +
243 					"3rd level cache for package '{}'. The existing identifier could not be resolved and " +
244 					"the file content of the package changed. In the worst case, public queries return the same " +
245 					"content for the new and old file-id until the old file-id gets updated as well or is " +
246 					"auto-invalidated when it reaches its idle timeout.", name);
247 		}
248 	}
249 
250 
251 	public void setIdentifiersToNames(Cache<FileIdentifier, String> identifiersToNames) {
252 		this.identifiersToNames = identifiersToNames;
253 		updateDependentAdapters(identifiersToNames);
254 	}
255 
256 	public void setCachedPackageInformation(Cache<String, SharedPackageInformation> cachedPackageInformation) {
257 		this.cachedPackageInformation = cachedPackageInformation;
258 		informationAdapter = new CacheAdapter<String, SharedPackageInformation>(cachedPackageInformation, sync);
259 		updateDependentAdapters(identifiersToNames);
260 	}
261 
262 	public void setCachedPackageDetails(Cache<String, SharedPackageDetails> cachedPackageDetails) {
263 		this.cachedPackageDetails = cachedPackageDetails;
264 		detailsAdapter = new CacheAdapter<String, SharedPackageDetails>(cachedPackageDetails, sync);
265 		informationFromDetails = new PackageDetailsToInformationTranslator<String>().newSource(detailsAdapter);
266 		updateDependentAdapters(identifiersToNames);
267 	}
268 
269 	private void updateDependentAdapters(Cache<FileIdentifier, String> identifiersToNames) {
270 		if (detailsAdapter != null && informationAdapter != null && identifiersToNames != null) {
271 			final IdentifierToNameTranslator<SharedPackageDetails> translator =
272 					new IdentifierToNameTranslator<SharedPackageDetails>(identifiersToNames);
273 			final IdentifierToNameTranslator<SharedPackageInformation> infoTranslator =
274 					new IdentifierToNameTranslator<SharedPackageInformation>(identifiersToNames);
275 
276 			detailsByIdSource = translator.newSource(detailsAdapter);
277 			detailsByIdDestination = translator.newDestination(detailsAdapter);
278 			informationByIdSource = infoTranslator.newSource(informationAdapter);
279 			informationByIdDestination = infoTranslator.newDestination(informationAdapter);
280 
281 			informationFromDetailsById = new PackageDetailsToInformationTranslator<FileIdentifier>().
282 					newSource(detailsByIdSource);
283 
284 			identifierFromDetails = new PackageDetailsToFileIdentifierTranslator<String>().newSource(detailsAdapter);
285 		}
286 	}
287 
288 	public void setUnknownFileIdentifiersCache(Cache<FileIdentifier, Boolean> unknownFileIdentifiersCache) {
289 		this.unknownFileIdentifiersCache = unknownFileIdentifiersCache;
290 		unknownFileIdentifiers = new CacheAdapter<FileIdentifier, Boolean>(unknownFileIdentifiersCache, sync);
291 	}
292 
293 	public void setUnknownNamesCache(Cache<String, Boolean> unknownNamesCache) {
294 		this.unknownNamesCache = unknownNamesCache;
295 		unknownNames = new CacheAdapter<String, Boolean>(unknownNamesCache, sync);
296 	}
297 
298 	private class GetFromSourceAndMapIdsCommand extends GetFromPJPSourceCommand<String, SharedPackageDetails> {
299 
300 		private GetFromSourceAndMapIdsCommand(AtomicLong fetchedKeyCount, Collection<String> keys, ProceedingJoinPoint pjp) {
301 			super(fetchedKeyCount, keys, pjp);
302 		}
303 
304 		@Override
305 		public Map<String, SharedPackageDetails> doCall() throws Exception {
306 			Map<String, SharedPackageDetails> values = super.doCall();
307 			mapNamesToIdentifiersFromValues(values);
308 			return values;
309 		}
310 
311 		private void mapNamesToIdentifiersFromValues(final Map<String, SharedPackageDetails> values) {
312 			if (DISABLED)
313 				return;
314 
315 			Map<FileIdentifier, String> mappings = new HashMap<FileIdentifier, String>(values.size());
316 			for (Map.Entry<String, SharedPackageDetails> entry : values.entrySet()) {
317 				if (entry.getValue() == null)
318 					continue;
319 				FileMetadata fm = entry.getValue().getFileMetadata();
320 				if (fm != null)
321 					mappings.put(fm.getIdentifier(), entry.getKey());
322 			}
323 
324 			if (!mappings.isEmpty()) {
325 				if (sync)
326 					identifiersToNames.putAll(mappings);
327 				else
328 					identifiersToNames.putAllAsync(mappings);
329 			}
330 
331 			/*
332 			* TODO
333 			call(new CacheResultsCommand<FileIdentifier, String>(
334 				values, identifiersToNames, Mode.putIfValueDiffers).asBatchCommand());
335 
336 			SharedPackageDetails details = (SharedPackageDetails) entry.getValue();
337 			FileMetadata fm = details == null ? null : details.getFileMetadata();
338 			return fm == null ? null : fm.getIdentifier();
339 			*/
340 		}
341 	}
342 }