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