/**
* Copyright (c) 2008-2011 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://www.sonatype.com/products/nexus/attributions.
*
* This program is free software: you can redistribute it and/or modify it only under the terms of the GNU Affero General
* Public License Version 3 as published by the Free Software Foundation.
*
* 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 Affero General Public License Version 3
* for more details.
*
* You should have received a copy of the GNU Affero General Public License Version 3 along with this program. If not, see
* http://www.gnu.org/licenses.
*
* Sonatype Nexus (TM) Open Source Version is available from Sonatype, Inc. Sonatype and Sonatype Nexus are trademarks of
* Sonatype, Inc. Apache Maven is a trademark of the Apache Foundation. M2Eclipse is a trademark of the Eclipse Foundation.
* All other trademarks are the property of their respective owners.
*/
package org.sonatype.nexus.proxy.attributes;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.commons.io.FilenameUtils;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
import org.codehaus.plexus.util.IOUtil;
import org.sonatype.nexus.configuration.ConfigurationChangeEvent;
import org.sonatype.nexus.configuration.application.ApplicationConfiguration;
import org.sonatype.nexus.proxy.access.Action;
import org.sonatype.nexus.proxy.item.AbstractStorageItem;
import org.sonatype.nexus.proxy.item.DefaultStorageCollectionItem;
import org.sonatype.nexus.proxy.item.DefaultStorageCompositeFileItem;
import org.sonatype.nexus.proxy.item.DefaultStorageFileItem;
import org.sonatype.nexus.proxy.item.DefaultStorageLinkItem;
import org.sonatype.nexus.proxy.item.RepositoryItemUid;
import org.sonatype.nexus.proxy.item.StorageCollectionItem;
import org.sonatype.nexus.proxy.item.StorageItem;
import org.sonatype.nexus.proxy.item.uid.IsMetadataMaintainedAttribute;
import org.sonatype.plexus.appevents.ApplicationEventMulticaster;
import org.sonatype.plexus.appevents.Event;
import org.sonatype.plexus.appevents.EventListener;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
/**
* AttributeStorage implementation driven by XStream. This implementation uses it's own FS storage to store attributes
* in separate place then LocalStorage. This is the "old" default storage.
*
* @author cstamas
*/
@Component( role = AttributeStorage.class )
public class DefaultFSAttributeStorage
implements AttributeStorage, EventListener, Initializable
{
@Requirement
private Logger logger;
@Requirement
private ApplicationEventMulticaster applicationEventMulticaster;
@Requirement
private ApplicationConfiguration applicationConfiguration;
/**
* The base dir.
*/
private File workingDirectory;
/** The xstream. */
private XStream xstream;
/**
* Instantiates a new FSX stream attribute storage.
*/
public DefaultFSAttributeStorage()
{
super();
this.xstream = new XStream();
this.xstream.alias( "file", DefaultStorageFileItem.class );
this.xstream.alias( "compositeFile", DefaultStorageCompositeFileItem.class );
this.xstream.alias( "collection", DefaultStorageCollectionItem.class );
this.xstream.alias( "link", DefaultStorageLinkItem.class );
}
protected Logger getLogger()
{
return logger;
}
public void initialize()
{
applicationEventMulticaster.addEventListener( this );
}
public void onEvent( Event<?> evt )
{
if ( ConfigurationChangeEvent.class.isAssignableFrom( evt.getClass() ) )
{
this.workingDirectory = null;
}
}
/**
* Gets the base dir.
*
* @return the base dir
*/
public File getWorkingDirectory()
throws IOException
{
if ( workingDirectory == null )
{
workingDirectory = applicationConfiguration.getWorkingDirectory( "proxy/attributes" );
if ( workingDirectory.exists() )
{
if ( workingDirectory.isFile() )
{
throw new IllegalArgumentException( "The attribute storage exists and is not a directory: "
+ workingDirectory.getAbsolutePath() );
}
}
else
{
getLogger().info( "Attribute storage directory does not exists, creating it here: " + workingDirectory );
if ( !workingDirectory.mkdirs() )
{
throw new IllegalArgumentException( "Could not create the attribute storage directory on path "
+ workingDirectory.getAbsolutePath() );
}
}
}
return workingDirectory;
}
public void setWorkingDirectory( File baseDir )
{
this.workingDirectory = baseDir;
}
protected boolean IsMetadataMaintained( RepositoryItemUid uid )
{
Boolean isMetadataMaintained = uid.getAttributeValue( IsMetadataMaintainedAttribute.class );
if ( isMetadataMaintained != null )
{
return isMetadataMaintained.booleanValue();
}
else
{
// safest
return true;
}
}
public boolean deleteAttributes( RepositoryItemUid uid )
{
if ( !IsMetadataMaintained( uid ) )
{
// do nothing
return false;
}
uid.lockAttributes( Action.delete );
try
{
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "Deleting attributes on UID=" + uid.toString() );
}
boolean result = false;
try
{
File ftarget = getFileFromBase( uid );
result = ftarget.exists() && ftarget.isFile() && ftarget.delete();
}
catch ( IOException e )
{
getLogger().warn( "Got IOException during delete of UID=" + uid.toString(), e );
}
return result;
}
finally
{
uid.unlockAttributes();
}
}
public AbstractStorageItem getAttributes( RepositoryItemUid uid )
{
if ( !IsMetadataMaintained( uid ) )
{
// do nothing
return null;
}
uid.lockAttributes( Action.read );
try
{
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "Loading attributes on UID=" + uid.toString() );
}
try
{
AbstractStorageItem result = null;
result = doGetAttributes( uid );
return result;
}
catch ( IOException ex )
{
getLogger().error( "Got IOException during reading of UID=" + uid.toString(), ex );
return null;
}
}
finally
{
uid.unlockAttributes();
}
}
public void putAttribute( StorageItem item )
{
if ( !IsMetadataMaintained( item.getRepositoryItemUid() ) )
{
// do nothing
return;
}
RepositoryItemUid origUid = item.getRepositoryItemUid();
origUid.lockAttributes( Action.create );
try
{
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "Storing attributes on UID=" + item.getRepositoryItemUid() );
}
if ( StorageCollectionItem.class.isAssignableFrom( item.getClass() ) )
{
// not saving attributes for directories anymore
return;
}
try
{
AbstractStorageItem onDisk = doGetAttributes( item.getRepositoryItemUid() );
if ( onDisk != null && ( onDisk.getGeneration() > item.getGeneration() ) )
{
// change detected, overlay the to be saved onto the newer one and swap
onDisk.setResourceStoreRequest( item.getResourceStoreRequest() );
onDisk.overlay( item );
// and overlay other things too
onDisk.setRepositoryItemUid( item.getRepositoryItemUid() );
onDisk.setReadable( item.isReadable() );
onDisk.setWritable( item.isWritable() );
item = onDisk;
}
File target = getFileFromBase( item.getRepositoryItemUid() );
target.getParentFile().mkdirs();
if ( target.getParentFile().exists() && target.getParentFile().isDirectory() )
{
FileOutputStream fos = null;
try
{
fos = new FileOutputStream( target );
item.incrementGeneration();
xstream.toXML( item, fos );
fos.flush();
}
finally
{
IOUtil.close( fos );
}
}
else
{
getLogger().error(
"Could not store attributes on UID=" + item.getRepositoryItemUid()
+ ", parent exists but is not a directory!" );
}
}
catch ( IOException ex )
{
getLogger().error( "Got IOException during store of UID=" + item.getRepositoryItemUid(), ex );
}
}
finally
{
origUid.unlockAttributes();
}
}
// ==
/**
* Gets the attributes.
*
* @param uid the uid
* @param isCollection the is collection
* @return the attributes
* @throws IOException Signals that an I/O exception has occurred.
*/
protected AbstractStorageItem doGetAttributes( RepositoryItemUid uid )
throws IOException
{
File target = getFileFromBase( uid );
AbstractStorageItem result = null;
boolean corrupt = false;
if ( target.exists() && target.isFile() )
{
FileInputStream fis = null;
try
{
fis = new FileInputStream( target );
result = (AbstractStorageItem) xstream.fromXML( fis );
result.setRepositoryItemUid( uid );
// fixing remoteChecked
if ( result.getRemoteChecked() == 0 || result.getRemoteChecked() == 1 )
{
result.setRemoteChecked( System.currentTimeMillis() );
result.setExpired( true );
}
// fixing lastRequested
if ( result.getLastRequested() == 0 )
{
result.setLastRequested( System.currentTimeMillis() );
}
}
catch ( IOException e )
{
getLogger().info( "While reading attributes of " + uid + " we got IOException:", e );
throw e;
}
catch ( NullPointerException e )
{
// NEXUS-3911: seems that on malformed XML the XMLpull parser throws NPE?
// org.xmlpull.mxp1.MXParser.fillBuf(MXParser.java:3020) : NPE
// it is corrupt
if ( getLogger().isDebugEnabled() )
{
// we log the stacktrace
getLogger().info( "Attributes of " + uid + " are corrupt, deleting it.", e );
}
else
{
// just remark about this
getLogger().info( "Attributes of " + uid + " are corrupt, deleting it." );
}
corrupt = true;
}
catch ( XStreamException e )
{
// it is corrupt -- so says XStream, but see above and NEXUS-3911
if ( getLogger().isDebugEnabled() )
{
// we log the stacktrace
getLogger().info( "Attributes of " + uid + " are corrupt, deleting it.", e );
}
else
{
// just remark about this
getLogger().info( "Attributes of " + uid + " are corrupt, deleting it." );
}
corrupt = true;
}
finally
{
IOUtil.close( fis );
}
}
if ( corrupt )
{
deleteAttributes( uid );
}
return result;
}
/**
* Gets the file from base.
*
* @param uid the uid
* @param isCollection the is collection
* @return the file from base
*/
protected File getFileFromBase( RepositoryItemUid uid )
throws IOException
{
File repoBase = new File( getWorkingDirectory(), uid.getRepository().getId() );
File result = null;
String path = FilenameUtils.getPath( uid.getPath() );
String name = FilenameUtils.getName( uid.getPath() );
result = new File( repoBase, path + "/" + name );
// to be foolproof
// 2007.11.09. - Believe or not, Nexus deleted my whole USB rack! (cstamas)
// ok, now you may laugh :)
if ( !result.getAbsolutePath().startsWith( getWorkingDirectory().getAbsolutePath() ) )
{
throw new IOException( "FileFromBase evaluated directory wrongly! baseDir="
+ getWorkingDirectory().getAbsolutePath() + ", target=" + result.getAbsolutePath() );
}
else
{
return result;
}
}
}