1   package com.trendmicro.grid.acl.ds.jpa;
2   
3   import com.trendmicro.grid.acl.ds.jpa.entities.*;
4   import com.trendmicro.grid.acl.l0.datatypes.*;
5   import com.trendmicro.grid.acl.metadata.Meta;
6   import com.trendmicro.grid.acl.metadata.Metadata;
7   import org.slf4j.Logger;
8   import org.slf4j.LoggerFactory;
9   
10  import java.net.URI;
11  import java.util.ArrayList;
12  import java.util.Collection;
13  import java.util.Collections;
14  import java.util.Iterator;
15  
16  import static com.trendmicro.grid.acl.ds.jpa.ReceivedPreparedPackagesHandler.PreparedPackage;
17  
18  /**
19   * Manages updates to PACKAGES & HISTORY table.
20   *
21   * @author juergen_kellerer, 2010-11-22
22   * @version 1.0
23   */
24  public class ReceivedPreparedPackagesHandler extends AbstractReceivedFileContentsHandler<Collection<PreparedPackage>> {
25  
26  	private static final Logger log = LoggerFactory.getLogger(ReceivedPreparedPackagesHandler.class);
27  
28  	private final JpaPackageRepository packageRepository;
29  
30  	private final AbstractReceivedResultsHandler<Collection<JpaPackageFamily>> familyHandler;
31  
32  	/**
33  	 * Creates a new thread safe instance.
34  	 *
35  	 * @param fileRepository    the repository to use for file contents.
36  	 * @param packageRepository the repository to use for packages.
37  	 * @param familyHandler     the FamilyHandler to use.
38  	 */
39  	public ReceivedPreparedPackagesHandler(JpaFileRepository fileRepository, JpaPackageRepository packageRepository,
40  	                                       AbstractReceivedResultsHandler<Collection<JpaPackageFamily>> familyHandler) {
41  		super(fileRepository);
42  		this.familyHandler = familyHandler;
43  		this.packageRepository = packageRepository;
44  	}
45  
46  	/**
47  	 * {@inheritDoc}
48  	 */
49  	@Override
50  	protected Collection<PreparedPackage> handle(StorageContext context) {
51  		final Iterator<JpaPackageFamily> familyRefs = familyHandler.handle(context).iterator();
52  
53  		Collection<PreparedPackage> preparedPackages = new ArrayList<PreparedPackage>(context.receivedDataSets.size());
54  		for (ProcessPackageDataSet dataSet : context.receivedDataSets) {
55  			// We must flush here to ensure history revisions are updated before queried.
56  			context.em.flush();
57  			preparedPackages.add(preparedPackage(context, familyRefs.next(), dataSet));
58  		}
59  
60  		return preparedPackages;
61  	}
62  
63  	/**
64  	 * Manages updates to PACKAGES & HISTORY table.
65  	 *
66  	 * @param context         the context for the current transaction.
67  	 * @param familyReference the reference to the package family.
68  	 * @param dataSet         the element describing the package.
69  	 * @return A PreparedPackage instance or 'null' if no further operation should be performed on the package.
70  	 */
71  	PreparedPackage preparedPackage(final StorageContext context,
72  	                                final JpaPackageFamily familyReference,
73  	                                final ProcessPackageDataSet dataSet) {
74  
75  		final PackageDetails packageDetails = dataSet.getProcessedPackage();
76  		final JpaFileDetails fileDetailsReference = createOrUpdateFileReference(context, packageDetails);
77  
78  		final PackageInformation information = packageDetails.getPackageInformation();
79  		final String packageName = information.getName();
80  
81  		JpaPackageDetails existingDetails = packageRepository.getJpaPackageDetailsListByName(
82  				Collections.singleton(packageName)).iterator().next();
83  		final boolean isNewPackage = existingDetails == null;
84  
85  		// Handle updates to PACKAGES and PACKAGE_HISTORY
86  		if (isNewPackage) {
87  			final String[] tags = information.getTags().toArray(new String[information.getTags().size()]);
88  
89  			JpaPackageInformation jpaInformation = new JpaPackageInformation(
90  					information.getName(), information.getDisplayName(),
91  					information.getFamilyName(), information.getVendorName(),
92  					tags, fileDetailsReference.getInformation());
93  
94  			existingDetails = new JpaPackageDetails(familyReference, jpaInformation,
95  					packageDetails.getMetadata(), fileDetailsReference);
96  
97  			if (log.isTraceEnabled()) {
98  				log.trace("Storing new package '{}', tagged as '{}'",
99  						information.getName(), existingDetails.getPackageInformation().getTags());
100 			}
101 
102 			context.em.persist(existingDetails);
103 
104 		} else {
105 			if (context.ignoreAllUpdates) {
106 				if (log.isTraceEnabled()) {
107 					log.trace("Not further processing package '{}', " +
108 							"updates are generally disabled.", information.getName());
109 				}
110 				return null;
111 			}
112 
113 			// Make sure we have the revision aligned with the session before we apply further updates.
114 			if (context.enableHistoryRecording) existingDetails.alignRevisionWithSession(context.em);
115 
116 			// TODO: What to do when the "existingFileDetails#primaryKey" changed.
117 			// TODO: (Handle at least the history updates in the next version of ACL!)
118 			if (existingDetails.getPackageFileDetails().getPrimaryKey() != fileDetailsReference.getPrimaryKey()) {
119 				log.warn("TMACL-01840:The previously stored package named '{}' was reprocessed with a different " +
120 						"package file than before identifier was {}, but is now {}. The change is applied but the " +
121 						"current ACL version doesn't support to record this in the history table.", new Object[]{
122 						packageName, existingDetails.getPackageFileDetails().getIdentifier(),
123 						fileDetailsReference.getIdentifier()});
124 			}
125 
126 			// Setting the new or updated file details reference to ensure we have update internal
127 			// data structures inside the package details.
128 			existingDetails.setPackageFileDetails(fileDetailsReference);
129 			if(!familyReference.equals(existingDetails.getPackageFamily()))
130 				existingDetails.setPackageFamily(familyReference);
131 		}
132 
133 		return new PreparedPackage(context, packageDetails, existingDetails,
134 				fileDetailsReference, extractPackageRemoteFilename(dataSet), isNewPackage);
135 	}
136 
137 
138 	private JpaFileDetails createOrUpdateFileReference(final StorageContext context, final PackageDetails details) {
139 		return createOrUpdateFileReference(context, new FileDetails(
140 				details.getFileMetadata().getIdentifier(),
141 				details.getPackageInformation().getPackageFileInformation(),
142 				details.getFileMetadata().getMetadata()), true
143 		);
144 	}
145 
146 
147 	private String extractPackageRemoteFilename(ProcessPackageDataSet dataSet) {
148 		// Guesses the filename as it was used on the remote side by evaluating the sources.
149 		String fileName = "";
150 		Collection<Source> sources = dataSet.getProcessSources();
151 		if (sources != null && !sources.isEmpty()) {
152 			Source mainSource = sources.iterator().next();
153 			URI remoteURI = mainSource.getRemoteURI();
154 			if (remoteURI != null) {
155 				fileName = remoteURI.getPath() == null ? "" : remoteURI.getPath();
156 				fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
157 			}
158 
159 			final Metadata sourceMetadata = mainSource.getMetadata();
160 			if (sourceMetadata != null)
161 				for (String key : StorageOptions.META_FILENAME_KEYS) {
162 					Meta m = sourceMetadata.get(key);
163 					if (m != null) {
164 						String fn = m.getValue();
165 						if (fn != null && fn.indexOf('.') != -1)
166 							fileName = fn;
167 					}
168 				}
169 		}
170 		return fileName;
171 	}
172 
173 
174 	/**
175 	 * Defines a prepared package that may be used for further updates.
176 	 */
177 	public static final class PreparedPackage {
178 
179 		private final StorageContext context;
180 		private final PackageDetails updatedDetails;
181 		private final JpaPackageDetails reference;
182 		private final JpaFileDetails fileReference;
183 		private final String remoteFileName;
184 		private final boolean isNew;
185 
186 		private PreparedPackage(StorageContext context, PackageDetails updatedDetails, JpaPackageDetails reference,
187 		                        JpaFileDetails fileReference, String remoteFileName, boolean isNew) {
188 			this.context = context;
189 			this.updatedDetails = updatedDetails;
190 			this.reference = reference;
191 			this.fileReference = fileReference;
192 			this.remoteFileName = remoteFileName;
193 
194 			this.isNew = isNew;
195 		}
196 
197 		/**
198 		 * Returns the package reference to link other resources.
199 		 *
200 		 * @return the package reference to link other resources.
201 		 */
202 		public JpaPackageDetails getReference() {
203 			return reference;
204 		}
205 
206 		/**
207 		 * Returns the reference to the package file.
208 		 *
209 		 * @return the reference to the package file.
210 		 */
211 		public JpaFileDetails getFileReference() {
212 			return fileReference;
213 		}
214 
215 		/**
216 		 * Returns the recorded public filename of the assigned file for this package.
217 		 *
218 		 * @return the recorded public filename of the assigned file for this package.
219 		 */
220 		public String getRemoteFileName() {
221 			return remoteFileName;
222 		}
223 
224 		/**
225 		 * Performs a final package update and optionally forces the increment of the revision.
226 		 * <p/>
227 		 * Note: This method must be called on every PreparedPackage instance before leaving
228 		 * the transaction scope.
229 		 *
230 		 * @param incrementRevision forces the increment of the revision when set to true.
231 		 */
232 		public void applyFinalPackageUpdates(boolean incrementRevision) {
233 			final PackageInformation information = updatedDetails.getPackageInformation();
234 			final Metadata metadata = updatedDetails.getMetadata();
235 			final boolean ignoreMetadata = context.ignoreNullMetadataUpdates && isNull(metadata);
236 
237 			if (!isNew && JpaPackageHistory.wasChanged(reference, updatedDetails, ignoreMetadata)) {
238 				if (log.isTraceEnabled())
239 					log.trace("Updating package-content to: {}", updatedDetails);
240 
241 				JpaPackageHistory history = new JpaPackageHistory(reference);
242 
243 				//If disabled, recording of data in the database are ignored. Default is enabled
244 				if (context.enableHistoryRecording) {
245 					if (log.isTraceEnabled())
246 						log.trace("Storing new package-content history entry: {}", history);
247 					context.em.persist(history);
248 				} else {
249 					if (log.isTraceEnabled())
250 						log.trace("enableHistoryRecording is set to {}. Disregarding package-content history entry: {}", context.enableHistoryRecording, history);
251 				}
252 
253 				final JpaPackageInformation existingInformation = reference.getPackageInformation();
254 				reference.setMetadata(metadata);
255 				existingInformation.setDisplayName(information.getDisplayName());
256 				existingInformation.setTags(information.getTags());
257 
258 			} else if (incrementRevision) {
259 				reference.setRevision(reference.getRevision() + 1);
260 
261 				if (log.isTraceEnabled()) {
262 					log.trace("Incremented package revision inside '{}' " +
263 							"after a change in the file assignments.", reference);
264 				}
265 			}
266 		}
267 	}
268 }