1   package com.trendmicro.grid.acl.ds.jpa;
2   
3   import com.trendmicro.grid.acl.ds.jpa.entities.JpaFileDetails;
4   import com.trendmicro.grid.acl.ds.jpa.entities.JpaFileHistory;
5   import com.trendmicro.grid.acl.ds.jpa.entities.JpaFileInformation;
6   import com.trendmicro.grid.acl.ds.jpa.util.FileDetailsMap;
7   import com.trendmicro.grid.acl.l0.datatypes.FileDetails;
8   import com.trendmicro.grid.acl.l0.datatypes.FileIdentifier;
9   import com.trendmicro.grid.acl.l0.datatypes.FileInformation;
10  import com.trendmicro.grid.acl.l0.datatypes.PackageMember;
11  import net.sf.tinyjee.config.PropertySection;
12  import org.slf4j.Logger;
13  import org.slf4j.LoggerFactory;
14  
15  import javax.persistence.Query;
16  import java.util.ArrayList;
17  import java.util.Collection;
18  import java.util.List;
19  
20  import static com.trendmicro.grid.acl.ds.config.DataSourcePropertySection.KEY_UPDATE_TIMESTAMPS_WHEN_CONTENT_IS_SAME;
21  import static com.trendmicro.grid.acl.ds.config.DataSourcePropertySection.getPropertySection;
22  
23  /**
24   * Is the base for handlers that deal with JpaFileDetails.
25   *
26   * @author juergen_kellerer, 2010-11-22
27   * @version 1.0
28   */
29  public abstract class AbstractReceivedFileContentsHandler<T> extends AbstractReceivedResultsHandler<T> {
30  
31  	private static final Logger log = LoggerFactory.getLogger(AbstractReceivedFileContentsHandler.class);
32  
33  	private final JpaFileRepository fileRepository;
34  
35  	private final boolean presetAlwaysUpdateTimestamps;
36  
37  	/**
38  	 * Creates a new instance.
39  	 *
40  	 * @param fileRepository the repository to use for file contents.
41  	 */
42  	protected AbstractReceivedFileContentsHandler(JpaFileRepository fileRepository) {
43  		PropertySection section = getPropertySection();
44  
45  		this.fileRepository = fileRepository;
46  		presetAlwaysUpdateTimestamps = getValue(section, KEY_UPDATE_TIMESTAMPS_WHEN_CONTENT_IS_SAME);
47  	}
48  
49  	/**
50  	 * Creates or updates the file references for the given list of package members.
51  	 *
52  	 * @param context the context for the current transaction.
53  	 * @param members the members to convert to file references.
54  	 * @return a list of file content references (to be used as FILE_CONTENT_IDs).
55  	 */
56  	protected Collection<JpaFileDetails> createOrUpdateFileReferences(final StorageContext context,
57  	                                                                  final Collection<PackageMember> members) {
58  
59  		final List<Object[]> timeUpdates = new ArrayList<Object[]>(members.size());
60  		final FileDetailsMap<JpaFileDetails> existingDetailsMap = new FileDetailsMap<JpaFileDetails>(members.size());
61  		final Collection<JpaFileDetails> fileRefs = new ArrayList<JpaFileDetails>(members.size());
62  
63  		// Selecting existing refs in before dealing with updates to get less fragmentation (= better batching results)
64  		final List<FileIdentifier> ids = new ArrayList<FileIdentifier>(members.size());
65  		for (PackageMember member : members)
66  			ids.add(member.getIdentifier());
67  		for (JpaFileDetails fileDetails : fileRepository.getJpaFileDetailsList(ids))
68  			if (fileDetails != null)
69  				existingDetailsMap.put(fileDetails.getIdentifier(), fileDetails);
70  
71  		for (PackageMember member : members) {
72  			FileDetails details = member.getDetails();
73  			if (details == null)
74  				details = new FileDetails(member.getIdentifier(), member.getInformation(), null);
75  
76  			fileRefs.add(doCreateOrUpdateFileReference(context, details, existingDetailsMap, timeUpdates, false));
77  		}
78  
79  		// Handle time updates separately at the end (= better batching results).
80  		handleTimeUpdates(context, timeUpdates);
81  
82  		return fileRefs;
83  	}
84  
85  	/**
86  	 * Creates or updates the file reference for the given source file details instance.
87  	 *
88  	 * @param context          the context for the current transaction.
89  	 * @param details          the source details to create or update.
90  	 * @param forceTimeUpdates Forces updating timestamps even if file details did not contain changed content and
91  	 *                         alwaysUpdateTimestamps is set to false.
92  	 * @return a list of file content references (to be used as FILE_CONTENT_IDs).
93  	 */
94  	protected JpaFileDetails createOrUpdateFileReference(final StorageContext context, final FileDetails details, boolean forceTimeUpdates) {
95  		final List<Object[]> timeUpdates = new ArrayList<Object[]>(1);
96  		final FileDetailsMap<JpaFileDetails> existingDetailsMap = new FileDetailsMap<JpaFileDetails>(1);
97  		existingDetailsMap.put(details.getIdentifier(), fileRepository.getFileDetails(details.getIdentifier()));
98  
99  		JpaFileDetails fileDetails = doCreateOrUpdateFileReference(context, details, existingDetailsMap, timeUpdates, forceTimeUpdates);
100 
101 		// Handle time updates separately at the end (= better batching results).
102 		handleTimeUpdates(context, timeUpdates);
103 
104 		return fileDetails;
105 	}
106 
107 
108 	private JpaFileDetails doCreateOrUpdateFileReference(final StorageContext context,
109 	                                                     final FileDetails details,
110 	                                                     final FileDetailsMap<JpaFileDetails> existingDetailsMap,
111 	                                                     List<Object[]> timeUpdates, boolean forceTimeUpdates) {
112 
113 		boolean needsTimeUpdate = false;
114 		JpaFileDetails existingDetails = existingDetailsMap.get(details.getIdentifier());
115 
116 		if (existingDetails == null) {
117 			if (log.isTraceEnabled())
118 				log.trace("Creating new file-content entry: {}", details);
119 			existingDetails = fileRepository.createFileDetails(details);
120 			existingDetailsMap.put(details.getIdentifier(), existingDetails);
121 
122 		} else if (!context.ignoreAllUpdates) {
123 
124 			boolean ignoreMetadata = context.ignoreNullMetadataUpdates && isNull(details.getMetadata());
125 			if (JpaFileHistory.wasChanged(existingDetails, details, ignoreMetadata)) {
126 				if (log.isTraceEnabled())
127 					log.trace("Updating file-content to: {}", details);
128 
129 				// Make sure we have the revision aligned with the session before we apply further updates.
130 				if (context.enableHistoryRecording) existingDetails.alignRevisionWithSession(context.em);
131 
132 				// Clear the unknown flag if it has previously been set.
133 				if (existingDetails.getInformation().isUnknown()) {
134 					log.info("TMACL-01850:Received processing result on file {} (tagged with {}) that was " +
135 							"previously collected through reports and marked as 'unknown'.", details.getIdentifier(),
136 							details.getInformation().getTags());
137 					existingDetails.getInformation().setUnknown(false);
138 				}
139 
140 				// Handle the history update
141 				JpaFileHistory history = new JpaFileHistory(existingDetails);
142 
143 				//If disabled, recording of data in the database are ignored. Default is enabled
144 				if (context.enableHistoryRecording) {
145 					if (log.isTraceEnabled())
146 						log.trace("Storing new file-content history entry: {}", history);
147 					context.em.persist(history);
148 				} else {
149 					if (log.isTraceEnabled())
150 						log.trace("enableHistoryRecording is set to {}. Disregarding file-content history entry: {}", context.enableHistoryRecording, history);
151 				}
152 
153 				existingDetails.setMetadata(details.getMetadata());
154 
155 				final JpaFileInformation info = existingDetails.getInformation();
156 				info.setTags(details.getInformation().getTags());
157 				info.setLastRetrieved(details.getInformation().getLastRetrieved());
158 				info.setLastProcessed(details.getInformation().getLastProcessed());
159 			} else
160 				needsTimeUpdate = true;
161 		}
162 
163 		if (needsTimeUpdate) {
164 			final JpaFileInformation existingInfo = existingDetails.getInformation();
165 			final FileInformation info = details.getInformation();
166 			final boolean alwaysUpdateTimestamps = !context.ignoreAllUpdates && (presetAlwaysUpdateTimestamps || forceTimeUpdates);
167 
168 			needsTimeUpdate = alwaysUpdateTimestamps ? (
169 					isAfter(existingInfo.getLastProcessed(), info.getLastProcessed()) ||
170 							isAfter(existingInfo.getLastRetrieved(), info.getLastRetrieved())
171 			) : existingInfo.getLastProcessed() == null || existingInfo.getLastRetrieved() == null;
172 
173 			if (needsTimeUpdate) {
174 				timeUpdates.add(new Object[]{
175 						details.getInformation().getLastRetrieved(),
176 						details.getInformation().getLastProcessed(),
177 						existingDetails.getPrimaryKey()});
178 			}
179 		}
180 
181 		return existingDetails;
182 	}
183 
184 
185 	private void handleTimeUpdates(final StorageContext context, List<Object[]> timeUpdates) {
186 		if (timeUpdates == null || timeUpdates.isEmpty() || context.ignoreAllUpdates)
187 			return;
188 
189 		final Query updateQuery = context.em.createNamedQuery("FileContents.UpdateModificationDates");
190 		for (Object[] timeUpdate : timeUpdates) {
191 			updateQuery.setParameter("lastRetrieved", timeUpdate[0]).
192 					setParameter("lastProcessed", timeUpdate[1]).
193 					setParameter("fileId", timeUpdate[2]).executeUpdate();
194 		}
195 	}
196 }