/** * 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.storage.local.fs; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.codehaus.plexus.component.annotations.Component; import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.util.StringUtils; import org.sonatype.nexus.proxy.ItemNotFoundException; import org.sonatype.nexus.proxy.LocalStorageException; import org.sonatype.nexus.proxy.NoSuchRepositoryException; import org.sonatype.nexus.proxy.ResourceStoreRequest; import org.sonatype.nexus.proxy.item.AbstractStorageItem; import org.sonatype.nexus.proxy.item.ContentLocator; import org.sonatype.nexus.proxy.item.DefaultStorageCollectionItem; import org.sonatype.nexus.proxy.item.DefaultStorageFileItem; import org.sonatype.nexus.proxy.item.DefaultStorageLinkItem; import org.sonatype.nexus.proxy.item.PreparedContentLocator; import org.sonatype.nexus.proxy.item.RepositoryItemUid; import org.sonatype.nexus.proxy.item.StorageFileItem; import org.sonatype.nexus.proxy.item.StorageItem; import org.sonatype.nexus.proxy.item.StorageLinkItem; import org.sonatype.nexus.proxy.repository.Repository; import org.sonatype.nexus.proxy.storage.UnsupportedStorageOperationException; import org.sonatype.nexus.proxy.storage.local.AbstractLocalRepositoryStorage; import org.sonatype.nexus.proxy.storage.local.LocalRepositoryStorage; import org.sonatype.nexus.util.ItemPathUtils; /** * The Class DefaultFSLocalRepositoryStorage. * * @author cstamas */ @Component( role = LocalRepositoryStorage.class, hint = DefaultFSLocalRepositoryStorage.PROVIDER_STRING ) public class DefaultFSLocalRepositoryStorage extends AbstractLocalRepositoryStorage { public static final String PROVIDER_STRING = "file"; @Requirement private FSPeer fsPeer; protected FSPeer getFSPeer() { return fsPeer; } public String getProviderId() { return PROVIDER_STRING; } public void validateStorageUrl( String url ) throws LocalStorageException { boolean result = org.sonatype.nexus.util.FileUtils.validFileUrl( url ); if ( !result ) { throw new LocalStorageException( "Invalid storage URL, not a file based one: " + url ); } } /** * Gets the base dir. * * @return the base dir */ public File getBaseDir( Repository repository, ResourceStoreRequest request ) throws LocalStorageException { URL url; try { request.pushRequestPath( RepositoryItemUid.PATH_ROOT ); url = getAbsoluteUrlFromBase( repository, request ); } finally { request.popRequestPath(); } File file; try { file = new File( url.toURI() ); } catch ( Throwable t ) { file = new File( url.getPath() ); } if ( file.exists() ) { if ( file.isFile() ) { throw new LocalStorageException( "The \"" + repository.getName() + "\" (ID=\"" + repository.getId() + "\") repository's baseDir is not a directory, path: " + file.getAbsolutePath() ); } } else { if ( !file.mkdirs() ) { throw new LocalStorageException( "Could not create the baseDir directory for repository \"" + repository.getName() + "\" (ID=\"" + repository.getId() + "\") on path " + file.getAbsolutePath() ); } } return file; } /** * Gets the file from base. * * @return the file from base */ public File getFileFromBase( final Repository repository, final ResourceStoreRequest request, final File repoBase ) throws LocalStorageException { if ( !repoBase.exists() ) { repoBase.mkdir(); } File result = null; if ( request.getRequestPath() == null || RepositoryItemUid.PATH_ROOT.equals( request.getRequestPath() ) ) { result = repoBase; } else if ( request.getRequestPath().startsWith( "/" ) ) { result = new File( repoBase, request.getRequestPath().substring( 1 ) ); } else { result = new File( repoBase, request.getRequestPath() ); } if ( getLogger().isDebugEnabled() ) { getLogger().debug( request.getRequestPath() + " --> " + result.getAbsoluteFile() ); } // to be foolproof, chrooting it if ( !result.getAbsolutePath().startsWith( getBaseDir( repository, request ).getAbsolutePath() ) ) { throw new LocalStorageException( "getFileFromBase() method evaluated directory wrongly in repository \"" + repository.getName() + "\" (id=\"" + repository.getId() + "\")! baseDir=" + getBaseDir( repository, request ).getAbsolutePath() + ", target=" + result.getAbsolutePath() ); } else { return result; } } /** * Gets the file from base. * * @return the file from base */ public File getFileFromBase( Repository repository, ResourceStoreRequest request ) throws LocalStorageException { return getFileFromBase( repository, request, getBaseDir( repository, request ) ); } /** * Retrieve item from file. * * @param uid the uid * @param target the target * @return the abstract storage item * @throws ItemNotFoundException the item not found exception * @throws LocalStorageException the storage exception */ protected AbstractStorageItem retrieveItemFromFile( Repository repository, ResourceStoreRequest request, File target ) throws ItemNotFoundException, LocalStorageException { String path = request.getRequestPath(); boolean mustBeACollection = path.endsWith( RepositoryItemUid.PATH_SEPARATOR ); if ( path.endsWith( "/" ) ) { path = path.substring( 0, path.length() - 1 ); } if ( StringUtils.isEmpty( path ) ) { path = RepositoryItemUid.PATH_ROOT; } RepositoryItemUid uid = repository.createUid( path ); AbstractStorageItem result = null; if ( target.exists() && target.isDirectory() ) { request.setRequestPath( path ); DefaultStorageCollectionItem coll = new DefaultStorageCollectionItem( repository, request, target.canRead(), target.canWrite() ); coll.setModified( target.lastModified() ); coll.setCreated( target.lastModified() ); repository.getAttributesHandler().fetchAttributes( coll ); result = coll; } else if ( target.exists() && target.isFile() && !mustBeACollection ) { request.setRequestPath( path ); FileContentLocator linkContent = new FileContentLocator( target, "text/plain" ); try { if ( getLinkPersister().isLinkContent( linkContent ) ) { try { DefaultStorageLinkItem link = new DefaultStorageLinkItem( repository, request, target.canRead(), target.canWrite(), getLinkPersister().readLinkContent( linkContent ) ); repository.getAttributesHandler().fetchAttributes( link ); link.setModified( target.lastModified() ); link.setCreated( target.lastModified() ); result = link; repository.getAttributesHandler().touchItemLastRequested( System.currentTimeMillis(), repository, request, link ); } catch ( NoSuchRepositoryException e ) { getLogger().warn( "Stale link object found on UID: " + uid.toString() + ", deleting it." ); target.delete(); throw new ItemNotFoundException( request, repository ); } } else { DefaultStorageFileItem file = new DefaultStorageFileItem( repository, request, target.canRead(), target.canWrite(), new FileContentLocator( target, getMimeUtil().getMimeType( target ) ) ); repository.getAttributesHandler().fetchAttributes( file ); file.setModified( target.lastModified() ); file.setCreated( target.lastModified() ); file.setLength( target.length() ); result = file; repository.getAttributesHandler().touchItemLastRequested( System.currentTimeMillis(), repository, request, file ); } } catch ( IOException e ) { throw new LocalStorageException( "Exception during reading up an item from FS storage!", e ); } } else { throw new ItemNotFoundException( request, repository ); } return result; } public boolean isReachable( Repository repository, ResourceStoreRequest request ) throws LocalStorageException { File target = getBaseDir( repository, request ); return getFSPeer().isReachable( repository, request, target ); } public boolean containsItem( Repository repository, ResourceStoreRequest request ) throws LocalStorageException { return getFSPeer().containsItem( repository, request, getFileFromBase( repository, request ) ); } public AbstractStorageItem retrieveItem( Repository repository, ResourceStoreRequest request ) throws ItemNotFoundException, LocalStorageException { return retrieveItemFromFile( repository, request, getFileFromBase( repository, request ) ); } public void storeItem( Repository repository, StorageItem item ) throws UnsupportedStorageOperationException, LocalStorageException { // set some sanity stuff item.setStoredLocally( System.currentTimeMillis() ); item.setRemoteChecked( item.getStoredLocally() ); item.setExpired( false ); File target = getFileFromBase( repository, item.getResourceStoreRequest() ); ContentLocator cl = null; if ( item instanceof StorageFileItem ) { StorageFileItem fItem = (StorageFileItem) item; prepareStorageFileItemForStore( fItem ); cl = fItem.getContentLocator(); } else if ( item instanceof StorageLinkItem ) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { getLinkPersister().writeLinkContent( (StorageLinkItem) item, bos ); } catch ( IOException e ) { // should not happen, look at implementation // we will handle here two byte array backed streams! throw new LocalStorageException( "Problem ", e ); } cl = new PreparedContentLocator( new ByteArrayInputStream( bos.toByteArray() ), "text/xml" ); } getFSPeer().storeItem( repository, item, target, cl ); if ( item instanceof StorageFileItem ) { ( (StorageFileItem) item ).setLength( target.length() ); // replace content locator transparently, if we just consumed a non-reusable one // Hint: in general, those items coming from user uploads or remote proxy caching requests are non // reusable ones ( (StorageFileItem) item ).setContentLocator( new FileContentLocator( target, ( (StorageFileItem) item ).getMimeType() ) ); } final ContentLocator mdis = item instanceof StorageFileItem ? ( (StorageFileItem) item ).getContentLocator() : null; repository.getAttributesHandler().storeAttributes( item, mdis ); } public void shredItem( Repository repository, ResourceStoreRequest request ) throws ItemNotFoundException, UnsupportedStorageOperationException, LocalStorageException { RepositoryItemUid uid = repository.createUid( request.getRequestPath() ); repository.getAttributesHandler().deleteAttributes( uid ); File target = getFileFromBase( repository, request ); getFSPeer().shredItem( repository, request, target ); } public void moveItem( Repository repository, ResourceStoreRequest from, ResourceStoreRequest to ) throws ItemNotFoundException, UnsupportedStorageOperationException, LocalStorageException { RepositoryItemUid fromUid = repository.createUid( from.getRequestPath() ); AbstractStorageItem fromAttr = repository.getAttributesHandler().getAttributeStorage().getAttributes( fromUid ); // check does it have attrs at all if ( fromAttr != null ) { RepositoryItemUid toUid = repository.createUid( to.getRequestPath() ); fromAttr.setRepositoryItemUid( toUid ); repository.getAttributesHandler().getAttributeStorage().putAttribute( fromAttr ); } File fromTarget = getFileFromBase( repository, from ); File toTarget = getFileFromBase( repository, to ); getFSPeer().moveItem( repository, from, fromTarget, to, toTarget ); repository.getAttributesHandler().getAttributeStorage().deleteAttributes( fromUid ); } public Collection<StorageItem> listItems( Repository repository, ResourceStoreRequest request ) throws ItemNotFoundException, LocalStorageException { List<StorageItem> result = new ArrayList<StorageItem>(); File target = getFileFromBase( repository, request ); Collection<File> files = getFSPeer().listItems( repository, request, target ); if ( files != null ) { for ( File file : files ) { String newPath = ItemPathUtils.concatPaths( request.getRequestPath(), file.getName() ); request.pushRequestPath( newPath ); ResourceStoreRequest collMemberReq = new ResourceStoreRequest( request ); result.add( retrieveItemFromFile( repository, collMemberReq, file ) ); request.popRequestPath(); } } else { result.add( retrieveItemFromFile( repository, request, target ) ); } return result; } }