001    /*
002     * JBoss, Home of Professional Open Source.
003     * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004     * as indicated by the @author tags. See the copyright.txt file in the
005     * distribution for a full listing of individual contributors. 
006     *
007     * This is free software; you can redistribute it and/or modify it
008     * under the terms of the GNU Lesser General Public License as
009     * published by the Free Software Foundation; either version 2.1 of
010     * the License, or (at your option) any later version.
011     *
012     * This software is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this software; if not, write to the Free
019     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021     */
022    package org.jboss.example.dna.sequencers;
023    
024    import java.io.File;
025    import java.net.URL;
026    import java.util.ArrayList;
027    import java.util.Calendar;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Properties;
031    import java.util.TreeMap;
032    import java.util.concurrent.TimeUnit;
033    import javax.jcr.Credentials;
034    import javax.jcr.Node;
035    import javax.jcr.NodeIterator;
036    import javax.jcr.PathNotFoundException;
037    import javax.jcr.Property;
038    import javax.jcr.PropertyIterator;
039    import javax.jcr.Repository;
040    import javax.jcr.RepositoryException;
041    import javax.jcr.Session;
042    import javax.jcr.SimpleCredentials;
043    import javax.jcr.Value;
044    import javax.jcr.ValueFormatException;
045    import javax.jcr.observation.Event;
046    import org.apache.jackrabbit.api.JackrabbitNodeTypeManager;
047    import org.apache.jackrabbit.core.TransientRepository;
048    import org.jboss.dna.common.SystemFailureException;
049    import org.jboss.dna.repository.observation.ObservationService;
050    import org.jboss.dna.repository.sequencers.SequencerConfig;
051    import org.jboss.dna.repository.sequencers.SequencingService;
052    import org.jboss.dna.repository.util.BasicJcrExecutionContext;
053    import org.jboss.dna.repository.util.JcrExecutionContext;
054    import org.jboss.dna.repository.util.JcrTools;
055    import org.jboss.dna.repository.util.SessionFactory;
056    import org.jboss.dna.repository.util.SimpleSessionFactory;
057    
058    /**
059     * @author Randall Hauch
060     */
061    public class SequencingClient {
062    
063        public static final String DEFAULT_JACKRABBIT_CONFIG_PATH = "jackrabbitConfig.xml";
064        public static final String DEFAULT_WORKING_DIRECTORY = "repositoryData";
065        public static final String DEFAULT_REPOSITORY_NAME = "repo";
066        public static final String DEFAULT_WORKSPACE_NAME = "default";
067        public static final String DEFAULT_USERNAME = "jsmith";
068        public static final char[] DEFAULT_PASSWORD = "secret".toCharArray();
069    
070        public static void main( String[] args ) {
071            SequencingClient client = new SequencingClient();
072            client.setRepositoryInformation(DEFAULT_REPOSITORY_NAME, DEFAULT_WORKSPACE_NAME, DEFAULT_USERNAME, DEFAULT_PASSWORD);
073            client.setUserInterface(new ConsoleInput(client));
074        }
075    
076        private String repositoryName;
077        private String workspaceName;
078        private String username;
079        private char[] password;
080        private String jackrabbitConfigPath;
081        private String workingDirectory;
082        private Session keepAliveSession;
083        private Repository repository;
084        private SequencingService sequencingService;
085        private ObservationService observationService;
086        private UserInterface userInterface;
087        private JcrExecutionContext executionContext;
088    
089        public SequencingClient() {
090            setJackrabbitConfigPath(DEFAULT_JACKRABBIT_CONFIG_PATH);
091            setWorkingDirectory(DEFAULT_WORKING_DIRECTORY);
092            setRepositoryInformation(DEFAULT_REPOSITORY_NAME, DEFAULT_WORKSPACE_NAME, DEFAULT_USERNAME, DEFAULT_PASSWORD);
093        }
094    
095        protected void setWorkingDirectory( String workingDirectoryPath ) {
096            this.workingDirectory = workingDirectoryPath != null ? workingDirectoryPath : DEFAULT_WORKING_DIRECTORY;
097        }
098    
099        protected void setJackrabbitConfigPath( String jackrabbitConfigPath ) {
100            this.jackrabbitConfigPath = jackrabbitConfigPath != null ? jackrabbitConfigPath : DEFAULT_JACKRABBIT_CONFIG_PATH;
101        }
102    
103        protected void setRepositoryInformation( String repositoryName,
104                                                 String workspaceName,
105                                                 String username,
106                                                 char[] password ) {
107            if (this.repository != null) {
108                throw new IllegalArgumentException("Unable to set repository information when repository is already running");
109            }
110            this.repositoryName = repositoryName != null ? repositoryName : DEFAULT_REPOSITORY_NAME;
111            this.workspaceName = workspaceName != null ? workspaceName : DEFAULT_WORKSPACE_NAME;
112            this.username = username;
113            this.password = password;
114        }
115    
116        /**
117         * Set the user interface that this client should use.
118         * 
119         * @param userInterface
120         */
121        public void setUserInterface( UserInterface userInterface ) {
122            this.userInterface = userInterface;
123        }
124    
125        /**
126         * Start up the JCR repository. This method only operates using the JCR API and Jackrabbit-specific API.
127         * 
128         * @throws Exception
129         */
130        public void startRepository() throws Exception {
131            if (this.repository == null) {
132                try {
133    
134                    // Load the Jackrabbit configuration ...
135                    File configFile = new File(this.jackrabbitConfigPath);
136                    if (!configFile.exists()) {
137                        throw new SystemFailureException("The Jackrabbit configuration file cannot be found at "
138                                                         + configFile.getAbsoluteFile());
139                    }
140                    if (!configFile.canRead()) {
141                        throw new SystemFailureException("Unable to read the Jackrabbit configuration file at "
142                                                         + configFile.getAbsoluteFile());
143                    }
144                    String pathToConfig = configFile.getAbsolutePath();
145    
146                    // Find the directory where the Jackrabbit repository data will be stored ...
147                    File workingDirectory = new File(this.workingDirectory);
148                    if (workingDirectory.exists()) {
149                        if (!workingDirectory.isDirectory()) {
150                            throw new SystemFailureException("Unable to create working directory at "
151                                                             + workingDirectory.getAbsolutePath());
152                        }
153                    }
154                    String workingDirectoryPath = workingDirectory.getAbsolutePath();
155    
156                    // Get the Jackrabbit custom node definition (CND) file ...
157                    URL cndFile = Thread.currentThread().getContextClassLoader().getResource("jackrabbitNodeTypes.cnd");
158    
159                    // Create the Jackrabbit repository instance and establish a session to keep the repository alive ...
160                    this.repository = new TransientRepository(pathToConfig, workingDirectoryPath);
161                    if (this.username != null) {
162                        Credentials credentials = new SimpleCredentials(this.username, this.password);
163                        this.keepAliveSession = this.repository.login(credentials, this.workspaceName);
164                    } else {
165                        this.keepAliveSession = this.repository.login();
166                    }
167    
168                    try {
169                        // Register the node types (only valid the first time) ...
170                        JackrabbitNodeTypeManager mgr = (JackrabbitNodeTypeManager)this.keepAliveSession.getWorkspace().getNodeTypeManager();
171                        mgr.registerNodeTypes(cndFile.openStream(), JackrabbitNodeTypeManager.TEXT_X_JCR_CND);
172                    } catch (RepositoryException e) {
173                        if (!e.getMessage().contains("already exists")) throw e;
174                    }
175    
176                } catch (Exception e) {
177                    this.repository = null;
178                    this.keepAliveSession = null;
179                    throw e;
180                }
181            }
182        }
183    
184        /**
185         * Shutdown the repository. This method only uses the JCR API.
186         * 
187         * @throws Exception
188         */
189        public void shutdownRepository() throws Exception {
190            if (this.repository != null) {
191                try {
192                    this.keepAliveSession.logout();
193                } finally {
194                    this.repository = null;
195                    this.keepAliveSession = null;
196                }
197            }
198        }
199    
200        /**
201         * Start the DNA services.
202         * 
203         * @throws Exception
204         */
205        public void startDnaServices() throws Exception {
206            if (this.repository == null) {
207                this.startRepository();
208            }
209            if (this.sequencingService == null) {
210    
211                // Create an execution context for the sequencing service. This execution context provides an environment
212                // for the DNA services which knows about the JCR repositories, workspaces, and credentials used to
213                // establish sessions to these workspaces. This example uses the BasicJcrExecutionContext, but there is
214                // implementation for use with JCR repositories registered in JNDI.
215                final String repositoryWorkspaceName = this.repositoryName + "/" + this.workspaceName;
216                SimpleSessionFactory sessionFactory = new SimpleSessionFactory();
217                sessionFactory.registerRepository(this.repositoryName, this.repository);
218                if (this.username != null) {
219                    Credentials credentials = new SimpleCredentials(this.username, this.password);
220                    sessionFactory.registerCredentials(repositoryWorkspaceName, credentials);
221                }
222                this.executionContext = new BasicJcrExecutionContext(sessionFactory, repositoryWorkspaceName);
223    
224                // Create the sequencing service, passing in the execution context ...
225                this.sequencingService = new SequencingService();
226                this.sequencingService.setExecutionContext(executionContext);
227    
228                // Configure the sequencers. In this example, we only two sequencers that processes image and mp3 files.
229                // So create a configurations. Note that the sequencing service expects the class to be on the thread's current
230                // context
231                // classloader, or if that's null the classloader that loaded the SequencingService class.
232                //
233                // Part of the configuration includes telling DNA which JCR paths should be processed by the sequencer.
234                // These path expressions tell the service that this sequencer should be invoked on the "jcr:data" property
235                // on the "jcr:content" child node of any node uploaded to the repository whose name ends with one of the
236                // supported extensions, and the sequencer should place the generated output metadata in a node with the same name as
237                // the file but immediately below the "/images" node. Path expressions can be fairly complex, and can even
238                // specify that the generated information be placed in a different repository.
239                // 
240                // Sequencer configurations can be added before or after the service is started, but here we do it before the service
241                // is running.
242                String name = "Image Sequencer";
243                String desc = "Sequences image files to extract the characteristics of the image";
244                String classname = "org.jboss.dna.sequencer.images.ImageMetadataSequencer";
245                String[] classpath = null; // Use the current classpath
246                String[] pathExpressions = {"//(*.(jpg|jpeg|gif|bmp|pcx|png|iff|ras|pbm|pgm|ppm|psd)[*])/jcr:content[@jcr:data] => /images/$1"};
247                SequencerConfig imageSequencerConfig = new SequencerConfig(name, desc, classname, classpath, pathExpressions);
248                this.sequencingService.addSequencer(imageSequencerConfig);
249    
250                // Set up the MP3 sequencer ...
251                name = "Mp3 Sequencer";
252                desc = "Sequences mp3 files to extract the id3 tags of the audio file";
253                classname = "org.jboss.dna.sequencer.mp3.Mp3MetadataSequencer";
254                String[] mp3PathExpressions = {"//(*.mp3[*])/jcr:content[@jcr:data] => /mp3s/$1"};
255                SequencerConfig mp3SequencerConfig = new SequencerConfig(name, desc, classname, classpath, mp3PathExpressions);
256                this.sequencingService.addSequencer(mp3SequencerConfig);
257    
258                // Set up the MP3 sequencer ...
259                name = "Java Sequencer";
260                desc = "Sequences java files to extract the characteristics of the java sources";
261                classname = "org.jboss.dna.sequencer.java.JavaMetadataSequencer";
262                String[] javaPathExpressions = {"//(*.java[*])/jcr:content[@jcr:data] => /java/$1"};
263                SequencerConfig javaSequencerConfig = new SequencerConfig(name, desc, classname, classpath, javaPathExpressions);
264                this.sequencingService.addSequencer(javaSequencerConfig);
265    
266                // Use the DNA observation service to listen to the JCR repository (or multiple ones), and
267                // then register the sequencing service as a listener to this observation service...
268                this.observationService = new ObservationService(this.executionContext.getSessionFactory());
269                this.observationService.getAdministrator().start();
270                this.observationService.addListener(this.sequencingService);
271                this.observationService.monitor(repositoryWorkspaceName, Event.NODE_ADDED | Event.PROPERTY_ADDED
272                                                                         | Event.PROPERTY_CHANGED);
273            }
274            // Start up the sequencing service ...
275            this.sequencingService.getAdministrator().start();
276        }
277    
278        /**
279         * Shut down the DNA services.
280         * 
281         * @throws Exception
282         */
283        public void shutdownDnaServices() throws Exception {
284            if (this.sequencingService == null) return;
285    
286            // Shut down the service and wait until it's all shut down ...
287            this.sequencingService.getAdministrator().shutdown();
288            this.sequencingService.getAdministrator().awaitTermination(5, TimeUnit.SECONDS);
289    
290            // Shut down the observation service ...
291            this.observationService.getAdministrator().shutdown();
292            this.observationService.getAdministrator().awaitTermination(5, TimeUnit.SECONDS);
293        }
294    
295        /**
296         * Get the sequencing statistics.
297         * 
298         * @return the statistics; never null
299         */
300        public SequencingService.Statistics getStatistics() {
301            return this.sequencingService.getStatistics();
302        }
303    
304        /**
305         * Prompt the user interface for the file to upload into the JCR repository, then upload it using the JCR API.
306         * 
307         * @throws Exception
308         */
309        public void uploadFile() throws Exception {
310            URL url = this.userInterface.getFileToUpload();
311            // Grab the last segment of the URL path, using it as the filename
312            String filename = url.getPath().replaceAll("([^/]*/)*", "");
313            String nodePath = this.userInterface.getRepositoryPath("/a/b/" + filename);
314            String mimeType = getMimeType(url);
315    
316            // Now use the JCR API to upload the file ...
317            Session session = createSession();
318            JcrTools tools = this.executionContext.getTools();
319            try {
320                // Create the node at the supplied path ...
321                Node node = tools.findOrCreateNode(session, nodePath, "nt:folder", "nt:file");
322    
323                // Upload the file to that node ...
324                Node contentNode = tools.findOrCreateChild(session, node, "jcr:content", "nt:resource");
325                contentNode.setProperty("jcr:mimeType", mimeType);
326                contentNode.setProperty("jcr:lastModified", Calendar.getInstance());
327                contentNode.setProperty("jcr:data", url.openStream());
328    
329                // Save the session ...
330                session.save();
331            } finally {
332                session.logout();
333            }
334        }
335    
336        /**
337         * Perform a search of the repository for all image metadata automatically created by the image sequencer.
338         * 
339         * @throws Exception
340         */
341        public void search() throws Exception {
342            // Use JCR to search the repository for image metadata ...
343            List<ContentInfo> infos = new ArrayList<ContentInfo>();
344            Session session = createSession();
345            try {
346                // Find the node ...
347                Node root = session.getRootNode();
348    
349                if (root.hasNode("images") || root.hasNode("mp3s")) {
350                    Node mediasNode;
351                    if (root.hasNode("images")) {
352                        mediasNode = root.getNode("images");
353    
354                        for (NodeIterator iter = mediasNode.getNodes(); iter.hasNext();) {
355                            Node mediaNode = iter.nextNode();
356                            if (mediaNode.hasNode("image:metadata")) {
357                                infos.add(extractMediaInfo("image:metadata", "image", mediaNode));
358                            }
359                        }
360                    }
361                    if (root.hasNode("mp3s")) {
362                        mediasNode = root.getNode("mp3s");
363    
364                        for (NodeIterator iter = mediasNode.getNodes(); iter.hasNext();) {
365                            Node mediaNode = iter.nextNode();
366                            if (mediaNode.hasNode("mp3:metadata")) {
367                                infos.add(extractMediaInfo("mp3:metadata", "mp3", mediaNode));
368                            }
369                        }
370                    }
371    
372                }
373                if (root.hasNode("java")) {
374                    Map<String, List<Properties>> tree = new TreeMap<String, List<Properties>>();
375                    // Find the compilation unit node ...
376                    List<Properties> javaElements;
377                    if (root.hasNode("java")) {
378                        Node javaSourcesNode = root.getNode("java");
379                        for (NodeIterator i = javaSourcesNode.getNodes(); i.hasNext();) {
380    
381                            Node javaSourceNode = i.nextNode();
382    
383                            if (javaSourceNode.hasNodes()) {
384                                Node javaCompilationUnit = javaSourceNode.getNodes().nextNode();
385                                // package informations
386    
387                                javaElements = new ArrayList<Properties>();
388                                try {
389                                    Node javaPackageDeclarationNode = javaCompilationUnit.getNode("java:package/java:packageDeclaration");
390                                    javaElements.add(extractJavaInfo(javaPackageDeclarationNode));
391                                    tree.put("Class package", javaElements);
392                                } catch (PathNotFoundException e) {
393                                    // do nothing
394                                }
395    
396                                // import informations
397                                javaElements = new ArrayList<Properties>();
398                                try {
399                                    for (NodeIterator singleImportIterator = javaCompilationUnit.getNode("java:import/java:importDeclaration/java:singleImport").getNodes(); singleImportIterator.hasNext();) {
400                                        Node javasingleTypeImportDeclarationNode = singleImportIterator.nextNode();
401                                        javaElements.add(extractJavaInfo(javasingleTypeImportDeclarationNode));
402                                    }
403                                    tree.put("Class single Imports", javaElements);
404                                } catch (PathNotFoundException e) {
405                                    // do nothing
406                                }
407    
408                                javaElements = new ArrayList<Properties>();
409                                try {
410                                    for (NodeIterator javaImportOnDemandIterator = javaCompilationUnit.getNode("java:import/java:importDeclaration/java:importOnDemand").getNodes(); javaImportOnDemandIterator.hasNext();) {
411                                        Node javaImportOnDemandtDeclarationNode = javaImportOnDemandIterator.nextNode();
412                                        javaElements.add(extractJavaInfo(javaImportOnDemandtDeclarationNode));
413                                    }
414                                    tree.put("Class on demand imports", javaElements);
415    
416                                } catch (PathNotFoundException e) {
417                                    // do nothing
418                                }
419                                // class head informations
420                                javaElements = new ArrayList<Properties>();
421                                Node javaNormalDeclarationClassNode = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration");
422                                javaElements.add(extractJavaInfo(javaNormalDeclarationClassNode));
423                                tree.put("Class head information", javaElements);
424    
425                                // field member informations
426                                javaElements = new ArrayList<Properties>();
427                                for (NodeIterator javaFieldTypeIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:field/java:fieldType").getNodes(); javaFieldTypeIterator.hasNext();) {
428                                    Node rootFieldTypeNode = javaFieldTypeIterator.nextNode();
429                                    if (rootFieldTypeNode.hasNode("java:primitiveType")) {
430                                        Node javaPrimitiveTypeNode = rootFieldTypeNode.getNode("java:primitiveType");
431                                        javaElements.add(extractJavaInfo(javaPrimitiveTypeNode));
432                                        // more informations
433                                    }
434    
435                                    if (rootFieldTypeNode.hasNode("java:simpleType")) {
436                                        Node javaSimpleTypeNode = rootFieldTypeNode.getNode("java:simpleType");
437                                        javaElements.add(extractJavaInfo(javaSimpleTypeNode));
438                                    }
439                                    if (rootFieldTypeNode.hasNode("java:parameterizedType")) {
440                                        Node javaParameterizedType = rootFieldTypeNode.getNode("java:parameterizedType");
441                                        javaElements.add(extractJavaInfo(javaParameterizedType));
442                                    }
443                                    if (rootFieldTypeNode.hasNode("java:arrayType")) {
444                                        Node javaArrayType = rootFieldTypeNode.getNode("java:arrayType[2]");
445                                        javaElements.add(extractJavaInfo(javaArrayType));
446                                    }
447                                }
448                                tree.put("Class field members", javaElements);
449    
450                                // constructor informations
451                                javaElements = new ArrayList<Properties>();
452                                for (NodeIterator javaConstructorIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:constructor").getNodes(); javaConstructorIterator.hasNext();) {
453                                    Node javaConstructor = javaConstructorIterator.nextNode();
454                                    javaElements.add(extractJavaInfo(javaConstructor));
455                                }
456                                tree.put("Class constructors", javaElements);
457    
458                                // method informations
459                                javaElements = new ArrayList<Properties>();
460                                for (NodeIterator javaMethodIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:method").getNodes(); javaMethodIterator.hasNext();) {
461                                    Node javaMethod = javaMethodIterator.nextNode();
462                                    javaElements.add(extractJavaInfo(javaMethod));
463                                }
464                                tree.put("Class member functions", javaElements);
465    
466                                JavaInfo javaInfo = new JavaInfo(javaCompilationUnit.getPath(), javaCompilationUnit.getName(),
467                                                                 "java source", tree);
468                                infos.add(javaInfo);
469                            }
470                        }
471                    }
472    
473                }
474            } finally {
475                session.logout();
476            }
477    
478            // Display the search results ...
479            this.userInterface.displaySearchResults(infos);
480        }
481    
482        private MediaInfo extractMediaInfo( String metadataNodeName,
483                                            String mediaType,
484                                            Node mediaNode ) throws RepositoryException, PathNotFoundException, ValueFormatException {
485            String nodePath = mediaNode.getPath();
486            String nodeName = mediaNode.getName();
487            mediaNode = mediaNode.getNode(metadataNodeName);
488    
489            // Create a Properties object containing the properties for this node; ignore any children ...
490            Properties props = new Properties();
491            for (PropertyIterator propertyIter = mediaNode.getProperties(); propertyIter.hasNext();) {
492                Property property = propertyIter.nextProperty();
493                String name = property.getName();
494                String stringValue = null;
495                if (property.getDefinition().isMultiple()) {
496                    StringBuilder sb = new StringBuilder();
497                    boolean first = true;
498                    for (Value value : property.getValues()) {
499                        if (!first) {
500                            sb.append(", ");
501                            first = false;
502                        }
503                        sb.append(value.getString());
504                    }
505                    stringValue = sb.toString();
506                } else {
507                    stringValue = property.getValue().getString();
508                }
509                props.put(name, stringValue);
510            }
511            // Create the image information object, and add it to the collection ...
512            return new MediaInfo(nodePath, nodeName, mediaType, props);
513        }
514    
515        /**
516         * Extract informations from a specific node.
517         * 
518         * @param node - node, that contains informations.
519         * @return a properties of keys/values.
520         * @throws RepositoryException
521         * @throws IllegalStateException
522         * @throws ValueFormatException
523         */
524        private Properties extractJavaInfo( Node node ) throws ValueFormatException, IllegalStateException, RepositoryException {
525            if (node.hasProperties()) {
526                Properties properties = new Properties();
527                for (PropertyIterator propertyIter = node.getProperties(); propertyIter.hasNext();) {
528                    Property property = propertyIter.nextProperty();
529                    String name = property.getName();
530                    String stringValue = property.getValue().getString();
531                    properties.put(name, stringValue);
532                }
533                return properties;
534            }
535            return null;
536        }
537    
538        /**
539         * Utility method to create a new JCR session from the execution context's {@link SessionFactory}.
540         * 
541         * @return the session
542         * @throws RepositoryException
543         */
544        protected Session createSession() throws RepositoryException {
545            return this.executionContext.getSessionFactory().createSession(this.repositoryName + "/" + this.workspaceName);
546        }
547    
548        protected String getMimeType( URL file ) {
549            String filename = file.getPath().toLowerCase();
550            if (filename.endsWith(".gif")) return "image/gif";
551            if (filename.endsWith(".png")) return "image/png";
552            if (filename.endsWith(".pict")) return "image/x-pict";
553            if (filename.endsWith(".bmp")) return "image/bmp";
554            if (filename.endsWith(".jpg")) return "image/jpeg";
555            if (filename.endsWith(".jpe")) return "image/jpeg";
556            if (filename.endsWith(".jpeg")) return "image/jpeg";
557            if (filename.endsWith(".ras")) return "image/x-cmu-raster";
558            if (filename.endsWith(".mp3")) return "audio/mpeg";
559            if (filename.endsWith(".java")) return "text/x-java-source";
560            throw new SystemFailureException("Unknown mime type for " + file);
561        }
562    
563    }