001: package com.pentaho.repository.dbbased.solution;
002:
003: import java.io.File;
004: import java.io.IOException;
005: import java.util.ArrayList;
006: import java.util.HashMap;
007: import java.util.Iterator;
008: import java.util.List;
009: import java.util.Map;
010: import java.util.regex.Pattern;
011: import org.apache.commons.logging.Log;
012: import org.apache.commons.logging.LogFactory;
013: import org.hibernate.Criteria;
014: import org.hibernate.FetchMode;
015: import org.hibernate.criterion.Restrictions;
016: import org.pentaho.core.solution.ISolutionFile;
017: import org.pentaho.messages.Messages;
018: import org.pentaho.repository.HibernateUtil;
019: import org.pentaho.util.FileHelper;
020:
021: /**
022: * This class is used for handling all the bits and twiddles of updating the RDBMS Solution Repository. All state during update is handled here.
023: *
024: * @author mbatchel
025: */
026: public class RepositoryUpdateHelper {
027:
028: protected static final Log logger = LogFactory
029: .getLog(RepositoryUpdateHelper.class);
030:
031: String fromBase; // The directory name of the solution
032:
033: String toBase; // The name of the root folder in the RDBMS
034:
035: // Holds from->to name replacements
036: //
037: // E.g. From: c:\workspace\pentaho\solutions\samples\myfile.xaction
038: // To: /solutions/samples/myfile.xaction
039: Map nameReplacementMap = new HashMap();
040:
041: Map reposFileStructure; // Passed in contains the RDBMS files and folders with last modified dates
042:
043: Map createdOrRetrievedFolders = new HashMap(); // As the name states
044:
045: List updatedFiles = new ArrayList();
046:
047: List newFolders = new ArrayList();
048:
049: List newFiles = new ArrayList();
050:
051: List updatedFolders = new ArrayList();
052:
053: SolutionRepository dbBasedRepository;
054:
055: private static final Pattern SlashPattern = Pattern.compile("\\\\"); //$NON-NLS-1$
056:
057: protected RepositoryUpdateHelper(String fromBase, String toBase,
058: Map reposFileStructure, SolutionRepository inRepository) {
059: this .fromBase = fromBase;
060: this .toBase = toBase;
061: this .reposFileStructure = reposFileStructure;
062: dbBasedRepository = inRepository;
063: }
064:
065: /**
066: * Converts the name from a DOS/Windows/Unix canonical name into the name in the RDBMS repository For example: From: c:\workspace\pentaho\solutions\samples\myfile.xaction To: /solutions/samples/myfile.xaction
067: *
068: * @param fName
069: * Canonical file name
070: * @return Fixed file name within the RDBMS repository
071: */
072: protected String convertFileName(String fName) {
073: String rtn = (String) nameReplacementMap.get(fName); // Check to see if I've already done this
074: if (rtn == null) {
075: // Need to do the conversion
076: rtn = toBase
077: + SlashPattern.matcher(
078: fName.substring(fromBase.length()))
079: .replaceAll("/"); //$NON-NLS-1$
080: nameReplacementMap.put(fName, rtn);
081: }
082: return rtn;
083: }
084:
085: /**
086: * Process additions (files and folders) by adding them to the repository tree
087: *
088: * @throws IOException
089: */
090: protected void processAdditions() throws IOException {
091: //
092: // Process New Folders
093: //
094: for (int i = 0; i < newFolders.size(); i++) {
095: File newFolder = (File) newFolders.get(i);
096: RepositoryFile newFolderObject = createFolder(newFolder);
097: logger
098: .info(Messages
099: .getString(
100: "SolutionRepository.INFO_0004_ADDED_FOLDER", newFolderObject.getFullPath())); //$NON-NLS-1$
101: }
102: //
103: // Process New Files
104: //
105: for (int i = 0; i < newFiles.size(); i++) {
106: File newFile = (File) newFiles.get(i);
107: RepositoryFile newFileObject = createNewFile(newFile);
108: logger
109: .info(Messages
110: .getString(
111: "SolutionRepository.INFO_0006_ADDED_FILE", newFileObject.getFullPath())); //$NON-NLS-1$
112: }
113: }
114:
115: /**
116: * Process updates (files and folders) by updating their time and/or updating contents
117: *
118: * @throws IOException
119: */
120: protected void processUpdates() throws IOException {
121: //
122: // Process Updated Files
123: //
124: for (int i = 0; i < updatedFiles.size(); i++) {
125: File updatedFile = (File) updatedFiles.get(i);
126: String updRepoFileName = convertFileName(updatedFile
127: .getCanonicalPath());
128: RepositoryFile updRepoFileObject = (RepositoryFile) dbBasedRepository
129: .getFileByPath(updRepoFileName); // Hibernate Query
130: byte[] data = FileHelper.getBytesFromFile(updatedFile);
131: updRepoFileObject.setLastModified(updatedFile
132: .lastModified());
133: updRepoFileObject.setData(data);
134: logger
135: .info(Messages
136: .getString(
137: "SolutionRepository.INFO_0007_UPDATED_FILE", updRepoFileObject.getFullPath())); //$NON-NLS-1$
138: }
139: //
140: // Process Updated Folders
141: //
142: RepositoryFile updFolderObject = null;
143: for (int i = 0; i < updatedFolders.size(); i++) {
144: File updatedFolder = (File) updatedFolders.get(i);
145: String folderNameCorrected = this
146: .convertFileName(updatedFolder.getCanonicalPath());
147: // Check for it to already be there...
148: updFolderObject = (RepositoryFile) createdOrRetrievedFolders
149: .get(folderNameCorrected);
150: if (updFolderObject == null) {
151: updFolderObject = (RepositoryFile) dbBasedRepository
152: .getFileByPath(folderNameCorrected); // Hibernate Query
153: createdOrRetrievedFolders.put(folderNameCorrected,
154: updFolderObject); // Put it here so we can use it later if needed
155: }
156: updFolderObject.setLastModified(updatedFolder
157: .lastModified()); // Update the date/time stamp
158: logger
159: .info(Messages
160: .getString(
161: "SolutionRepository.INFO_0002_UPDATED_FOLDER", folderNameCorrected)); //$NON-NLS-1$
162: }
163: }
164:
165: /**
166: * Processes deletions by looking at the InfoHolder object for items that weren't touched during traversal of the file system.
167: *
168: * @param deleteOrphans
169: * Whether to actually delete the items from Hibernate
170: * @return
171: */
172: protected List processDeletions(boolean deleteOrphans) {
173: // Return (and optionally process) deletions
174: List deletions = new ArrayList();
175: Iterator it = reposFileStructure.entrySet().iterator();
176: while (it.hasNext()) {
177: Map.Entry me = (Map.Entry) it.next();
178: InfoHolder info = (InfoHolder) me.getValue();
179: if (!info.touched) {
180: deletions.add(me.getKey());
181: }
182: }
183: if (deleteOrphans) {
184: performHibernateDelete(deletions);
185: }
186: return deletions;
187: }
188:
189: /**
190: * Actually deletes the RepositoryFile objects from Hibernate
191: *
192: * @param deletions
193: * List of files deleted
194: */
195: protected void performHibernateDelete(List deletions) {
196:
197: // TODO: This should really be handled with a subQuery rather than
198: // an in clause... Oracle in clauses are notoriously bad performers.
199: // Not changing the implementation now as we are releasing 1.6GA soon.
200:
201: List listOfDeletions = new ArrayList();
202: Criteria criteria = null;
203:
204: if (deletions != null && deletions.size() > 0) {
205:
206: if (HibernateUtil.isOracleDialect()) {
207: for (int i = 0; i < deletions.size(); i += 500) {
208: // Oracle sets a limit on the number of items contained in
209: // the "in" clause on a SQL statement (< 1000). So we are chunking
210: // deletions by 500, in case we run into the case where we exceed the Oracle
211: // limit.
212: // See the following threads for details:
213: // http://www.dbforums.com/showthread.php?t=369013
214: // http://jira.pentaho.org:8080/browse/BISERVER-372
215: int maxChunkIndex = (i + 500) > deletions.size() ? deletions
216: .size()
217: : (i + 500);
218: List chunkDeletions = deletions.subList(i,
219: maxChunkIndex);
220: criteria = HibernateUtil.getSession()
221: .createCriteria(RepositoryFile.class);
222: criteria.add(Restrictions.in(
223: "fullPath", chunkDeletions)); // Get all objects to be deleted. //$NON-NLS-1$
224: listOfDeletions.add(criteria);
225: }
226: } else { // all other dialects
227: criteria = HibernateUtil.getSession().createCriteria(
228: RepositoryFile.class);
229: criteria.add(Restrictions.in("fullPath", deletions)); // Get all objects to be deleted. //$NON-NLS-1$
230: // Due to a bug in the Oracle JDBC driver, we must disable outer join fetching in
231: // Hibernate in order for deletions to execute successfully.
232: // See the following threads for details:
233: // http://forum.hibernate.org/viewtopic.php?t=82
234: // http://forum.hibernate.org/viewtopic.php?t=930650
235: // http://jira.pentaho.org/browse/BISERVER-232
236: criteria.setFetchMode("parent", FetchMode.JOIN); //$NON-NLS-1$
237: listOfDeletions.add(criteria);
238: }
239:
240: for (Iterator iter = listOfDeletions.iterator(); iter
241: .hasNext();) {
242: Criteria element = (Criteria) iter.next();
243: List deleteResult = element.list(); // Should return all things to be deleted.
244: for (int i = 0; i < deleteResult.size(); i++) {
245: RepositoryFile toBeDeleted = (RepositoryFile) deleteResult
246: .get(i);
247: RepositoryFile deletedParent = (RepositoryFile) toBeDeleted
248: .retrieveParent();
249: if (deletedParent != null) {
250: deletedParent.removeChildFile(toBeDeleted);
251: }
252: HibernateUtil.makeTransient(toBeDeleted);
253: }
254: }
255: }
256: }
257:
258: /**
259: * Determines whether the folder already exists, or needs to be added.
260: *
261: * @param aFile
262: * The File object pointing to the folder on the drive
263: * @throws IOException
264: */
265: protected void recordFolder(File aFile) throws IOException {
266: String fixedFileName = convertFileName(aFile.getCanonicalPath());
267: InfoHolder infoHolder = (InfoHolder) reposFileStructure
268: .get(fixedFileName);
269: if (infoHolder != null) {
270: infoHolder.touched = true;
271: if (aFile.lastModified() != infoHolder.lastModifiedDate) {
272: updatedFolders.add(aFile);
273: }
274: } else {
275: newFolders.add(aFile);
276: }
277: }
278:
279: /**
280: * Determines whether a file has been updated or was added.
281: *
282: * @param f
283: * File object pointing to the file on the hard drive
284: * @return true if the file has been changed.
285: * @throws IOException
286: */
287: protected boolean recordFile(File f) throws IOException {
288: boolean changed = false;
289: // First, convert the file - this code will move soon
290: String fName = f.getCanonicalPath();
291: String convertedSolnFileName = convertFileName(fName);
292: long lastRDBMSModDate = getLastModifiedDateFromMap(convertedSolnFileName);
293: if (lastRDBMSModDate > 0) {
294: // File is in RDBMS. Check the mode date
295: if (f.lastModified() != lastRDBMSModDate) {
296: updatedFiles.add(f);
297: changed = true;
298: }
299: } else {
300: // This file is brand-spankin' new
301: newFiles.add(f);
302: }
303: return changed;
304: }
305:
306: /**
307: * Retrieves the last modified date from the map returned from Hibernate. Also touches the object in the map to indicate it was traversed during the filesystem crawl
308: *
309: * @param fileName
310: * The name of the file to lookup in the map
311: * @return null if the file isn't already in the RDBMS, or the last modified date/time of the file
312: */
313: protected long getLastModifiedDateFromMap(String fileName) {
314: InfoHolder info = (InfoHolder) reposFileStructure.get(fileName);
315: if (info != null) {
316: info.touched = true;
317: return info.lastModifiedDate;
318: }
319: return -1;
320: }
321:
322: /**
323: * Gets the parent folder for the file/folder. May result in a Hibernate query if the folder wasn't one that was created during the process. There shouldn't be a way for the folder to not exist. Either it was previously created during this update cycle (in which case it'll already be in the createdOrRetrievedFolders map) or it already existed in the RDBMS Repo in which case it will be retrieved and put in the map.
324: *
325: * @param parentName
326: * The solution path to the parent.
327: * @return RepositoryFile The parent object
328: */
329: protected ISolutionFile getParent(String parentName) {
330: // Check the map first
331: ISolutionFile theParent = (RepositoryFile) createdOrRetrievedFolders
332: .get(parentName);
333: if (theParent == null) {
334: // It's not there - need to get it from the RDBMS
335: theParent = dbBasedRepository.getFileByPath(parentName); // Hibernate Query
336: createdOrRetrievedFolders.put(parentName, theParent);
337: }
338: return theParent;
339: }
340:
341: /**
342: * This method creates a new folder in the RDBMS Repository. This means finding the correct parent (a Hibernate Query may have to be executed to get the parent).
343: *
344: * @param newFolder
345: * The File that points to the new folder to create
346: * @return The RepositoryFile object created
347: * @throws IOException
348: */
349: protected RepositoryFile createFolder(File newFolder)
350: throws IOException {
351: // Determine the corrected file name of the new folder
352: String fixedFolderName = convertFileName(newFolder
353: .getCanonicalPath());
354: // Get the file's parent folder
355: File parentFolder = newFolder.getParentFile();
356: // Get the corrected file name of the parent folder
357: String fixedParentFolderName = convertFileName(parentFolder
358: .getCanonicalPath());
359: // Get the Parent Folder either from our map or from Hibernate if necessary
360: RepositoryFile parentFolderObject = (RepositoryFile) getParent(fixedParentFolderName);
361: // Now, we have the parent in hand, we can create the RepositoryFile object
362: RepositoryFile newFolderObject = new RepositoryFile(newFolder
363: .getName(), parentFolderObject, null, newFolder
364: .lastModified());
365: createdOrRetrievedFolders.put(fixedFolderName, newFolderObject); // Add to map for later potential use
366: return newFolderObject;
367: }
368:
369: /**
370: * Creates a new RepositoryFile object from the File on the hard drive
371: *
372: * @param newFile
373: * The File object pointing to the file on the hard drive
374: * @return RepositoryFile object created
375: * @throws IOException
376: */
377: protected RepositoryFile createNewFile(File newFile)
378: throws IOException {
379: File parentFolder = newFile.getParentFile(); // Gets the parent folder of the File object
380: String fixedParentFolderName = convertFileName(parentFolder
381: .getCanonicalPath()); // Get the parent RepositoryFile object
382: RepositoryFile parentFolderObject = (RepositoryFile) getParent(fixedParentFolderName); // Fix up the name for the solution repository
383: // Create the new Object
384: RepositoryFile newFileObject = new RepositoryFile(newFile
385: .getName(), parentFolderObject, FileHelper
386: .getBytesFromFile(newFile), newFile.lastModified());
387: return newFileObject;
388: }
389: /*
390: * private void deleteFilesFromSolutionTree(List deleteList) { if (deleteList != null) { Iterator iter = deleteList.iterator(); while (iter.hasNext()) { RepositoryFile file = (RepositoryFile) iter.next(); RepositoryFile parent = file.getParent(); if (parent != null) { // this take care of the case of deleting the // repository completely parent.removeChildFile(file); } } } }
391: */
392: }
393:
394: /** *************************** END New Update DB Repository Methods ******************************* */
|