/*************************************************************************** * Copyright (C) 2008-2012 by Fabrizio Montesi <famontesi@gmail.com> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * * For details about the authors of this software, see the AUTHORS file. * ***************************************************************************/ package joliex.io; import com.sun.xml.xsom.XSSchemaSet; import com.sun.xml.xsom.XSType; import com.sun.xml.xsom.parser.XSOMParser; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.Writer; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Comparator; import java.util.Enumeration; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.activation.FileTypeMap; import javax.activation.MimetypesFileTypeMap; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import jolie.jap.JapURLConnection; import jolie.net.http.json.JsonUtils; import jolie.runtime.AndJarDeps; import jolie.runtime.ByteArray; import jolie.runtime.FaultException; import jolie.runtime.JavaService; import jolie.runtime.Value; import jolie.runtime.ValueVector; import jolie.runtime.embedding.RequestResponse; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * * @author Fabrizio Montesi */ @AndJarDeps( {"jolie-xml.jar", "xsom.jar"} ) public class FileService extends JavaService { private final static Pattern fileKeywordPattern = Pattern.compile( "(#+)file\\s+(.*)" ); private final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); private final TransformerFactory transformerFactory = TransformerFactory.newInstance(); private FileTypeMap fileTypeMap = FileTypeMap.getDefaultFileTypeMap(); public FileService() { super(); documentBuilderFactory.setIgnoringElementContentWhitespace( true ); } @RequestResponse public String convertFromBinaryToBase64Value( Value value ) { byte[] buffer = value.byteArrayValue().getBytes(); sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder(); return encoder.encode( buffer ); } @RequestResponse public ByteArray convertFromBase64ToBinaryValue( Value value ) throws FaultException { ByteArray returnValue = null; try { String stringValue = value.strValue(); sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder(); byte[] supportArray = decoder.decodeBuffer( stringValue ); returnValue = new ByteArray( supportArray ); return returnValue; } catch( IOException ex ) { throw new FaultException( "IOException", ex ); } } @RequestResponse public void setMimeTypeFile( String filename ) throws FaultException { try { fileTypeMap = new MimetypesFileTypeMap( filename ); } catch( IOException e ) { throw new FaultException( "IOException", e ); } } private static void readBase64IntoValue( InputStream istream, long size, Value value ) throws IOException { byte[] buffer = new byte[(int) size]; istream.read( buffer ); sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder(); value.setValue( encoder.encode( buffer ) ); } private static void readBinaryIntoValue( InputStream istream, long size, Value value ) throws IOException { byte[] buffer = new byte[(int) size]; istream.read( buffer ); value.setValue( new ByteArray( buffer ) ); } private void readJsonIntoValue( InputStream istream, Value value, Charset charset ) throws IOException { InputStreamReader isr; if ( charset == null ) { isr = new InputStreamReader( istream ); } else { isr = new InputStreamReader( istream, charset ); } Reader r = new BufferedReader( isr ); JsonUtils.parseJsonIntoValue( r, value ); } private void readXMLIntoValue( InputStream istream, Value value ) throws IOException { try { DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); InputSource src = new InputSource( new InputStreamReader( istream ) ); Document doc = builder.parse( src ); value = value.getFirstChild( doc.getDocumentElement().getNodeName() ); jolie.xml.XmlUtils.documentToValue( doc, value ); } catch( ParserConfigurationException e ) { throw new IOException( e ); } catch( SAXException e ) { throw new IOException( e ); } } private void readXMLIntoValueForStoring( InputStream istream, Value value ) throws IOException { try { DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); InputSource src = new InputSource( new InputStreamReader( istream ) ); Document doc = builder.parse( src ); value = value.getFirstChild( doc.getDocumentElement().getNodeName() ); jolie.xml.XmlUtils.storageDocumentToValue( doc, value ); } catch( ParserConfigurationException e ) { throw new IOException( e ); } catch( SAXException e ) { throw new IOException( e ); } } private static void readTextIntoValue( InputStream istream, long size, Value value, Charset charset ) throws IOException { byte[] buffer = new byte[(int) size]; istream.read( buffer ); istream.close(); if ( charset == null ) { value.setValue( new String( buffer ) ); } else { value.setValue( new String( buffer, charset ) ); } } private void readPropertiesFile( InputStream istream, Value value ) throws IOException { Properties properties = new Properties(); properties.load( new InputStreamReader( istream ) ); Enumeration< String> names = (Enumeration< String>) properties.propertyNames(); String name; String propertyValue; Matcher matcher; while( names.hasMoreElements() ) { name = names.nextElement(); propertyValue = properties.getProperty( name ); matcher = fileKeywordPattern.matcher( propertyValue ); if ( matcher.matches() ) { if ( matcher.group( 1 ).length() > 1 ) { // The number of # propertyValue = propertyValue.substring( 1 ); } else { // It's a #file directive // TODO: this is a bit of a hack. We should have a private // method for performing all the lookups of files into // JAPs, local directories etc. instead of calling readFile // again. Value request = Value.create(); request.getFirstChild( "filename" ).setValue( matcher.group( 2 ) ); request.getFirstChild( "format" ).setValue( "text" ); try { propertyValue = readFile( request ).strValue(); } catch( FaultException e ) { throw new IOException( e ); } } } value.getFirstChild( name ).setValue( propertyValue ); } } public Value readFile( Value request ) throws FaultException { Value filenameValue = request.getFirstChild( "filename" ); Value retValue = Value.create(); String format = request.getFirstChild( "format" ).strValue(); File file = new File( filenameValue.strValue() ); InputStream istream = null; long size; try { if ( file.exists() ) { istream = new FileInputStream( file ); size = file.length(); } else { URL fileURL = interpreter().getClassLoader().findResource( filenameValue.strValue() ); if ( fileURL != null && fileURL.getProtocol().equals( "jap" ) ) { URLConnection conn = fileURL.openConnection(); if ( conn instanceof JapURLConnection ) { JapURLConnection jarConn = (JapURLConnection) conn; size = jarConn.getEntrySize(); if ( size < 0 ) { throw new IOException( "File dimension is negative for file " + fileURL.toString() ); } istream = jarConn.getInputStream(); } else { throw new FileNotFoundException( filenameValue.strValue() ); } } else { throw new FileNotFoundException( filenameValue.strValue() ); } } istream = new BufferedInputStream( istream ); try { if ( "base64".equals( format ) ) { readBase64IntoValue( istream, size, retValue ); } else if ( "binary".equals( format ) ) { readBinaryIntoValue( istream, size, retValue ); } else if ( "xml".equals( format ) ) { readXMLIntoValue( istream, retValue ); } else if ( "xml_store".equals( format ) ) { readXMLIntoValueForStoring( istream, retValue ); } else if ( "properties".equals( format ) ) { readPropertiesFile( istream, retValue ); } else if ( "json".equals( format ) ) { Charset charset = null; Value formatValue = request.getFirstChild( "format" ); if ( formatValue.hasChildren( "charset" ) ) { charset = Charset.forName( formatValue.getFirstChild( "charset" ).strValue() ); } readJsonIntoValue( istream, retValue, charset ); } else { Charset charset = null; Value formatValue = request.getFirstChild( "format" ); if ( formatValue.hasChildren( "charset" ) ) { charset = Charset.forName( formatValue.getFirstChild( "charset" ).strValue() ); } readTextIntoValue( istream, size, retValue, charset ); } } finally { istream.close(); } } catch( FileNotFoundException e ) { throw new FaultException( "FileNotFound", e ); } catch( IOException e ) { throw new FaultException( "IOException", e ); } return retValue; } public Boolean exists( String filename ) { return (new File( filename ).exists()) ? true : false; } public Boolean mkdir( String directory ) { return (new File( directory ).mkdirs()) ? true : false; } public String getMimeType( String filename ) throws FaultException { File file = new File( filename ); if ( file.exists() == false ) { throw new FaultException( "FileNotFound", filename ); } return fileTypeMap.getContentType( file ); } public String getServiceDirectory() { String dir = null; try { dir = interpreter().programDirectory().getCanonicalPath(); } catch( IOException e ) { e.printStackTrace(); } if ( dir == null || dir.isEmpty() ) { dir = "."; } return dir; } public String getFileSeparator() { return jolie.lang.Constants.fileSeparator; } private void writeXML( File file, Value value, boolean append, String schemaFilename, String doctypeSystem, boolean indent ) throws IOException { if ( value.children().isEmpty() ) { return; // TODO: perhaps we should erase the content of the file before returning. } String rootName = value.children().keySet().iterator().next(); try { XSType type = null; if ( schemaFilename != null ) { try { XSOMParser parser = new XSOMParser(); parser.parse( schemaFilename ); XSSchemaSet schemaSet = parser.getResult(); if ( schemaSet != null ) { type = schemaSet.getElementDecl( "", rootName ).getType(); } } catch( SAXException e ) { throw new IOException( e ); } } Document doc = documentBuilderFactory.newDocumentBuilder().newDocument(); if ( type == null ) { jolie.xml.XmlUtils.valueToDocument( value.getFirstChild( rootName ), rootName, doc ); } else { jolie.xml.XmlUtils.valueToDocument( value.getFirstChild( rootName ), rootName, doc, type ); } Transformer transformer = transformerFactory.newTransformer(); if ( indent ) { transformer.setOutputProperty( OutputKeys.INDENT, "yes" ); } else { transformer.setOutputProperty( OutputKeys.INDENT, "no" ); } if ( doctypeSystem != null ) { transformer.setOutputProperty( "doctype-system", doctypeSystem ); } Writer writer = new FileWriter( file, append ); StreamResult result = new StreamResult( writer ); transformer.transform( new DOMSource( doc ), result ); } catch( ParserConfigurationException e ) { throw new IOException( e ); } catch( TransformerConfigurationException e ) { throw new IOException( e ); } catch( TransformerException e ) { throw new IOException( e ); } } private void writeStorageXML( File file, Value value ) throws IOException { if ( value.children().isEmpty() ) { return; // TODO: perhaps we should erase the content of the file before returning. } String rootName = value.children().keySet().iterator().next(); try { Document doc = documentBuilderFactory.newDocumentBuilder().newDocument(); jolie.xml.XmlUtils.valueToStorageDocument( value.getFirstChild( rootName ), rootName, doc ); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty( OutputKeys.INDENT, "no" ); Writer writer = new FileWriter( file, false ); StreamResult result = new StreamResult( writer ); transformer.transform( new DOMSource( doc ), result ); } catch( ParserConfigurationException e ) { throw new IOException( e ); } catch( TransformerConfigurationException e ) { throw new IOException( e ); } catch( TransformerException e ) { throw new IOException( e ); } } private static void writeBinary( File file, Value value, boolean append ) throws IOException { FileOutputStream os = new FileOutputStream( file, append ); os.write( value.byteArrayValue().getBytes() ); os.flush(); os.close(); } private static void writeText( File file, Value value, boolean append ) throws IOException { FileWriter writer = new FileWriter( file, append ); writer.write( value.strValue() ); writer.flush(); writer.close(); } @RequestResponse public void writeFile( Value request ) throws FaultException { boolean append = false; Value content = request.getFirstChild( "content" ); String format = request.getFirstChild( "format" ).strValue(); File file = new File( request.getFirstChild( "filename" ).strValue() ); if ( request.getFirstChild( "append" ).intValue() > 0 ) { append = true; } try { if ( "text".equals( format ) ) { writeText( file, content, append ); } else if ( "binary".equals( format ) ) { writeBinary( file, content, append ); } else if ( "xml".equals( format ) ) { String schemaFilename = null; if ( request.getFirstChild( "format" ).hasChildren( "schema" ) ) { schemaFilename = request.getFirstChild( "format" ).getFirstChild( "schema" ).strValue(); } boolean indent = false; if ( request.getFirstChild( "format" ).hasChildren( "indent" ) ) { indent = request.getFirstChild( "format" ).getFirstChild( "indent" ).boolValue(); } String doctypePublic = null; if ( request.getFirstChild( "format" ).hasChildren( "doctype_system" ) ) { doctypePublic = request.getFirstChild( "format" ).getFirstChild( "doctype_system" ).strValue(); } writeXML( file, content, append, schemaFilename, doctypePublic, indent ); } else if ( "xml_store".equals( format ) ) { writeStorageXML( file, content ); } else if ( format.isEmpty() ) { if ( content.isByteArray() ) { writeBinary( file, content, append ); } else { writeText( file, content, append ); } } } catch( IOException e ) { throw new FaultException( "IOException", e ); } } public Boolean delete( Value request ) { String filename = request.strValue(); boolean isRegex = request.getFirstChild( "isRegex" ).intValue() > 0; boolean ret = true; if ( isRegex ) { File dir = new File( filename ).getAbsoluteFile().getParentFile(); String[] files = dir.list( new ListFilter( filename ) ); if ( files != null ) { for( String file : files ) { new File( file ).delete(); } } } else { if ( new File( filename ).delete() == false ) { ret = false; } } return ret; } @RequestResponse public void rename( Value request ) throws FaultException { String filename = request.getFirstChild( "filename" ).strValue(); String toFilename = request.getFirstChild( "to" ).strValue(); if ( new File( filename ).renameTo( new File( toFilename ) ) == false ) { throw new FaultException( "IOException" ); } } public Value list( Value request ) { File dir = new File( request.getFirstChild( "directory" ).strValue() ); String regex; if ( request.hasChildren( "regex" ) ) { regex = request.getFirstChild( "regex" ).strValue(); } else { regex = ".*"; } String[] files = dir.list( new ListFilter( regex ) ); if ( request.hasChildren("order") ) { Value order = request.getFirstChild("order"); if ( order.hasChildren("byname") && order.getFirstChild("byname").boolValue() ) { Arrays.sort( files ); } } Value response = Value.create(); if ( files != null ) { ValueVector results = response.getChildren( "result" ); for( String file : files ) { results.add( Value.create( file ) ); } } return response; } private static class ListFilter implements FilenameFilter { final private Pattern pattern; public ListFilter( String regex ) { this.pattern = Pattern.compile( regex ); } public boolean accept( File file, String name ) { return pattern.matcher( name ).matches(); } } }