Source code: com/flexstor/flexdbserver/services/linkupdate/LinkUpdateService.java
1 /*
2 * LinkUpdateService.java
3 *
4 * Copyright $Date: 2003/08/11 02:22:38 $ FLEXSTOR.net Inc.
5 *
6 * This work is licensed for use and distribution under license terms found at
7 * http://www.flexstor.org/license.html
8 *
9 */
10
11 package com.flexstor.flexdbserver.services.linkupdate;
12
13 import java.util.ArrayList;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Set;
18 import java.util.StringTokenizer;
19 import java.util.Vector;
20
21 import com.flexstor.common.constants.ActionPropertiesI;
22 import com.flexstor.common.constants.ServicesI;
23 import com.flexstor.common.data.ActionData;
24 import com.flexstor.common.data.ActionResult;
25 import com.flexstor.common.data.AssetRecordData;
26 import com.flexstor.common.data.ejb.disguiserecord.DisguiseAssetRecordData;
27 import com.flexstor.common.data.ejb.disguiserecord.DisguiseElementRecordData;
28 import com.flexstor.common.data.ejb.disguiserecord.DisguiseRecordData;
29 import com.flexstor.common.errorlogger.FlexError;
30 import com.flexstor.common.exceptions.InvalidDataException;
31 import com.flexstor.common.gateway.exceptions.TransactionFailedException;
32 import com.flexstor.common.importprocessor.ImportData;
33 import com.flexstor.common.importprocessor.ImportResult;
34 import com.flexstor.common.resources.Resources;
35 import com.flexstor.common.services.ServiceArgumentsI;
36 import com.flexstor.common.services.SrvcNotAvailException;
37 import com.flexstor.common.util.DirectDotHot;
38 import com.flexstor.ejb.disguise.persist.ServerDisguiseExtendData;
39 import com.flexstor.flexdbserver.disguise.DisguiseLoader;
40 import com.flexstor.flexdbserver.disguise.DisguiseLoaderException;
41 import com.flexstor.flexdbserver.services.Service;
42 import com.flexstor.flexdbserver.services.ServiceContext;
43
44 /**
45 * <P>
46 * LinkUpdateService <BR>
47 * <BLOCKQUOTE>
48 * The Link Update Service run during the import of new assets into FLEXSTOR.db containing links
49 * to other assets in the database. The service is intended to be a generic service that can be
50 * used in any import or output process. <BR>
51 * This service provides the following functions: <BR>
52 * <P>
53 * 1) Checks to see if the files contained in the import data object are already in the system. <BR>
54 * Checking is done by filename only. Check is done for assets already cataloged. <BR>
55 * 2) Updates ImportData to reflect the results of the operation above: creates new elements <BR>
56 * for new assets, if required, and updates asset information for existing assets; or remove <BR>
57 * those elements for which one of the links could not be found. <BR>
58 * 3) For files not already in the system, processes a failure using FLEXdbServer standard error <BR>
59 * handling mechanism and possibly send an email to an assigned user indicating that some of <BR>
60 * the files are not present. <BR>
61 * </P>
62 * The following rules apply when defining arguments: <BR>
63 * <P>
64 * - Properties defined in roletype_services.config override properties already defined in
65 * services.config. <BR>
66 * - Properties defined in either custom_process.xml or FLEXsi xml overrides properties
67 * already defined in services.config. <BR>
68 * </P>
69 * <BR>
70 * If run as an import service, this service must be defined in the pre-services section of the
71 * control file after both the Typer and RoleAssign services have been invoked. This convention
72 * guarantees that assets of different types, linked to other assets in the data object, are
73 * processed together, before the splitting of the data object by type and role. <BR>
74 *
75 * </BLOCKQUOTE>
76 * </P>
77 *
78 * <P>
79 * Input Data Object <BR>
80 * <BLOCKQUOTE>
81 * com.flexstor.common.data.ActionData or
82 * com.flexstor.common.importprocessor.ImportData
83 * </BLOCKQUOTE>
84 * </P>
85 *
86 * <P>
87 * Output Data Object <BR>
88 * <BLOCKQUOTE>
89 * com.flexstor.common.data.ActionResult containing a
90 * com.flexstor.common.importprocessor.ImportData object.
91 * </BLOCKQUOTE>
92 * </P>
93 *
94 * <P>
95 * Configurable Properties (in services.config or roletype_services.config) and <BR>
96 * Programmable Properties (passed inside data object ) <BR>
97 * <BLOCKQUOTE>
98 * Global Properties (apply to all assets) <BR>
99 * <BLOCKQUOTE>
100 * <P>
101 * inrole: The role of the assets to be linked to the primary parent asset. <BR>
102 * Data type: String <BR>
103 * Legal values: One of the following: HIGHRES, LOWRES, THUMBNAIL, LAYOUT, AUDIO,
104 * VIDEO or ALL <BR>
105 * </P>
106 * <P>
107 * intype: The type of the assets to be linked to the primary parent asset. <BR>
108 * Data type: String <BR>
109 * Legal values: A type as defined in typerdat.txt <BR>
110 * </P>
111 * <P>
112 * inflag: The flag of the assets to be linked to the primary parent asset. <BR>
113 * Data type: String <BR>
114 * Legal values: One of the following: PARENT, TEMP_PARENT, CHILDREN, TEMP_CHILDREN,
115 * ALL, TEMP_ALL <BR>
116 * </P>
117 * <P>
118 * disguiseList: Disguises in which to check for the existence of a file. If multiple disguises
119 * are specified they are checked in the order in which they are listed. <BR>
120 * Data type: String <BR>
121 * Legal values: comma-separated list of disguise labels or ids <BR>
122 * </P>
123 * <P>
124 * createNewElement: If this flag is set to true and an asset is not found in the system <BR>
125 * (but is part of the current ImportData object) a new element is created and the asset <BR>
126 * imported as a primary asset; the linked assets will then be a logical link to this asset <BR>
127 * in the database. If the flag is set to false the asset is removed from the ImportData <BR>
128 * object and added to the error list. If the flag is set to false and the failing asset is <BR>
129 * a child asset then the entire element is removed and placed in a error state. This assures <BR>
130 * that only complete collections are imported into the system. <BR>
131 * Data type: boolean <BR>
132 * Legal values: true or false <BR>
133 * </P>
134 * </BLOCKQUOTE>
135 * </BLOCKQUOTE>
136 * </P>
137 */
138 public class LinkUpdateService
139 implements Service
140 {
141 // MKS Identifier
142 public final static String IDENTIFIER = "$id";
143
144 protected ServiceContext context;
145 protected int id = -1;
146 protected String fileSeparator;
147 private ImportData importData;
148 private String sThisService;
149 private boolean bIsImportData; // Set to true only if the data passed to the service is an ImportData
150 private boolean bCreateNewElement;
151 private Set sFailedParent = new HashSet(); // Use a set to prevent duplicate assets to be added
152 private String sDisguiseList; // This is a list containing all disguises used for searching. It is kept
153 // for error logging purposes
154
155 /**
156 * Calls before the service is initialized (before initData is called) to
157 * pass information about the environment in which the service is running.
158 * This environment consists of information about the properties set for the
159 * service in one of these files (services.config, roletype_services.config,
160 * or *.ctl), plus methods to access other information such as an instance
161 * of the service broker to invoke other services, the transaction id for
162 * the service, file separator character and local path for the installation
163 * directory and configuration directory.
164 *
165 * @param context Holds information about the environment in which the service
166 * is running.
167 */
168 public void setServiceContext( ServiceContext context )
169 {
170 this.context = context;
171 id = context.getTransactionId();
172 fileSeparator = context.getFileSeparator();
173 }
174
175 /**
176 *
177 * @param data
178 * @see com.flexstor.flexdbserver.services.Service#initData(com.flexstor.common.data.ActionData)
179 */
180 public void initData(ActionData data)
181 {
182 //7098=Linked File Update Service
183 sThisService = Resources.get(7098) + " (" + id + ")";
184
185 // If the ActionData is not an instance of ImportData, convert it to one
186 if ( data instanceof ImportData )
187 {
188 bIsImportData = true;
189 importData = (ImportData) data;
190 }
191 else
192 importData = createImportData(data);
193
194 // Get CreateNewElement property
195 bCreateNewElement = Boolean.valueOf(context.getProperty( "createNewElement" )).booleanValue();
196 }
197
198 /**
199 * @return ActionResult
200 * @see com.flexstor.flexdbserver.services.Service#go()
201 */
202 public ActionResult go()
203 {
204 Vector vAssets = getImportAssets();
205 if ( vAssets != null )
206 {
207 // Validate disguise (id/label)
208 sDisguiseList = context.getProperty("disguiseList");
209 int[] naDisguises = validateDisguises( sDisguiseList );
210 if ( naDisguises != null )
211 {
212 try
213 {
214 // Constructs Search Criteria:
215 // select ... from ...
216 // where (filename = X and not archived) or
217 // (filename = X.bin and archived)
218 //
219 // and performs search
220 ImportedFileNameSearch importedSearch = new ImportedFileNameSearch( vAssets, naDisguises );
221 importedSearch.findAssets();
222 Vector vMatchedInDB = importedSearch.getMatchedAssets();
223 Vector vUnmatched = importedSearch.getUnmatchedAssets();
224
225 if ( vMatchedInDB != null )
226 updateImportData( vMatchedInDB );
227
228 if ( vUnmatched != null )
229 {
230 // Remove from the ImportData object, those assets that were not found in the database
231 // (including the primary parent so the entire element won't get added)
232 removePrimaryParents( vUnmatched );
233
234 // Log error including full path to unavailable assets
235 //
236 // Send an email to the user specified in the argument indicating that the new record
237 // could not be created because one of its link is not already in the system.
238 logFailedAssets( vUnmatched, null );
239 }
240 }
241 catch ( InvalidDataException ide )
242 {
243 logFailedAssets( vAssets, ide.getMessage() );
244 }
245 catch ( TransactionFailedException tfe )
246 {
247 logFailedAssets( vAssets, tfe.getMessage() );
248 }
249 }
250 }
251
252 // Return true only if there are primary assets left in the data object, otherwise return false
253 ImportResult result;
254 if ( importData.getDisguiseRecordRef().getPrimaryAssets().isEmpty() )
255 result = new ImportResult(false);
256 else
257 result = new ImportResult(true);
258
259 result.setImportData(importData);
260 return result;
261 }
262
263 /**
264 * Creates a ImportData object based on the ActionData object passed in the argument
265 * @param data
266 * @return ImportData
267 */
268 private ImportData createImportData( ActionData data )
269 {
270 // Call the ImportDataCreateService in order to convert the ActionData object into
271 // a ImportData object.
272 // Before calling the ImportDataCreateService, we first need to set the property that will specify
273 // where the hot directory is. Use the first AssetRecordData in ActionData
274 DirectDotHot refDirectDotHot = new DirectDotHot();
275
276 AssetRecordData record = (AssetRecordData) data.getRecords().elementAt(0);
277 String sRecordCtlFilePath = refDirectDotHot.getCtlFileName( fileSeparator + record.getLocation(), true );
278 data.setString( ActionPropertiesI.HOT_DIRECTORY, sRecordCtlFilePath );
279 data.setServiceName( ServicesI.IMPORTDATA_CREATE_SERVICE );
280 ActionResult srvResult = null;
281 try { srvResult = (ActionResult) context.getServiceBroker().invokeImmediately(data); }
282 catch ( SrvcNotAvailException snae ) {}
283
284 if ( srvResult != null && srvResult.isSuccess() )
285 return (ImportData) srvResult.getData();
286 else
287 return null;
288 }
289
290 /**
291 * Returns a Vector with the DisguiseAssetRecordData objects in the ImportData object
292 * @return Vector
293 */
294 private Vector getImportAssets()
295 {
296 DisguiseRecordData disguiseRecord = importData.getDisguiseRecordRef();
297 if ( disguiseRecord != null )
298 {
299 String sRole = context.getProperty(ServiceArgumentsI.ROLE_DATA_SOURCE);
300 String sType = context.getProperty(ServiceArgumentsI.TYPE_DATA_SOURCE);
301 String sFlag = context.getProperty(ServiceArgumentsI.FLAG_DATA_SOURCE);
302 Vector vAssets = disguiseRecord.getAssets(sRole, sType, sFlag);
303 if ( vAssets != null && !vAssets.isEmpty() )
304 return vAssets;
305 }
306
307 //6293=No assets found for this service.
308 logError( Resources.get(6293) );
309 return null;
310 }
311
312 /**
313 * Takes the disguiseList argument (from the services.config, *.ctl or roletype_services.config),
314 * parses it and validates each disguise (label or id) against the database list. Return only
315 * those that passed validation; for failed ones, log an error.
316 * @param sDisguiseList
317 * @return int[] list of valid disguise ids, or null if none passed validation
318 */
319 private int[] validateDisguises( String sDisguiseList )
320 {
321 if ( sDisguiseList != null )
322 {
323 String sToken;
324 Integer nToken;
325 ServerDisguiseExtendData disguiseExt;
326 List lDisguiseList = new ArrayList();
327
328 StringTokenizer st = new StringTokenizer( sDisguiseList, "," );
329
330 while ( st.hasMoreTokens() )
331 {
332 sToken = st.nextToken().trim();
333
334 // Try to convert the token to an Integer, if it works the chances are it represents an id
335 try { nToken = Integer.valueOf(sToken); }
336 catch ( NumberFormatException nfe ) { nToken = null; }
337
338 try
339 {
340 if ( nToken != null )
341 {
342 DisguiseLoader.getDisguise(nToken.intValue());
343 lDisguiseList.add(nToken);
344 continue;
345 }
346 }
347 catch( DisguiseLoaderException dle )
348 {
349 // If the exception is thrown because the number does not represent a disguise id;
350 // try to use it as the disguise label in the next try/catch block
351 }
352
353 try
354 {
355 // disguise is not a number, test for label
356 disguiseExt = DisguiseLoader.getDisguise(sToken);
357 lDisguiseList.add( new Integer(disguiseExt.getId()) );
358 continue;
359 }
360 catch( DisguiseLoaderException dle )
361 {
362 // Don't do anything here, errors will be reported in the next statement
363 }
364
365 // Token is not a valid disguise, abort
366 // 7066=%1 does not represent a valid disguise
367 logError( Resources.get( 7066, sToken ) );
368 }
369
370 int[] naDisguises = new int[ lDisguiseList.size() ];
371 for ( int i = 0; i < lDisguiseList.size(); i++ )
372 naDisguises[i] = ((Integer)lDisguiseList.get(i)).intValue();
373
374 return naDisguises;
375 }
376 else
377 return null;
378 }
379
380 /**
381 * After the search, the asset records are already updated with the server, location and filename
382 * of its match. What is left for this method to do is go through the primary parents and
383 * make sure their path is also updated (if CreateNewElement is true) or they are removed from
384 * the ImportData object (if CreateNewElement is false)
385 *
386 * @param vAssets
387 * @return Vector
388 */
389 private void updateImportData( Vector vAssets )
390 {
391 // Get all primary assets
392 DisguiseRecordData disguiseRecord = importData.getDisguiseRecordRef();
393 Vector vPrimaryAssets = disguiseRecord.getPrimaryAssets();
394 // For each primary asset we need to find out if they are also defined as child of another asset
395 DisguiseAssetRecordData parent, child;
396
397 for ( Iterator i = vAssets.iterator(); i.hasNext(); )
398 {
399 child = (DisguiseAssetRecordData) i.next();
400 if ( bCreateNewElement )
401 child.addProperty("createNewElement", Boolean.TRUE );
402
403 for ( Iterator j = vPrimaryAssets.iterator(); j.hasNext(); )
404 {
405 parent = (DisguiseAssetRecordData) j.next();
406 if ( parent.getFileName().equals(child.getFileName()) )
407 {
408 AssetUpdate.addParentCopySourceInfo(parent, child);
409
410 // Delete the element that contains this primary asset.
411 removeElement( parent );
412 }
413 }
414 }
415 }
416
417 /**
418 * For each asset in the unmatched list, find its primary parent and remove it from the
419 * ImportData object.
420 *
421 * @param vUnmatched List of children that were not matched in the database
422 */
423 private void removePrimaryParents( Vector vUnmatched )
424 {
425 DisguiseAssetRecordData parent;
426 for ( Iterator i = vUnmatched.iterator(); i.hasNext(); )
427 {
428 parent = getPrimaryParent( (DisguiseAssetRecordData)i.next() );
429 removeElement( parent );
430 sFailedParent.add(parent);
431 }
432 }
433
434 private void removeElement( DisguiseAssetRecordData parent )
435 {
436 // Delete the element that contains this primary asset.
437 DisguiseElementRecordData element = (DisguiseElementRecordData) parent.getTraversalPath().lastElement();
438 importData.getDisguiseRecordRef().deleteElement(element);
439 }
440
441 private DisguiseAssetRecordData getPrimaryParent( DisguiseAssetRecordData asset )
442 {
443 if ( asset.isPrimaryParent() )
444 return asset;
445 else
446 return getPrimaryParent( asset.getParent() );
447 }
448
449 /**
450 * Log error including full path to unavailable assets
451 *
452 * Send an email to the user specified in the argument indicating that the new record
453 * could not be created because one of its link is not already in the system.
454 */
455 private void logFailedAssets( Vector vUnmatched, String sErrorMsg )
456 {
457 DisguiseAssetRecordData asset;
458 StringBuffer sbError = new StringBuffer();
459 if ( sErrorMsg == null )
460 {
461 // 7099=The following assets were not found in the following disguises %%1:
462 sbError.append( Resources.get(7099, sDisguiseList) );
463 sbError.append( "\r\n" );
464 for ( Iterator i = vUnmatched.iterator(); i.hasNext(); )
465 {
466 asset = (DisguiseAssetRecordData)i.next();
467 sbError.append( " " );
468 sbError.append( asset.getLocation());
469 sbError.append( asset.getFileName());
470 sbError.append( "\r\n" );
471 }
472 }
473 else
474 {
475 sbError.append( sErrorMsg );
476 sbError.append( "\r\n" );
477 }
478 // 7100=The following assets and their children will not be imported:
479 sbError.append( Resources.get(7100) );
480 sbError.append( "\r\n" );
481 for ( Iterator i = sFailedParent.iterator(); i.hasNext(); )
482 {
483 asset = (DisguiseAssetRecordData)i.next();
484 sbError.append( " " );
485 sbError.append( asset.getLocation());
486 sbError.append( asset.getFileName());
487 sbError.append( "\r\n" );
488 }
489
490 logError( sbError.toString() );
491 }
492
493 private void logError( String sErrorMsg )
494 {
495 FlexError error = new FlexError(FlexError.CRITICAL, sThisService, IDENTIFIER, sErrorMsg);
496 if ( bIsImportData )
497 importData.addErrorRecord(error);
498 }
499 }