001/*
002 * Copyright (c) 2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2018-01-15 20:27:04 +0000 (Mon, 15 Jan 2018) $' 
007 * '$Revision: 34649 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.kar.karxml;
031
032import java.io.File;
033import java.io.IOException;
034import java.io.InputStream;
035import java.io.StringReader;
036import java.util.Iterator;
037import java.util.jar.Attributes;
038import java.util.jar.Attributes.Name;
039import java.util.jar.Manifest;
040import java.util.regex.Pattern;
041import java.util.zip.ZipEntry;
042
043import org.apache.commons.logging.Log;
044import org.apache.commons.logging.LogFactory;
045import org.kepler.configuration.ConfigurationProperty;
046import org.kepler.kar.KAREntry;
047import org.kepler.kar.KARFile;
048import org.kepler.util.FileUtil;
049import org.w3c.dom.Document;
050import org.xml.sax.SAXException;
051
052/**
053 * The KarXmlGenerator class reads in a KARFile and generates an xml string that
054 * can be used to upload the KAR file to Metacat.
055 * 
056 * @author Aaron Schultz
057 */
058public class KarXmlGenerator {
059
060        private static final Log log = LogFactory.getLog(KarXmlGenerator.class
061                        .getName());
062        private static final boolean isDebugging = log.isDebugEnabled();
063        private String _schemaUrl = null;
064        private String _XSNamespace = "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"";
065        private String _namespace = null;
066        private String _schemaLocation = null;
067        private Name _manifestVersion = new Name("Manifest-Version");
068        
069        private KARFile _karFile;
070        private StringBuffer karxml;
071        private boolean hasReportLayout = false;
072
073        public static final String nl = System.getProperty("line.separator");
074        public static final String tab = "\t";
075
076        /**
077         * Empty Constructor
078         */
079        public KarXmlGenerator() {
080        }
081
082        /**
083         * Constructor that takes a KARFile as a parameter. This will be the KARFile
084         * read in during the call to getKarXml()
085         * 
086         * @param karFile
087         */
088        public KarXmlGenerator(KARFile karFile) {
089                setKarFile(karFile);
090        }
091
092        /**
093         * Set the KARFile object to generate the karxml from.
094         * 
095         * @param karFile
096         * @return
097         */
098        public void setKarFile(KARFile karFile) {
099                _karFile = karFile;
100                setKARVersionSpecificInfo(_karFile.getKARVersion());    
101        }
102        
103        /**
104         * Set _namespace, _schemaUrl, and _schemaLocation for KAR version
105         * @param karFileVersion
106         */
107        public void setKARVersionSpecificInfo(String karFileVersion) {
108
109                ConfigurationProperty KARVersionsProp = KARFile
110                                .getKARVersionsConfigProperty();
111
112                Iterator<ConfigurationProperty> karVersionsItr = KARVersionsProp
113                                .getProperties().iterator();
114                while (karVersionsItr.hasNext()) {
115                        ConfigurationProperty prop = karVersionsItr.next();
116                        if (prop.getName().equals(KARFile.KAR_VERSION_PROPERTY_NAME)) {
117                                ConfigurationProperty version = prop.getProperty(KARFile.KAR_VERSION_VERSION_PROPERTY_NAME);
118                                if (version.getValue().equals(karFileVersion)) {
119                                        ConfigurationProperty namespaceConfigProp = prop
120                                                        .getProperty(KARFile.KAR_VERSION_NAMESPACE_PROPERTY_NAME);
121                                        ConfigurationProperty schemaUrlConfigProp = prop
122                                                        .getProperty(KARFile.KAR_VERSION_SCHEMAURL_PROPERTY_NAME);
123                                        _namespace = namespaceConfigProp.getValue();
124                                        _schemaUrl = schemaUrlConfigProp.getValue();
125                                        _schemaLocation = "xsi:schemaLocation=\"" + _namespace
126                                                        + " " + _schemaUrl + "\"";
127                                }
128                        }
129                }
130
131        }
132
133        /**
134         * Get an XML file representation for a KARFile. Pass in the KARFile to be
135         * used while generating the karxml.
136         * 
137         * @param karFile
138         * @return
139         * @throws IOException
140         */
141        public String getKarXml(KARFile karFile) throws IOException, SAXException{
142                setKarFile(karFile);
143                String xml = getKarXml();
144                return xml;
145        }
146
147        /**
148         * Get an XML file representation for a KARFile. Use the setKarFile(KARFile)
149         * method before calling this method.
150         * 
151         * @return
152         * @throws IOException
153         */
154        public String getKarXml() throws IOException, SAXException {
155                karxml = new StringBuffer();
156                karxml.append("<?xml version=\"1.0\"?>" + nl);
157                karxml.append("<kar:kar xmlns:kar=\""+_namespace+"\""+" "+_XSNamespace+" "+_schemaLocation+">" 
158                    + nl);
159
160                // Adding this for Sean Riddle to facilitate using the kar filename
161                // in remote search results
162                File f = _karFile.getFileLocation();
163                String filename = f.getName();
164                karxml.append(tab + "<karFileName>" + nl);
165                karxml.append(tab + tab + filename + nl);
166                karxml.append(tab + "</karFileName>" + nl);
167                
168                long fileSize = f.length();
169                karxml.append(tab + "<karFileSize>" + nl);
170                karxml.append(tab + tab + fileSize + nl);
171                karxml.append(tab + "</karFileSize>" + nl);
172
173                appendXmlForMainAttributes();
174
175                for (KAREntry entry : _karFile.karEntries()) {
176                  if(entry != null && entry.isReportLayout()){
177                    hasReportLayout = true;
178                  }
179                        karxml.append(tab + "<karEntry>" + nl);
180                        appendXmlForEntryAttributes(entry);
181                        appendXmlFor(entry);
182                        karxml.append(tab + "</karEntry>" + nl);
183                }
184
185                karxml.append("</kar:kar>" + nl);
186                String karXmlStr = karxml.toString();
187                validateKarXml(karXmlStr);
188                return karXmlStr;
189        }
190        
191        /**
192         * Determine if the generated kar xml has a report layout.
193         * This method can only be called after calling getKarXml()
194         * @return
195         */
196        public boolean hasReportLayout(){
197          return hasReportLayout;
198        }
199        
200        /*
201         * validate the generated kar xml
202         */
203        private void validateKarXml(String xml) throws SAXException
204        {
205          if(xml != null)
206          {
207            Document document = KarXml.parseXml(new StringReader(xml));
208            if(document == null)
209            {
210              throw new SAXException("Kepler couldn't transform the generated kar xml to a DOM tree model. So the generated kar xml is not valid");
211            }
212            else
213            {
214              boolean isValid = KarXml.validateDocument(document);
215              if(!isValid)
216              {
217                throw new SAXException("The generated kar xml is not valid");
218              }
219            }
220          }
221          else
222          {
223            throw new SAXException("The generated kar xml is null and it is invalid");
224          }
225        }
226
227        /**
228         * Read in the main attributes of the KAR and append them to the xml.
229         * 
230         * @throws IOException
231         */
232        private void appendXmlForMainAttributes() throws IOException {
233
234                karxml.append(tab + "<mainAttributes>" + nl);
235
236                // gets the manifest
237                Manifest mf = _karFile.getManifest();
238
239                // gets the main attributes in the manifest
240                Attributes atts = mf.getMainAttributes();
241                if(atts != null){
242                String lsid = atts.getValue(KARFile.LSID);
243                if(lsid != null){
244                  karxml.append(tab + tab + "<" + KARFile.LSID + ">" + nl);
245        karxml.append(tab + tab + tab + lsid + nl);
246        karxml.append(tab + tab + "</" + KARFile.LSID + ">" + nl);
247                }
248                String moduleDependencies = atts.getValue(KARFile.MOD_DEPEND);
249      if(moduleDependencies != null){
250        karxml.append(tab + tab + "<" + KARFile.MOD_DEPEND + ">" + nl);
251        karxml.append(tab + tab + tab + moduleDependencies + nl);
252        karxml.append(tab + tab + "</" + KARFile.MOD_DEPEND + ">" + nl);
253      }
254      String karVersion = atts.getValue(KARFile.KAR_VERSION);
255      if(karVersion != null){
256        karxml.append(tab + tab + "<" + KARFile.KAR_VERSION + ">" + nl);
257        karxml.append(tab + tab + tab + karVersion + nl);
258        karxml.append(tab + tab + "</" + KARFile.KAR_VERSION + ">" + nl);
259      }
260      String manifestVersion = atts.getValue(_manifestVersion);
261      if(manifestVersion != null){
262        karxml.append(tab + tab + "<" + _manifestVersion + ">" + nl);
263        karxml.append(tab + tab + tab + manifestVersion + nl);
264        karxml.append(tab + tab + "</" + _manifestVersion + ">" + nl);
265      }
266                }
267                // Loop through the attributes
268                /*for (Object att : atts.keySet()) {
269
270                        if (att instanceof Name) {
271                                Name attrName = (Name) att;
272                                String attrValue = atts.getValue(attrName);
273
274                                karxml.append(tab + tab + "<" + attrName + ">" + nl);
275                                karxml.append(tab + tab + tab + attrValue + nl);
276                                karxml.append(tab + tab + "</" + attrName + ">" + nl);
277                        } else {
278                                throw new IOException("Unrecognized Main Attribute");
279                        }
280                }*/
281
282                karxml.append(tab + "</mainAttributes>" + nl);
283    //System.out.println("karxml is ==========\n "+karxml.toString());
284        }
285
286        /**
287         * Read in all attributes for the entry and append them to the xml.
288         * 
289         * @param entry
290         */
291        private void appendXmlForEntryAttributes(KAREntry entry) {
292
293                karxml.append(tab + tab + "<karEntryAttributes>" + nl);
294
295                // the name of a KAREntry is not found in it's attributes
296                // must get it directly
297                String entryName = entry.getName();
298                karxml.append(tab + tab + tab + "<Name>" + nl);
299                karxml.append(tab + tab + tab + tab + entryName + nl);
300                karxml.append(tab + tab + tab + "</Name>" + nl);
301
302                Attributes atts = entry.getAttributes();
303
304                for (Object att : atts.keySet()) {
305                        // System.out.println( att.toString() );
306                        if (att instanceof Name) {
307
308                                Name attrName = (Name) att;
309                                String value = atts.getValue(attrName);
310
311                                karxml.append(tab + tab + tab + "<" + attrName + ">" + nl);
312                                karxml.append(tab + tab + tab + tab + value + nl);
313                                karxml.append(tab + tab + tab + "</" + attrName + ">" + nl);
314
315                        }
316                }
317
318                karxml.append(tab + tab + "</karEntryAttributes>" + nl);
319
320        }
321
322        /**
323         * Decide what to do depending on what kind of entry this is.
324         * 
325         * @param entry
326         * @throws IOException
327         */
328        private void appendXmlFor(KAREntry entry) throws IOException {
329
330                if (isXmlEntry(entry)) {
331                        appendXmlEntryContents(entry);
332                } else {
333                        // maybe at some point we'll want to include this
334                        // for now, only include the kar entry attributes
335                        // for generic (aka non-xml) files
336                        // appendGenericEntryXml(entry);
337                }
338
339        }
340
341        /**
342         * Checks to see if the entry ends with the xml extension. This method is
343         * case insensitive.
344         * 
345         * @param entry
346         * @return
347         */
348        private boolean isXmlEntry(KAREntry entry) {
349
350                String entryName = entry.getName();
351                String lowerCaseEntryName = entryName.toLowerCase();
352                if (lowerCaseEntryName.endsWith(".xml"))
353                        return true;
354
355                return false;
356        }
357
358        /**
359         * For an XML file we convert the contents to a string and append it.
360         * 
361         * @param entry
362         * @throws IOException
363         */
364        private void appendXmlEntryContents(KAREntry entry) throws IOException {
365                karxml.append(tab + tab + "<karEntryXML>" + nl);
366
367                InputStream is = _karFile.getInputStream((ZipEntry) entry);
368
369                String contents = FileUtil.convertStreamToString(is);
370                contents = processXmlEntryContents(contents);
371
372                karxml.append(contents + nl);
373
374                karxml.append(tab + tab + "</karEntryXML>" + nl);
375        }
376
377        /**
378         * Strip out the "<?xml" tag from the xml. Also removes a header DOCTYPE, if present.
379         * 
380         * @param xml
381         * @return
382         */
383        public String processXmlEntryContents(String xml) {
384
385                xml = xml.replaceAll("<\\?xml.*?>", "");
386                xml = Pattern.compile("<!DOCTYPE.*?>", Pattern.DOTALL).matcher(xml).replaceAll("");
387                return xml;
388        }
389
390        /**
391         * For a file that is not XML maybe we want to add something here. Or maybe
392         * the attributes for the entry are enough.
393         * 
394         * @param entry
395         * 
396         *            private void appendGenericEntryXml(KAREntry entry) {
397         * 
398         *            karxml.append(tab + tab + "<genericKarEntry>" + nl);
399         * 
400         *            karxml.append(tab + tab + "</genericKarEntry>" + nl); }
401         */
402
403}