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                } else if (root.hasNode("java")) {
373                    Map<String, List<Properties>> tree = new TreeMap<String, List<Properties>>();
374                    // Find the compilation unit node ...
375                    List<Properties> javaElements;
376                    if (root.hasNode("java")) {
377                        Node javaSourcesNode = root.getNode("java");
378                        for (NodeIterator i = javaSourcesNode.getNodes(); i.hasNext();) {
379    
380                            Node javaSourceNode = i.nextNode();
381    
382                            if (javaSourceNode.hasNodes()) {
383                                Node javaCompilationUnit = javaSourceNode.getNodes().nextNode();
384                                // package informations
385    
386                                javaElements = new ArrayList<Properties>();
387                                try {
388                                    Node javaPackageDeclarationNode = javaCompilationUnit.getNode("java:package/java:packageDeclaration");
389                                    javaElements.add(extractJavaInfo(javaPackageDeclarationNode));
390                                    tree.put("Class package", javaElements);
391                                } catch (PathNotFoundException e) {
392                                    // do nothing
393                                }
394    
395                                // import informations
396                                javaElements = new ArrayList<Properties>();
397                                try {
398                                    for (NodeIterator singleImportIterator = javaCompilationUnit.getNode("java:import/java:importDeclaration/java:singleImport").getNodes(); singleImportIterator.hasNext();) {
399                                        Node javasingleTypeImportDeclarationNode = singleImportIterator.nextNode();
400                                        javaElements.add(extractJavaInfo(javasingleTypeImportDeclarationNode));
401                                    }
402                                    tree.put("Class single Imports", javaElements);
403                                } catch (PathNotFoundException e) {
404                                    // do nothing
405                                }
406    
407                                javaElements = new ArrayList<Properties>();
408                                try {
409                                    for (NodeIterator javaImportOnDemandIterator = javaCompilationUnit.getNode("java:import/java:importDeclaration/java:importOnDemand").getNodes(); javaImportOnDemandIterator.hasNext();) {
410                                        Node javaImportOnDemandtDeclarationNode = javaImportOnDemandIterator.nextNode();
411                                        javaElements.add(extractJavaInfo(javaImportOnDemandtDeclarationNode));
412                                    }
413                                    tree.put("Class on demand imports", javaElements);
414    
415                                } catch (PathNotFoundException e) {
416                                    // do nothing
417                                }
418                                // class head informations
419                                javaElements = new ArrayList<Properties>();
420                                Node javaNormalDeclarationClassNode = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration");
421                                javaElements.add(extractJavaInfo(javaNormalDeclarationClassNode));
422                                tree.put("Class head information", javaElements);
423    
424                                // field member informations
425                                javaElements = new ArrayList<Properties>();
426                                for (NodeIterator javaFieldTypeIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:field/java:fieldType").getNodes(); javaFieldTypeIterator.hasNext();) {
427                                    Node rootFieldTypeNode = javaFieldTypeIterator.nextNode();
428                                    if (rootFieldTypeNode.hasNode("java:primitiveType")) {
429                                        Node javaPrimitiveTypeNode = rootFieldTypeNode.getNode("java:primitiveType");
430                                        javaElements.add(extractJavaInfo(javaPrimitiveTypeNode));
431                                        // more informations
432                                    }
433    
434                                    if (rootFieldTypeNode.hasNode("java:simpleType")) {
435                                        Node javaSimpleTypeNode = rootFieldTypeNode.getNode("java:simpleType");
436                                        javaElements.add(extractJavaInfo(javaSimpleTypeNode));
437                                    }
438                                    if (rootFieldTypeNode.hasNode("java:parameterizedType")) {
439                                        Node javaParameterizedType = rootFieldTypeNode.getNode("java:parameterizedType");
440                                        javaElements.add(extractJavaInfo(javaParameterizedType));
441                                    }
442                                    if (rootFieldTypeNode.hasNode("java:arrayType")) {
443                                        Node javaArrayType = rootFieldTypeNode.getNode("java:arrayType[2]");
444                                        javaElements.add(extractJavaInfo(javaArrayType));
445                                    }
446                                }
447                                tree.put("Class field members", javaElements);
448    
449                                // constructor informations
450                                javaElements = new ArrayList<Properties>();
451                                for (NodeIterator javaConstructorIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:constructor").getNodes(); javaConstructorIterator.hasNext();) {
452                                    Node javaConstructor = javaConstructorIterator.nextNode();
453                                    javaElements.add(extractJavaInfo(javaConstructor));
454                                }
455                                tree.put("Class constructors", javaElements);
456    
457                                // method informations
458                                javaElements = new ArrayList<Properties>();
459                                for (NodeIterator javaMethodIterator = javaCompilationUnit.getNode("java:unitType/java:classDeclaration/java:normalClass/java:normalClassDeclaration/java:method").getNodes(); javaMethodIterator.hasNext();) {
460                                    Node javaMethod = javaMethodIterator.nextNode();
461                                    javaElements.add(extractJavaInfo(javaMethod));
462                                }
463                                tree.put("Class member functions", javaElements);
464    
465                                JavaInfo javaInfo = new JavaInfo(javaCompilationUnit.getPath(), javaCompilationUnit.getName(),
466                                                                 "java source", tree);
467                                infos.add(javaInfo);
468                            }
469                        }
470                    }
471    
472                }
473            } finally {
474                session.logout();
475            }
476    
477            // Display the search results ...
478            this.userInterface.displaySearchResults(infos);
479        }
480    
481        private MediaInfo extractMediaInfo( String metadataNodeName,
482                                            String mediaType,
483                                            Node mediaNode ) throws RepositoryException, PathNotFoundException, ValueFormatException {
484            String nodePath = mediaNode.getPath();
485            String nodeName = mediaNode.getName();
486            mediaNode = mediaNode.getNode(metadataNodeName);
487    
488            // Create a Properties object containing the properties for this node; ignore any children ...
489            Properties props = new Properties();
490            for (PropertyIterator propertyIter = mediaNode.getProperties(); propertyIter.hasNext();) {
491                Property property = propertyIter.nextProperty();
492                String name = property.getName();
493                String stringValue = null;
494                if (property.getDefinition().isMultiple()) {
495                    StringBuilder sb = new StringBuilder();
496                    boolean first = true;
497                    for (Value value : property.getValues()) {
498                        if (!first) {
499                            sb.append(", ");
500                            first = false;
501                        }
502                        sb.append(value.getString());
503                    }
504                    stringValue = sb.toString();
505                } else {
506                    stringValue = property.getValue().getString();
507                }
508                props.put(name, stringValue);
509            }
510            // Create the image information object, and add it to the collection ...
511            return new MediaInfo(nodePath, nodeName, mediaType, props);
512        }
513    
514        /**
515         * Extract informations from a specific node.
516         * 
517         * @param node - node, that contains informations.
518         * @return a properties of keys/values.
519         * @throws RepositoryException
520         * @throws IllegalStateException
521         * @throws ValueFormatException
522         */
523        private Properties extractJavaInfo( Node node ) throws ValueFormatException, IllegalStateException, RepositoryException {
524            if (node.hasProperties()) {
525                Properties properties = new Properties();
526                for (PropertyIterator propertyIter = node.getProperties(); propertyIter.hasNext();) {
527                    Property property = propertyIter.nextProperty();
528                    String name = property.getName();
529                    String stringValue = property.getValue().getString();
530                    properties.put(name, stringValue);
531                }
532                return properties;
533            }
534            return null;
535        }
536    
537        /**
538         * Utility method to create a new JCR session from the execution context's {@link SessionFactory}.
539         * 
540         * @return the session
541         * @throws RepositoryException
542         */
543        protected Session createSession() throws RepositoryException {
544            return this.executionContext.getSessionFactory().createSession(this.repositoryName + "/" + this.workspaceName);
545        }
546    
547        protected String getMimeType( URL file ) {
548            String filename = file.getPath().toLowerCase();
549            if (filename.endsWith(".gif")) return "image/gif";
550            if (filename.endsWith(".png")) return "image/png";
551            if (filename.endsWith(".pict")) return "image/x-pict";
552            if (filename.endsWith(".bmp")) return "image/bmp";
553            if (filename.endsWith(".jpg")) return "image/jpeg";
554            if (filename.endsWith(".jpe")) return "image/jpeg";
555            if (filename.endsWith(".jpeg")) return "image/jpeg";
556            if (filename.endsWith(".ras")) return "image/x-cmu-raster";
557            if (filename.endsWith(".mp3")) return "audio/mpeg";
558            if (filename.endsWith(".java")) return "text/x-java-source";
559            throw new SystemFailureException("Unknown mime type for " + file);
560        }
561    
562    }