Source code: com/flexstor/flexdbserver/services/asset/iptc/FileInfoPSDService.java
1 /*
2 * FileInfoPSDService.java
3 *
4 * Copyright $Date: 2003/08/11 02:22:34 $ 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.asset.iptc;
12
13 import java.util.Enumeration;
14 import java.util.Hashtable;
15 import java.util.Vector;
16
17 import com.flexstor.common.data.ActionData;
18 import com.flexstor.common.data.ActionResult;
19 import com.flexstor.common.data.ejb.disguiserecord.AssetRoleData;
20 import com.flexstor.common.data.ejb.disguiserecord.DisguiseAssetRecordData;
21 import com.flexstor.common.data.ejb.disguiserecord.DisguiseElementRecordData;
22 import com.flexstor.common.data.ejb.disguiserecord.DisguiseFieldRecordData;
23 import com.flexstor.common.data.ejb.disguiserecord.DisguiseRecordData;
24 import com.flexstor.common.errorlogger.FlexError;
25 import com.flexstor.common.importprocessor.ImportData;
26 import com.flexstor.common.importprocessor.ImportResult;
27 import com.flexstor.common.io.xfile.FlexXFile;
28 import com.flexstor.common.services.ServiceArgumentsI;
29 import com.flexstor.common.util.Diagnostic;
30 import com.flexstor.ejb.bucket.persist.ServerBucketExtendData;
31 import com.flexstor.ejb.disguise.persist.ServerDisguiseExtendData;
32 import com.flexstor.ejb.field.persist.ServerFieldExtendData;
33 import com.flexstor.flexdbserver.disguise.DisguiseLoader;
34 import com.flexstor.flexdbserver.services.Service;
35 import com.flexstor.flexdbserver.services.ServiceContext;
36
37 /**
38 * <P>
39 * FileInfoPSDService <BR>
40 * <BLOCKQUOTE>
41 * This service will obtain the IPTC information from a Photoshop file and place it in
42 * the metadata based on the configuration specification in the roletype_services.config
43 * file.
44 * </BLOCKQUOTE>
45 * </P>
46 *
47 * <P>
48 * Configurable Properties in services.config <BR>
49 * <BLOCKQUOTE>
50 * Asset-file-type/subservice code mapping. Specific file types must be mapped to specific
51 * subservice parsers. The file type name must be the name used in the Typer service definition
52 * file. The currently available parser/type mappings are:
53 * <BR>
54 * EPSType = <file-type-name>
55 * PSDType = <file-type-name>
56 * TIFFType = <file-type-name>
57 * <BR>
58 * </BLOCKQUOTE>
59 * </P>
60 *
61 * Configurable Properties in roletype_services.config <BR>
62 * <BLOCKQUOTE>
63 * Any combination of the following properties can be set to correspond
64 * to an asset or element level field: Caption, CaptionWriter, Headline,
65 * SpecialInstructions, Keywords,Category, SupplementalCategories,Urgency,
66 * Byline, BylineTitle, Credit, Source, ObjectName, DateCreated,City,
67 * ProvinceState, CountryName, OriginalTransmissionReference, and CopyrightNotice
68 * <BR>
69 * For example: <BR>
70 * service2.Keywords = Asset,Asset Notes
71 * <BR>
72 * or
73 * <BR>
74 * service2.Caption = Element,Notes
75 * </BLOCKQUOTE>
76 *
77 * <!-- THE ACTUAL TABLE: -->
78 *
79 * <TABLE BORDER="1" CELLPADDING="3" CELLSPACING="3">
80 * <CAPTION ALIGN=TOP>
81 * <B> In/Out Properties for Assets </B>
82 * </CAPTION>
83 * <TR>
84 * <FONT SIZE=+1><B>
85 * <TH WIDTH="120">Attribute</TH>
86 * <TH WIDTH="30">IN</TH> <TH WIDTH="30">OUT</TH> <TH WIDTH="30">Default IN</TH> <TH WIDTH="30">Default OUT</TH>
87 * </B></FONT>
88 * </TR>
89 * <TR>
90 * <TH ALIGN=LEFT><FONT SIZE=+1><B>ROLE</B></FONT></TH><TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER> ALL </TD> <TD ALIGN=CENTER> ALL </TD>
91 * </TR>
92 * <TR><TH> Highres </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
93 * <TR><TH> Lowres </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
94 * <TR><TH> Thumbnail </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
95 * <TR><TH> Layout </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
96 * <TR><TH> Video </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
97 * <TR><TH> Audio </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
98 * <TR>
99 * <TH ALIGN=LEFT><FONT SIZE=+1><B>TYPE</B></FONT></TH>
100 * <TD ALIGN=CENTER> X* </TD> <TD ALIGN=CENTER> X* </TD> <TD ALIGN=CENTER> ALL* </TD> <TD ALIGN=CENTER> ALL* </TD></TR>
101 * </TR>
102 * <TR>
103 * <TH ALIGN=LEFT><FONT SIZE=+1><B>FLAG</B></FONT></TH>
104 * </TR>
105 * <TR><TH> PARENT </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD></TR>
106 * <TR><TH> CHLDREN </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
107 * <TR><TH> ALL </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
108 * <TR><TH> TEMP_PARENT </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
109 * <TR><TH> TEMP_CHILDREN </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
110 * <TR><TH> TEMP_ALL </TH> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER> X </TD> <TD ALIGN=CENTER>   </TD> <TD ALIGN=CENTER>   </TD></TR>
111 * </TABLE>
112 * *Note: This service will only operate on Photoshop files. The service does some byte
113 * matching similar to the FileTyper to make sure it has a Photoshop file before it even
114 * attempts to parse the IPTC information from it. However, the actual Type name is not
115 * verified and so it could potentially be anything.
116 *
117 * <P>
118 * Input Data Object <BR>
119 * <BLOCKQUOTE>
120 * com.flexstor.common.importprocessor.ImportData
121 * </BLOCKQUOTE>
122 * </P>
123 *
124 * <P>
125 * Output Data Object <BR>
126 * <BLOCKQUOTE>
127 * com.flexstor.common.importprocessor.ImportResult
128 * </BLOCKQUOTE>
129 * </P>
130 *
131 */
132 public class FileInfoPSDService
133 implements Service
134 {
135 /** Is this the MKS identifier? Hmm...
136 */
137 public String IDENTIFIER = "$Id:";
138
139 protected ServiceContext context;
140
141 /** A string for holding the service name (set during initData)
142 */
143 private String sThisService = "";
144
145 /** The final result; whether we were successful. Start out an optimist.
146 */
147 protected boolean successful = true;
148
149 /** The import data object.
150 */
151 private ImportData importData = null;
152
153 /** The disguise extend data contains the field labels, etc., for the disguise.
154 */
155 private ServerDisguiseExtendData refDisguiseExtend = null;
156
157 /** This hashtable holds the IPTC_dataset_number to Asset_field mapping
158 */
159 private Hashtable hDataToField = new Hashtable();
160
161 /** This hashtable holds the asset file type to subservice mapping
162 */
163 private Hashtable hAssetToSubservice = new Hashtable();
164
165 /** These are the possible "File Info..." fields which can be mapped to
166 * asset data fields. The string at [n][0] is the label as it appears in
167 * Photoshop 5.5. The number is the IPTC mapped dataset number.
168 */
169 public final String[][] asFileInfoDataItems =
170 {
171 /* CAPTION */
172 {"Caption", "2:120"},
173 {"CaptionWriter", "2:122"},
174 {"Headline", "2:105"},
175 {"SpecialInstructions", "2:40"},
176 /* KEYWORDS */
177 {"Keywords", "2:25"},
178 /* CATEGORIES */
179 {"Category", "2:15"},
180 {"SupplementalCategories", "2:20"},
181 {"Urgency", "2:10"},
182 /* CREDITS */
183 {"Byline", "2:80"},
184 {"BylineTitle", "2:85"},
185 {"Credit", "2:110"},
186 {"Source", "2:115"},
187 /* ORIGIN */
188 {"ObjectName", "2:5"},
189 {"DateCreated", "2:55"},
190 {"City", "2:90"},
191 {"ProvinceState", "2:95"},
192 {"CountryName", "2:101"},
193 {"OriginalTransmissionReference", "2:103"},
194 // "Preserve Additional Information" -- not found in IPTC spec
195 /* COPYRIGHT & URL */
196 // "Mark As Copyrighted" -- not found in IPTC spec
197 {"CopyrightNotice", "2:116"}
198 // "Image URL" -- not found in IPTC spec
199 };
200
201
202 /**
203 * Calls before the service is initialized (before initData is called) to
204 * pass information about the environment in which the service is running.
205 * This environment consists of information about the properties set for the
206 * service in one of these files (services.config, roletype_services.config,
207 * or *.ctl), plus methods to access other information such as an instance
208 * of the service broker to invoke other services, the transaction id for
209 * the service, file separator character and local path for the installation
210 * directory and configuration directory.
211 *
212 * @param context Holds information about the environment in which the service
213 * is running.
214 */
215 public void setServiceContext( ServiceContext context )
216 {
217 this.context = context;
218 }
219
220 /**
221 * Data initialization method called at the beginning of the service.
222 *
223 * @param actionData ActionData is the super class of the data wrapper object
224 * which contains all the information for executing the service.
225 */
226 public void initData( ActionData actionData )
227 {
228 Diagnostic.trace( Diagnostic.APPSERVER_IMPORT, "Starting service " + sThisService );
229
230 importData = (ImportData) actionData;
231
232 // In order to retrieve the bucket extend data, we'll need the disguise record data
233 DisguiseRecordData disguiseRecord = importData.getDisguiseRecordRef();
234 // Once we have the disguise record data, we can get the Disguise Id and Extend data
235 try
236 {
237 refDisguiseExtend = DisguiseLoader.getDisguise( disguiseRecord.getDisguiseId() );
238 }
239 catch( Exception e )
240 {
241 System.out.println( "Exception occurred whilst loading the disguise" );
242 e.printStackTrace();
243 }
244
245 sThisService = context.getProperty(ServiceArgumentsI.SERVICE_NAME);
246
247 String sTempPropertyValue = null;
248 for( int i=0; i < asFileInfoDataItems.length; i++ )
249 {
250 sTempPropertyValue = context.getProperty( asFileInfoDataItems[i][0] );
251 if( sTempPropertyValue != null )
252 {
253 hDataToField.put( asFileInfoDataItems[i][1], sTempPropertyValue );
254 Diagnostic.trace( Diagnostic.APPSERVER_IMPORT,
255 "Will map " + asFileInfoDataItems[i][0] +
256 "(" + asFileInfoDataItems[i][1] + ") to " +
257 context.getProperty( asFileInfoDataItems[i][0] ) );
258 }
259 }
260
261 // Get the asset file types to subservice mapping from services.config
262 String sValue = context.getProperty("PSDType");
263 if ((sValue != null) && (sValue.equals("")) == false)
264 {
265 hAssetToSubservice.put(sValue, "PSDType");
266 }
267
268 sValue = context.getProperty("EPSType");
269 if ((sValue != null) && (sValue.equals("") == false))
270 {
271 hAssetToSubservice.put(sValue, "EPSType");
272 }
273
274 sValue = context.getProperty("TIFFType");
275 if ((sValue != null) && (sValue.equals("") == false))
276 {
277 hAssetToSubservice.put(sValue, "TIFFType");
278 }
279
280 } // initData
281
282
283
284 /**
285 * Start of the Service
286 *
287 * @return a Result object with the an updated data object.
288 */
289 public ActionResult go()
290 {
291
292 DisguiseRecordData disguiseRecordData = importData.getDisguiseRecordRef();
293 String sRole = context.getProperty(ServiceArgumentsI.ROLE_DATA_SOURCE);
294 String sType = context.getProperty(ServiceArgumentsI.TYPE_DATA_SOURCE);
295 String sFlag = context.getProperty(ServiceArgumentsI.FLAG_DATA_SOURCE);
296
297 Vector assetRecords = null;
298
299 if(disguiseRecordData != null)
300 {
301 if(sRole == null || sType == null || sFlag == null)
302 successful = false;
303 else
304 {
305 assetRecords = disguiseRecordData.getAssets(sRole, sType, sFlag);
306 if(assetRecords != null)
307 {
308 Enumeration assets = assetRecords.elements();
309 while(assets.hasMoreElements())
310 {
311 // Get the next Asset in line
312 DisguiseAssetRecordData anAsset =
313 (DisguiseAssetRecordData)assets.nextElement();
314
315 // And process that asset
316 processAsset( anAsset );
317 }
318 }
319 else
320 {
321 // If there are no assets, this service was not successful.
322 successful = false;
323 }
324 }
325 }
326 else
327 {
328 // If the disguise could not be loaded, this service could not be successful.
329 successful = false;
330 }
331
332 // Create the response
333 ImportResult response = new ImportResult(successful);
334 response.setImportData(importData);
335
336 Diagnostic.trace( Diagnostic.APPSERVER_IMPORT, "Finished service " + sThisService );
337
338 return response;
339 }
340
341
342 /** Cycle through the IPTC info already found and retrieved from a file
343 * and attach it to the Asset
344 */
345 private boolean processAsset( DisguiseAssetRecordData anAsset )
346 {
347 String sCurrentDatasetID = null; // This is the 2:XX dataset ID number
348 String sFieldLabel = null; // The Field Label from the Disguise
349 String sValue = null; // The data value in the file
350 Hashtable hElementDataHash = new Hashtable(); // Holds element data until processing
351
352 // System.out.println( "The asset can be found at: " +
353 // anAsset.getServer() + ":/" + anAsset.getLocation() +
354 // anAsset.getFileName() );
355
356 // File handle for the asset so we can read it.
357 String sFilePath = "/" + anAsset.getLocation() + anAsset.getFileName();
358 FlexXFile anAssetFile = new FlexXFile( sFilePath );
359
360 // Get the asset file type so that we can determine the subservice code that
361 // will operate on it based on the mapping data obtained from services.config
362 AssetRoleData roleData = anAsset.getAssetRole();
363 String sAssetFileType = roleData.getAssetFileType();
364 String sSubServiceType = (String)hAssetToSubservice.get(sAssetFileType);
365 IIPTCParser IptcParser = null;
366
367 // Select the subservice class, default to PSDParser
368 if ((sSubServiceType == null) ||
369 (sSubServiceType.equals("") == true))
370 {
371 // Type error
372 IptcParser = null;
373 }
374
375 else if (sSubServiceType.equals("PSDType") == true)
376 {
377 IptcParser = new PSDParser( anAssetFile );
378 }
379
380 else if (sSubServiceType.equals("EPSType") == true)
381 {
382 IptcParser = new EPSParser( anAssetFile );
383 }
384
385 else if (sSubServiceType.equals("TIFFType") == true)
386 {
387 IptcParser = new TIFFParser( anAssetFile );
388 }
389
390 else
391 {
392 // Type error
393 IptcParser = null;
394 }
395
396 System.out.println("Instantiating parser subservice class: " + sSubServiceType + " for " + sFilePath);
397 if (IptcParser == null)
398 {
399 System.out.println("Error instantiating parser subservice class. ");
400 new FlexError(FlexError.CRITICAL, sThisService, IDENTIFIER, "Iptc subservice parser not specified for file type: " + sAssetFileType);
401 return true;
402 }
403
404 // The subservice class has been selected and instantiated so now
405 // request it to parse the file
406 if( IptcParser.parseFile() == true )
407 {
408 Diagnostic.trace( Diagnostic.APPSERVER_IMPORT,
409 "File Info successfully parsed from file: " + sFilePath);
410 }
411 else
412 {
413 Diagnostic.warn( Diagnostic.APPSERVER_IMPORT,
414 "File Info not successfully parsed from file: " + sFilePath );
415 }
416
417
418 // loop through all of the dataset numbers that the service settings had
419 // mentioned and see if the file we parsed had any of the applicable fields.
420 for( Enumeration e = hDataToField.keys(); e.hasMoreElements(); )
421 {
422 // Retrieve the 2:XX number
423 sCurrentDatasetID = (String) e.nextElement();
424 // Retrieve the Field Label
425 sFieldLabel = (String) hDataToField.get( sCurrentDatasetID );
426 // Retrieve the value that we found in the file
427 sValue = (String) IptcParser.getDatasetValue( sCurrentDatasetID );
428
429 // If this DataSet/Field does not have a value, don't worry about it.
430 if( sValue != null )
431 {
432 if( sFieldLabel.startsWith( "Asset," ) )
433 {
434 // Strip the "Asset," indicator from the sFieldLabel
435 sFieldLabel = sFieldLabel.substring( 6 );
436
437 System.out.println( "Asset field '" + sFieldLabel + "' will be: " + sValue );
438
439 // Add the data to the asset
440 anAsset.addUserData( sFieldLabel, sValue );
441 }
442 else if( sFieldLabel.startsWith( "Element," ) )
443 {
444 // Strip the "Element," indicator from the sFieldLabel
445 sFieldLabel = sFieldLabel.substring( 8 );
446
447 System.out.println( "Element field '" + sFieldLabel + "' will be: " + sValue );
448
449 // Adding the data to the element is a little more complicated
450 // Place the data in a hashtable so the data can be added all-at-once
451 hElementDataHash.put( sFieldLabel, sValue );
452 }
453 else
454 {
455 System.out.println( "Unknown bucket level. Should be Asset or Element." );
456 }
457
458 }
459 }
460
461 // This next section will only need to be executed if there is data that will
462 // be placed into Element level fields.
463 if( hElementDataHash.isEmpty() != true )
464 {
465 // This will be the element that contains our asset
466 DisguiseElementRecordData theElement = null;
467
468 // In order to find the element that this asset is in, get the traversal path
469 Vector vTraversalPath = anAsset.getTraversalPath();
470 // The element is the last item in the vector
471 theElement =
472 (DisguiseElementRecordData) vTraversalPath.elementAt( vTraversalPath.size()-1 );
473
474 // Get the extend data for the Element bucket (I hope)
475 ServerBucketExtendData tmpSBED =
476 refDisguiseExtend.getServerBucketExtendDataObject( theElement.getBucketStructId() );
477
478 // Now retrieve the vector of field data objects
479 Vector vFields = tmpSBED.getFieldDataObjects();
480
481 // This will probably never happen, but in case it does, it would be nice to know
482 if( vFields == null ) System.out.println( "Unable to obtain the field label data" );
483
484 // By declaring this variable outside the loop, we don't have to create it each time
485 ServerFieldExtendData tmpSFED = null;
486 String sTmpValue = null;
487
488 // Get the Field Record Data for the element
489 DisguiseFieldRecordData tmpDFRD [] = theElement.getValues();
490 // Remember this for later if we have new DisguiseFieldRecordDatas (assume false)
491 boolean bSetNewDFRD = false;
492 if( tmpDFRD == null )
493 {
494 tmpDFRD = new DisguiseFieldRecordData[ vFields.size() ];
495 for( int j=0; j < vFields.size(); j++ )
496 {
497 tmpDFRD[j] = new DisguiseFieldRecordData();
498 }
499 // Maybe we should re-attach it to the element...
500 bSetNewDFRD = true;
501 }
502
503 // Cycle through the field labels, check if we have data for each field
504 for( int i=0; i < vFields.size(); i++ )
505 {
506 // Get a field extend data (for getting the label).
507 tmpSFED = (ServerFieldExtendData) vFields.elementAt(i);
508
509 // Get the value, if there is one, for that label.
510 sTmpValue = (String) hElementDataHash.get( tmpSFED.getLabel() );
511
512 // If there is not a new value for this field (==null), just skip it
513 // Otherwise, modify the current value for this field
514 if( sTmpValue != null )
515 {
516 // A disguise field exists for the input Iptc data field (labels match)
517 // Now check to make sure that the input data will fit in the db field
518 if (tmpSFED.getLength() < sTmpValue.length())
519 {
520 // The disguise (db) field length is smaller than the data we want to
521 // put into it so we can't update the field and should abort this asset.
522 System.out.println("Data field overrun " + tmpSFED.getLabel());
523 new FlexError(FlexError.CRITICAL, sThisService, IDENTIFIER, "Iptc data length is too long for the field " + tmpSFED.getLabel() + ": " + sTmpValue);
524 return true;
525 }
526
527 //DEBUG System.out.print( "New value was found for " + tmpSFED.getLabel() );
528 // the DisguiseFieldRecordData method to change values requires an array
529 String asNewValues[] = { sTmpValue };
530 // modify the current data value
531 tmpDFRD[i].setValues( asNewValues );
532 //DEBUG System.out.println( "; now the value is >" + tmpDFRD[i].getValue() + "<" );
533 }
534 } /* end of for loop */
535
536 if ( bSetNewDFRD == true )
537 {
538 theElement.setValues( tmpDFRD );
539 System.out.println( "Note: element level data may not be modified during GUI import" );
540 }
541
542 } /* End of element specific field/value stuff */
543
544 return true;
545 } // processAsset()
546
547 }