1   package com.trendmicro.grid.acl.ds.jpa;
2   
3   import com.trendmicro.grid.acl.ds.PackageProvider;
4   import com.trendmicro.grid.acl.ds.RemoteCacheable;
5   import com.trendmicro.grid.acl.ds.TagQueryExpression;
6   import com.trendmicro.grid.acl.ds.datatypes.SharedNamedFileIdentifier;
7   import com.trendmicro.grid.acl.ds.datatypes.SharedNamedFileIdentifierListPage;
8   import com.trendmicro.grid.acl.ds.datatypes.SharedPackageDetails;
9   import com.trendmicro.grid.acl.ds.datatypes.SharedPackageInformation;
10  import com.trendmicro.grid.acl.ds.jpa.entities.JpaFileInformation;
11  import com.trendmicro.grid.acl.ds.jpa.entities.JpaNamedFileIdentifier;
12  import com.trendmicro.grid.acl.ds.jpa.entities.JpaPackageDetails;
13  import com.trendmicro.grid.acl.ds.jpa.entities.JpaPackageInformation;
14  import com.trendmicro.grid.acl.ds.jpa.tagquery.TagQueryProviderSelector;
15  import com.trendmicro.grid.acl.ds.jpa.util.FileQueryConfigurator;
16  import com.trendmicro.grid.acl.l0.datatypes.*;
17  import org.slf4j.Logger;
18  import org.slf4j.LoggerFactory;
19  import org.springframework.stereotype.Repository;
20  import org.springframework.transaction.annotation.Transactional;
21  
22  import javax.annotation.Resource;
23  import javax.persistence.EntityManager;
24  import javax.persistence.PersistenceContext;
25  import javax.persistence.Query;
26  import javax.persistence.TypedQuery;
27  import java.util.*;
28  
29  import static com.trendmicro.grid.acl.ds.jpa.util.JpaUtils.*;
30  
31  /**
32   * Implements FileProvider using JPA.
33   *
34   * @author juergen_kellerer, 2010-06-11
35   * @version 1.0
36   */
37  @Repository
38  @Transactional(readOnly = true)
39  public class JpaPackageRepository implements PackageProvider {
40  
41  	private static final Logger log = LoggerFactory.getLogger(JpaPackageRepository.class);
42  
43  	private static int pageSize = 1000;
44  
45  	public static int getPageSize() {
46  		return pageSize;
47  	}
48  
49  	public static void setPageSize(int pageSize) {
50  		JpaPackageRepository.pageSize = pageSize;
51  	}
52  
53  	/**
54  	 * Assembles an SharedNamedFileIdentifier instance.
55  	 * <p/>
56  	 * Note: The elements returned by this implementation must not be of an
57  	 * instance of JpaNamedFileIdentifier as the equals and hashCode
58  	 * implementations differ and are not compatible.
59  	 */
60  	static final Callback<Object[], NamedFileIdentifier> namedFileIdentifierAssembler = new Callback<Object[], NamedFileIdentifier>() {
61  		@Override
62  		public NamedFileIdentifier call(Object[] columns) {
63  			final FileIdentifier identifier = (FileIdentifier) columns[1];
64  			if (columns[0] instanceof String) {
65  				final String fileName = (String) columns[0];
66  				return new SharedNamedFileIdentifier(identifier, fileName);
67  			} else
68  				return ((JpaNamedFileIdentifier) columns[0]).export(identifier);
69  		}
70  	};
71  
72  	@PersistenceContext(unitName = "CoreDB")
73  	EntityManager em;
74  
75  	@Resource
76  	TagQueryProviderSelector providerSelector;
77  
78  	Integer rootPackageKey;
79  
80  	@Override
81  	public NameListPage getPackageNamesInFamily(String packageFamilyName, int pageNumber) {
82  		TypedQuery<String> query = em.createNamedQuery("Packages.GetPackageNamesInFamily", String.class).
83  				setParameter("familyName", packageFamilyName);
84  		return toNameListPage(query, pageNumber, pageSize);
85  	}
86  
87  	@Override
88  	@RemoteCacheable
89  	public Collection<Boolean> isPackagesTaggedWithById(Collection<FileIdentifier> files, String[] tags) {
90  		// This is a un-optimized version of this method that should not be called.
91  		// The reason that this implementation exists is only to handle the case that caching is disabled.
92  		final Collection<Boolean> results = new ArrayList<Boolean>(files.size());
93  		for (SharedPackageInformation information : getPackageInformationListById(files))
94  			results.add(isTaggedWith(information, tags));
95  
96  		return results;
97  	}
98  
99  	@Override
100 	@RemoteCacheable
101 	public Collection<Boolean> isPackagesTaggedWithByName(Collection<String> packageNames, String[] tags) {
102 		// This is a un-optimized version of this method that should not be called.
103 		// The reason that this implementation exists is only to handle the case that caching is disabled.
104 		final Collection<Boolean> results = new ArrayList<Boolean>(packageNames.size());
105 		for (SharedPackageInformation information : getPackageInformationListByName(packageNames))
106 			results.add(isTaggedWith(information, tags));
107 
108 		return results;
109 	}
110 
111 	@Override
112 	public NameListPage getMatchingPackageNames(TagQueryExpression expression, Range range, int pageNumber) {
113 		if (range != null && !(range instanceof DaysRange)) {
114 			log.warn("TMACL-00830:Ignoring CompositeRange in JPA implementation. Such requests should get handled inside the Cache module.");
115 			return null;
116 		}
117 
118 		final TagQueryProvider provider = providerSelector.getSelectedProvider(em);
119 		final Query query = provider.getMatchingPackageNamesQuery(em, expression, (DaysRange) range);
120 
121 		return toNameListPage(query, pageNumber, pageSize);
122 	}
123 
124 	@Override
125 	public SharedNamedFileIdentifierListPage getFilesContainedInPackageById(FileIdentifier packageFile, int pageNumber) {
126 		final FileQueryConfigurator queryConfigurator = new FileQueryConfigurator<Object>(em, null, "Packages.SelectFilesContainedInPackage");
127 		Query query = applyPage(queryConfigurator.getUntypedQuery(packageFile), pageNumber, pageSize);
128 		return toListPage(query, namedFileIdentifierAssembler, new SharedNamedFileIdentifierListPage());
129 	}
130 
131 	@Override
132 	public SharedNamedFileIdentifierListPage getFilesContainedInPackageByName(String packageName, int pageNumber) {
133 		Query query = em.createNamedQuery("Packages.SelectFilesContainedInPackageByName").setParameter("name", packageName);
134 		applyPage(query, pageNumber, pageSize);
135 		return toListPage(query, namedFileIdentifierAssembler, new SharedNamedFileIdentifierListPage());
136 	}
137 
138 	/**
139 	 * Returns a map of contained files for the given package details.
140 	 * <p/>
141 	 * Note: It is adviceable to use the values of the map only to satisfy foreign key dependencies
142 	 * as all operations cause lazy loading of file or package detail resources.
143 	 *
144 	 * @param packageDetails the package details to lookup all contained files for.
145 	 * @return a map of contained files for the given package details.
146 	 */
147 	public Map<NamedFileIdentifier, JpaNamedFileIdentifier> getFilesContainedInPackage(JpaPackageDetails packageDetails) {
148 
149 		final List queryResults = em.createNamedQuery("Packages.SelectFilesContainedInPackageByPrimaryKey").
150 				setParameter("packageDetails", packageDetails).getResultList();
151 		final Map<NamedFileIdentifier, JpaNamedFileIdentifier> results = new HashMap<NamedFileIdentifier, JpaNamedFileIdentifier>(queryResults.size());
152 
153 		for (Object queryResult : queryResults) {
154 			Object[] cols = (Object[]) queryResult;
155 			results.put(namedFileIdentifierAssembler.call(cols), (JpaNamedFileIdentifier) cols[0]);
156 		}
157 
158 		return results;
159 	}
160 
161 	@Override
162 	public NameListPage getReferencingPackageNamesById(FileIdentifier file, int pageNumber) {
163 		final FileQueryConfigurator<String> queryConfigurator =
164 				new FileQueryConfigurator<String>(em, String.class, "Packages.SelectReferencingPackageNames");
165 		return toNameListPage(queryConfigurator.getQuery(file), pageNumber, pageSize);
166 	}
167 
168 	@Override
169 	public NameListPage getReferencingPackageNames(String packageName, int pageNumber) {
170 		final TypedQuery<String> query = em.createNamedQuery(
171 				"Packages.SelectReferencingPackageNamesByName", String.class).setParameter("name", packageName);
172 		return toNameListPage(query, pageNumber, pageSize);
173 	}
174 
175 	@Override
176 	public NameListPage getPackagesContainedInPackageById(FileIdentifier packageFile, int pageNumber) {
177 		final FileQueryConfigurator<String> queryConfigurator =
178 				new FileQueryConfigurator<String>(em, String.class, "Packages.SelectPackagesContainedInPackage");
179 		return toNameListPage(queryConfigurator.getQuery(packageFile), pageNumber, pageSize);
180 	}
181 
182 	@Override
183 	public NameListPage getPackagesContainedInPackageByName(String packageName, int pageNumber) {
184 		final TypedQuery<String> query = em.createNamedQuery(
185 				"Packages.SelectPackagesContainedInPackageByName", String.class).setParameter("name", packageName);
186 		return toNameListPage(query, pageNumber, pageSize);
187 	}
188 
189 	@Override
190 	@RemoteCacheable
191 	public Collection<SharedPackageInformation> getPackageInformationListById(Collection<FileIdentifier> files) {
192 		final FileQueryConfigurator queryConfigurator = new FileQueryConfigurator<Object>(em, null, "Packages.SelectPackageInformation");
193 
194 		final Collection<SharedPackageInformation> results = new ArrayList<SharedPackageInformation>(files.size());
195 		for (FileIdentifier file : files)
196 			doGetPackageInformationList(queryConfigurator.getUntypedQuery(file), results);
197 
198 		return results;
199 	}
200 
201 	@Override
202 	@RemoteCacheable
203 	public Collection<SharedPackageInformation> getPackageInformationListByName(Collection<String> packageNames) {
204 		final Query query = em.createNamedQuery("Packages.SelectPackageInformationByName");
205 
206 		final Collection<SharedPackageInformation> results = new ArrayList<SharedPackageInformation>(packageNames.size());
207 		for (String name : packageNames)
208 			doGetPackageInformationList(query.setParameter("name", name), results);
209 
210 		return results;
211 	}
212 
213 	@SuppressWarnings("unchecked")
214 	private static void doGetPackageInformationList(final Query query, final Collection<SharedPackageInformation> results) {
215 		List<Object[]> rows = query.getResultList();
216 		if (rows.isEmpty())
217 			results.add(null);
218 		else {
219 			Object[] row = rows.get(0);
220 			((JpaPackageInformation) row[0]).setPackageFileInformation((JpaFileInformation) row[1]);
221 			results.add((SharedPackageInformation) row[0]);
222 		}
223 	}
224 
225 	@Override
226 	@SuppressWarnings("unchecked")
227 	@RemoteCacheable(comment = "Note: Don't use this method for JPA purposes, " +
228 			"the returned entities will not be usable when returned from the remote cache.")
229 	public Collection<SharedPackageDetails> getPackageDetailsListById(Collection<FileIdentifier> files) {
230 		return (Collection) getJpaPackageDetailsListById(files);
231 	}
232 
233 	/**
234 	 * Returns the package details on the given package file ids.
235 	 *
236 	 * @param files The SHA1 hashes of the package files.
237 	 * @return the package details on the given package file ids.
238 	 */
239 	public Collection<JpaPackageDetails> getJpaPackageDetailsListById(Collection<FileIdentifier> files) {
240 		final FileQueryConfigurator<Integer> queryConfigurator =
241 				new FileQueryConfigurator<Integer>(em, Integer.class, "Packages.SelectPackageDetailsPrimaryKey");
242 
243 		final Collection<Integer> results = new ArrayList<Integer>(files.size());
244 		for (FileIdentifier file : files)
245 			appendSingleElementToList(results, queryConfigurator.getQuery(file));
246 
247 		return toDetails(results);
248 	}
249 
250 	@Override
251 	@SuppressWarnings("unchecked")
252 	@RemoteCacheable(comment = "Note: Don't use this method for JPA purposes, " +
253 			"the returned entities will not be usable when returned from the remote cache.")
254 	public Collection<SharedPackageDetails> getPackageDetailsListByName(Collection<String> packageNames) {
255 		return (Collection) getJpaPackageDetailsListByName(packageNames);
256 	}
257 
258 	/**
259 	 * Returns the package details on the given package names.
260 	 *
261 	 * @param packageNames The names the packages to lookup.
262 	 * @return the package details on the given package names.
263 	 */
264 	public Collection<JpaPackageDetails> getJpaPackageDetailsListByName(Collection<String> packageNames) {
265 		final TypedQuery<Integer> query = em.createNamedQuery("Packages.SelectPackageDetailsPrimaryKeyByName", Integer.class);
266 
267 		final Collection<Integer> results = new ArrayList<Integer>(packageNames.size());
268 		for (String name : packageNames)
269 			appendSingleElementToList(results, query.setParameter("name", name));
270 
271 		return toDetails(results);
272 	}
273 
274 	private Collection<JpaPackageDetails> toDetails(Collection<Integer> primaryKeys) {
275 		final Collection<JpaPackageDetails> results = new ArrayList<JpaPackageDetails>(primaryKeys.size());
276 		for (Integer pk : primaryKeys)
277 			results.add(pk == null ? null : em.find(JpaPackageDetails.class, pk));
278 		return results;
279 	}
280 
281 	@Override
282 	@RemoteCacheable(comment = "Note: Don't use this method for JPA purposes, " +
283 			"the returned entities will not be usable when returned from the remote cache.")
284 	public Collection<FileIdentifier> getPackageFileIdentifiersByName(Collection<String> packageNames) {
285 		TypedQuery<FileIdentifier> query = em.createNamedQuery("Packages.GetPackageFileIdentifierByName", FileIdentifier.class);
286 
287 		final Collection<FileIdentifier> results = new ArrayList<FileIdentifier>(packageNames.size());
288 		for (String name : packageNames)
289 			appendSingleElementToList(results, query.setParameter("name", name));
290 
291 		return results;
292 	}
293 
294 	/**
295 	 * Returns the primary key of the specified package or 'null' if it does not exist.
296 	 *
297 	 * @param packageName the name of the package to lookup.
298 	 * @return the primary key of the package or 'null' if it does not exist.
299 	 */
300 	public Integer getPackagePrimaryKeyByName(String packageName) {
301 		final boolean isRoot = JpaPackageInformation.ROOT_PACKAGE_NAME.equalsIgnoreCase(packageName);
302 		if (isRoot && rootPackageKey != null)
303 			return rootPackageKey;
304 
305 		final List<Integer> r = em.createNamedQuery("Packages.SelectPackageDetailsPrimaryKeyByName", Integer.class).
306 				setParameter("name", packageName).getResultList();
307 		final Integer result = r.isEmpty() ? null : r.get(0);
308 
309 		if (isRoot)
310 			rootPackageKey = result;
311 
312 		return result;
313 	}
314 }