|
VolD 0.1
|
00001 00002 package de.zib.vold.backend; 00003 00004 import de.zib.vold.common.VoldException; 00005 00006 import java.io.File; 00007 import java.io.FileFilter; 00008 import java.io.UnsupportedEncodingException; 00009 import java.io.IOException; 00010 00011 import java.util.List; 00012 import java.util.LinkedList; 00013 import java.util.Map; 00014 import java.util.HashMap; 00015 00016 import java.net.URLEncoder; 00017 import java.net.URLDecoder; 00018 00019 import javax.annotation.PostConstruct; 00020 import javax.annotation.PreDestroy; 00021 00022 import org.slf4j.Logger; 00023 import org.slf4j.LoggerFactory; 00024 00038 public class FileSystemDirectory implements PartitionedDirectoryBackend 00039 { 00040 private File root; 00041 private String rootPath; 00042 protected final Logger log = LoggerFactory.getLogger( this.getClass() ); 00043 00044 private String enc = "utf-8"; 00045 00055 public FileSystemDirectory( String path, String enc ) 00056 { 00057 this.rootPath = path; 00058 this.root = null; 00059 this.enc = enc; 00060 } 00061 00065 public FileSystemDirectory( ) 00066 { 00067 this.rootPath = null; 00068 this.root = null; 00069 } 00070 00078 public void setRootPath( String rootPath ) 00079 { 00080 if( isopen() ) 00081 { 00082 log.warn( "Tried to change root path while database is open. Closing it before!" ); 00083 close(); 00084 } 00085 00086 this.rootPath = rootPath; 00087 } 00088 00096 public void setEnc( String enc ) 00097 { 00098 if( isopen() ) 00099 { 00100 log.warn( "Tried to change encoding while database is open. Closing it before! Nevertheless, this is a dangerous operation!" ); 00101 close(); 00102 } 00103 00104 this.enc = enc; 00105 00106 } 00107 00111 public void checkState( ) 00112 { 00113 if( null == rootPath ) 00114 { 00115 throw new IllegalStateException( "Tried to operate on FileSystemDirectory while it had not been initialized yet. Set the rootPath before!" ); 00116 } 00117 } 00118 00126 @Override 00127 @PostConstruct 00128 public void open( ) 00129 { 00130 // guard 00131 { 00132 checkState(); 00133 00134 if( isopen() ) 00135 { 00136 log.warn( "Tried to open twice. Closing it before!" ); 00137 close(); 00138 } 00139 } 00140 00141 try 00142 { 00143 root = new File( rootPath ); 00144 } 00145 catch( Exception e ) 00146 { 00147 root = null; 00148 throw new VoldException( e ); 00149 } 00150 00151 if( ! root.isDirectory() ) 00152 { 00153 root = null; 00154 throw new VoldException( "Directory could not be opened: " + rootPath + " is no directory!" ); 00155 } 00156 00157 log.info( "Backend opened." ); 00158 } 00159 00167 @Override 00168 @PreDestroy 00169 public void close( ) 00170 { 00171 if( ! isopen() ) 00172 { 00173 log.warn( "Tried to close database while it wasn't open." ); 00174 return; 00175 } 00176 00177 root = null; 00178 00179 log.info( "Backend closed." ); 00180 } 00181 00187 @Override 00188 public boolean isopen( ) 00189 { 00190 if( null == root || null == rootPath ) 00191 return false; 00192 else 00193 return true; 00194 } 00195 00210 @Override 00211 public void insert( int partition, List< String > key, List< String > value ) 00212 { 00213 // guard 00214 { 00215 log.trace( "Insert: " + partition + ":'" + key.toString() + "' -> '" + value.toString() + "'" ); 00216 00217 if( ! isopen() ) 00218 { 00219 throw new VoldException( "Tried to operate on closed database." ); 00220 } 00221 } 00222 00223 List< String > d = _get_partition_dir( partition, key ); 00224 00225 String path = _buildpath( d ); 00226 00227 // create key (directory) and clear its content 00228 { 00229 File f = new File( path ); 00230 if( ! f.exists() ) 00231 { 00232 f.mkdirs(); 00233 //log.debug( "Could not create directory '" + path + "'" ); 00234 } 00235 00236 for( File file: f.listFiles() ) 00237 { 00238 if( file.isFile() ) 00239 { 00240 file.delete(); 00241 } 00242 } 00243 } 00244 00245 // create all files 00246 { 00247 for( String filename: value ) 00248 { 00249 String filepath; 00250 try 00251 { 00252 filepath = path + "/" + _buildfile( filename ); 00253 } 00254 catch( VoldException e ) 00255 { 00256 throw new VoldException( "Error on insertion of value " + filename + " for key " + key.toString() + "(" + path + ").", e ); 00257 } 00258 00259 log.debug( "Creating value '" + filepath + "'" ); 00260 File f = new File( filepath ); 00261 00262 try 00263 { 00264 f.createNewFile(); 00265 } 00266 catch( IOException e ) 00267 { 00268 throw new VoldException( "Error on insertion of value " + filename + " for key " + key.toString() + "(" + path + ").", e ); 00269 } 00270 } 00271 } 00272 } 00273 00282 @Override 00283 public void delete( int partition, List< String > key ) 00284 { 00285 // guard 00286 { 00287 log.trace( "Delete: " + partition + ":'" + key.toString() + "'" ); 00288 00289 if( ! isopen() ) 00290 { 00291 throw new VoldException( "Tried to operate on closed database." ); 00292 } 00293 } 00294 00295 List< String > d = _get_partition_dir( partition, key ); 00296 00297 String path = _buildpath( d ); 00298 00299 // clear content of directory (but not subdirectories!) 00300 { 00301 File f = new File( path ); 00302 00303 if( ! f.exists() ) 00304 { 00305 log.warn( "FileSystemDirectory tried to delete nonexistent " + f.getAbsolutePath() ); 00306 return; 00307 } 00308 00309 for( File file: f.listFiles() ) 00310 { 00311 if( file.isFile() ) 00312 { 00313 file.delete(); 00314 } 00315 } 00316 } 00317 00318 // recursively delete directory, if it is empty now 00319 { 00320 while( d.size() != 0 ) 00321 { 00322 File dir = new File( path ); 00323 00324 // try to delete directory (must be empty for that...) 00325 if( ! dir.delete() ) 00326 break; 00327 00328 d.remove( d.size()-1 ); 00329 path = _buildpath( d ); 00330 } 00331 } 00332 } 00333 00343 @Override 00344 public List< String > lookup( int partition, List< String > key ) 00345 { 00346 // guard 00347 { 00348 log.trace( "Lookup: " + partition + ":'" + key.toString() + "'" ); 00349 00350 if( ! isopen() ) 00351 { 00352 throw new VoldException( "Tried to operate on closed database." ); 00353 } 00354 } 00355 00356 List< String > d = _get_partition_dir( partition, key ); 00357 String path = _buildpath( d ); 00358 File f = new File( path ); 00359 00360 if( ! f.exists() ) 00361 { 00362 log.trace( " ... no results." ); 00363 return null; 00364 } 00365 00366 List< String > result = new LinkedList< String >(); 00367 00368 for( File file: f.listFiles() ) 00369 { 00370 if( file.isFile() ) 00371 { 00372 try 00373 { 00374 result.add( buildfile( file.getName() ) ); 00375 } 00376 catch( VoldException e ) 00377 { 00378 log.warn( "Skipping file " + file.getName() + " while looking for " + key.toString() + ", since an error occured: " + e.getMessage() ); 00379 } 00380 } 00381 } 00382 00383 // empty directorys (i.e. they contain no files!) are interpreted to not exist as key 00384 if( 0 == result.size() ) 00385 { 00386 log.trace( "... no results." ); 00387 return null; 00388 } 00389 00390 log.trace( " results: " + result.toString() ); 00391 return result; 00392 } 00393 00403 @Override 00404 public Map< List< String >, List< String > > prefixlookup( int partition, List< String > key ) 00405 { 00406 // guard 00407 { 00408 log.trace( "PrefixLookup: " + partition + ":" + key.toString() ); 00409 00410 if( ! isopen() ) 00411 { 00412 throw new VoldException( "Tried to operate on closed database." ); 00413 } 00414 } 00415 00416 Map< List< String >, List< String > > result = new HashMap< List< String >, List< String > >(); 00417 00418 String prefix = _builddir( key.remove( key.size()-1 ) ); 00419 00420 List< String > d = _get_partition_dir( partition, key ); 00421 00422 String path = _buildpath( d ); 00423 00424 File f = new File( path ); 00425 if( ! f.exists() ) 00426 { 00427 log.trace( " ... no results." ); 00428 return result; 00429 } 00430 00431 for( File file: f.listFiles( new PrefixFilter( prefix ) ) ) 00432 { 00433 // potential candidate... 00434 if( file.isDirectory() ) 00435 { 00436 List< String > k = new LinkedList< String >( key ); 00437 k.add( builddir( file.getName() ) ); 00438 00439 try 00440 { 00441 recursive_add( k, path + "/" + file.getName(), result ); 00442 } 00443 catch( VoldException e ) 00444 { 00445 log.warn( "Skipping directory " + path + "/" + file.getName() + ", since an error occured: " + e.getMessage() ); 00446 } 00447 } 00448 else 00449 { 00450 try 00451 { 00452 file_add( key, file.getName(), result ); 00453 } 00454 catch( VoldException e ) 00455 { 00456 log.warn( "Skipping file " + file.getName() + " in recursive listing, since it has no valid format: " + e.getMessage() ); 00457 } 00458 } 00459 } 00460 00461 log.trace( " results: " + result.toString() ); 00462 return result; 00463 } 00464 00468 private void recursive_add( List< String > key, String dir, Map< List< String >, List< String > > map ) 00469 { 00470 File _dir = new File( dir ); 00471 00472 if( ! _dir.isDirectory() ) 00473 { 00474 throw new VoldException( "The path " + dir + " describes no directory, as expected" ); 00475 } 00476 00477 for( File file: _dir.listFiles() ) 00478 { 00479 if( file.isDirectory() ) 00480 { 00481 List< String > k = new LinkedList< String >( key ); 00482 try 00483 { 00484 k.add( builddir( file.getName() ) ); 00485 } 00486 catch( VoldException e ) 00487 { 00488 log.warn( "Skipping directory " + file.getName() + " in recursive listing, since it has no valid format: " + e.getMessage() ); 00489 } 00490 00491 recursive_add( k, dir + "/" + file.getName(), map ); 00492 } 00493 else 00494 { 00495 try 00496 { 00497 file_add( key, file.getName(), map ); 00498 } 00499 catch( VoldException e ) 00500 { 00501 log.warn( "Skipping file " + file.getName() + " in recursive listing, since it has no valid format: " + e.getMessage() ); 00502 } 00503 } 00504 } 00505 } 00506 00510 private void file_add( List< String > key, String value, Map< List< String >, List< String > > map ) 00511 { 00512 if( map.containsKey( key ) ) 00513 { 00514 List< String > l = map.get( key ); 00515 l.add( buildfile( value ) ); 00516 } 00517 else 00518 { 00519 List< String > v = new LinkedList< String >(); 00520 v.add( buildfile( value ) ); 00521 map.put( key, v ); 00522 } 00523 } 00524 00528 private String _buildpath( List< String > dir ) 00529 { 00530 String path = new String( rootPath ); 00531 00532 for( String d: dir ) 00533 { 00534 path += "/" + d; 00535 } 00536 00537 return path; 00538 } 00539 00543 private List< String > _get_partition_dir( int partition, List< String > dir ) 00544 { 00545 List< String > l = new LinkedList< String >( ); 00546 00547 l.add( String.valueOf( partition ) ); 00548 00549 for( String d: dir ) 00550 { 00551 try 00552 { 00553 l.add( _builddir( d ) ); 00554 } 00555 catch( VoldException e ) 00556 { 00557 throw new VoldException( "Could not determine Lowlevel directory of abstract directory " + partition + ":" + dir.toString() + ". ", e ); 00558 } 00559 } 00560 00561 return l; 00562 } 00563 00567 private String _buildfile( String dir ) 00568 { 00569 try 00570 { 00571 return "-" + URLEncoder.encode( dir, enc ); 00572 } 00573 catch( UnsupportedEncodingException e ) 00574 { 00575 throw new VoldException( "Could not encode directory name of " + dir + ".", e ); 00576 } 00577 } 00578 00582 private String buildfile( String dir ) 00583 { 00584 try 00585 { 00586 String dec = URLDecoder.decode( dir, enc ); 00587 return dec.substring( 1, dec.length() ); 00588 } 00589 catch( UnsupportedEncodingException e ) 00590 { 00591 throw new VoldException( "Could not decode directory name of " + dir + ".", e ); 00592 } 00593 } 00594 00598 private String _builddir( String dir ) 00599 { 00600 try 00601 { 00602 return "+" + URLEncoder.encode( dir, enc ); 00603 } 00604 catch( UnsupportedEncodingException e ) 00605 { 00606 throw new VoldException( "Could not encode directory name of " + dir + ".", e ); 00607 } 00608 } 00609 00613 private String builddir( String dir ) 00614 { 00615 try 00616 { 00617 String dec = URLDecoder.decode( dir, enc ); 00618 return dec.substring( 1, dec.length() ); 00619 } 00620 catch( UnsupportedEncodingException e ) 00621 { 00622 throw new VoldException( "Could not decode directory name of " + dir + ".", e ); 00623 } 00624 } 00625 00632 private class PrefixFilter implements FileFilter 00633 { 00634 private final String prefix; 00635 00636 public PrefixFilter( String prefix ) 00637 { 00638 this.prefix = prefix; 00639 } 00640 00641 public boolean accept( File pathname ) 00642 { 00643 return pathname.getName().substring( 0, prefix.length() ).equals( prefix ); 00644 } 00645 } 00646 00647 }