package org.apache.archiva.web.api; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import org.apache.archiva.admin.model.RepositoryAdminException; import org.apache.archiva.admin.model.admin.ArchivaAdministration; import org.apache.archiva.admin.model.beans.ManagedRepository; import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin; import org.apache.archiva.metadata.model.facets.AuditEvent; import org.apache.archiva.checksum.ChecksumAlgorithm; import org.apache.archiva.checksum.ChecksummedFile; import org.apache.archiva.common.utils.VersionComparator; import org.apache.archiva.common.utils.VersionUtil; import org.apache.archiva.maven2.metadata.MavenMetadataReader; import org.apache.archiva.model.ArchivaRepositoryMetadata; import org.apache.archiva.model.ArtifactReference; import org.apache.archiva.model.SnapshotVersion; import org.apache.archiva.redback.components.taskqueue.TaskQueueException; import org.apache.archiva.repository.ManagedRepositoryContent; import org.apache.archiva.repository.RepositoryContentFactory; import org.apache.archiva.repository.RepositoryException; import org.apache.archiva.repository.RepositoryNotFoundException; import org.apache.archiva.repository.metadata.MetadataTools; import org.apache.archiva.repository.metadata.RepositoryMetadataException; import org.apache.archiva.repository.metadata.RepositoryMetadataWriter; import org.apache.archiva.rest.api.services.ArchivaRestServiceException; import org.apache.archiva.rest.services.AbstractRestService; import org.apache.archiva.scheduler.ArchivaTaskScheduler; import org.apache.archiva.scheduler.repository.model.RepositoryTask; import org.apache.archiva.web.model.FileMetadata; import org.apache.archiva.xml.XMLException; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.apache.cxf.jaxrs.ext.multipart.Attachment; import org.apache.cxf.jaxrs.ext.multipart.MultipartBody; import org.apache.maven.model.Model; import org.apache.maven.model.io.xpp3.MavenXpp3Writer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import javax.inject.Inject; import javax.inject.Named; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.TimeZone; import java.util.concurrent.CopyOnWriteArrayList; /** * @author Olivier Lamy */ @Service("fileUploadService#rest") public class DefaultFileUploadService extends AbstractRestService implements FileUploadService { private Logger log = LoggerFactory.getLogger( getClass() ); @Context private HttpServletRequest httpServletRequest; @Inject private ManagedRepositoryAdmin managedRepositoryAdmin; @Inject private RepositoryContentFactory repositoryFactory; @Inject private ArchivaAdministration archivaAdministration; private ChecksumAlgorithm[] algorithms = new ChecksumAlgorithm[]{ ChecksumAlgorithm.SHA1, ChecksumAlgorithm.MD5 }; @Inject @Named(value = "archivaTaskScheduler#repository") private ArchivaTaskScheduler scheduler; private String getStringValue( MultipartBody multipartBody, String attachmentId ) throws IOException { Attachment attachment = multipartBody.getAttachment( attachmentId ); return attachment == null ? "" : IOUtils.toString( attachment.getDataHandler().getInputStream() ); } @Override public FileMetadata post( MultipartBody multipartBody ) throws ArchivaRestServiceException { try { String classifier = getStringValue( multipartBody, "classifier" ); String packaging = getStringValue( multipartBody, "packaging" ); // skygo: http header form pomFile was once sending 1 for true and void for false // leading to permanent false value for pomFile if using toBoolean(); use , "1", "" boolean pomFile = BooleanUtils.toBoolean( getStringValue( multipartBody, "pomFile" ) ); Attachment file = multipartBody.getAttachment( "files[]" ); //Content-Disposition: form-data; name="files[]"; filename="org.apache.karaf.features.command-2.2.2.jar" String fileName = file.getContentDisposition().getParameter( "filename" ); File tmpFile = File.createTempFile( "upload-artifact", ".tmp" ); tmpFile.deleteOnExit(); IOUtils.copy( file.getDataHandler().getInputStream(), new FileOutputStream( tmpFile ) ); FileMetadata fileMetadata = new FileMetadata( fileName, tmpFile.length(), "theurl" ); fileMetadata.setServerFileName( tmpFile.getPath() ); fileMetadata.setClassifier( classifier ); fileMetadata.setDeleteUrl( tmpFile.getName() ); fileMetadata.setPomFile( pomFile ); fileMetadata.setPackaging( packaging ); log.info( "uploading file: {}", fileMetadata ); List<FileMetadata> fileMetadatas = getSessionFilesList(); fileMetadatas.add( fileMetadata ); return fileMetadata; } catch ( IOException e ) { throw new ArchivaRestServiceException( e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e ); } } /** * FIXME must be per session synchronized not globally * * @return */ protected synchronized List<FileMetadata> getSessionFilesList() { List<FileMetadata> fileMetadatas = (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY ); if ( fileMetadatas == null ) { fileMetadatas = new CopyOnWriteArrayList<>(); httpServletRequest.getSession().setAttribute( FILES_SESSION_KEY, fileMetadatas ); } return fileMetadatas; } @Override public Boolean deleteFile( String fileName ) throws ArchivaRestServiceException { File file = new File( SystemUtils.getJavaIoTmpDir(), fileName ); log.debug( "delete file:{},exists:{}", file.getPath(), file.exists() ); boolean removed = getSessionFileMetadatas().remove( new FileMetadata( fileName ) ); // try with full name as ui only know the file name if ( !removed ) { /* unused */ getSessionFileMetadatas().remove( new FileMetadata( file.getPath() ) ); } if ( file.exists() ) { return file.delete(); } return Boolean.FALSE; } @Override public Boolean clearUploadedFiles() throws ArchivaRestServiceException { List<FileMetadata> fileMetadatas = new ArrayList( getSessionFileMetadatas() ); for ( FileMetadata fileMetadata : fileMetadatas ) { deleteFile( new File( fileMetadata.getServerFileName() ).getPath() ); } getSessionFileMetadatas().clear(); return Boolean.TRUE; } @Override public List<FileMetadata> getSessionFileMetadatas() throws ArchivaRestServiceException { List<FileMetadata> fileMetadatas = (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY ); return fileMetadatas == null ? Collections.<FileMetadata>emptyList() : fileMetadatas; } @Override public Boolean save( String repositoryId, String groupId, String artifactId, String version, String packaging, boolean generatePom ) throws ArchivaRestServiceException { repositoryId = StringUtils.trim( repositoryId ); groupId = StringUtils.trim( groupId ); artifactId = StringUtils.trim( artifactId ); version = StringUtils.trim( version ); packaging = StringUtils.trim( packaging ); List<FileMetadata> fileMetadatas = getSessionFilesList(); if ( fileMetadatas == null || fileMetadatas.isEmpty() ) { return Boolean.FALSE; } try { ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId ); if ( managedRepository == null ) { // TODO i18n ? throw new ArchivaRestServiceException( "Cannot find managed repository with id " + repositoryId, Response.Status.BAD_REQUEST.getStatusCode(), null ); } if ( VersionUtil.isSnapshot( version ) && !managedRepository.isSnapshots() ) { // TODO i18n ? throw new ArchivaRestServiceException( "Managed repository with id " + repositoryId + " do not accept snapshots", Response.Status.BAD_REQUEST.getStatusCode(), null ); } } catch ( RepositoryAdminException e ) { throw new ArchivaRestServiceException( e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e ); } // get from the session file with groupId/artifactId Iterable<FileMetadata> filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>() { public boolean apply( FileMetadata fileMetadata ) { return fileMetadata != null && !fileMetadata.isPomFile(); } } ); Iterator<FileMetadata> iterator = filesToAdd.iterator(); boolean pomGenerated = false; while ( iterator.hasNext() ) { FileMetadata fileMetadata = iterator.next(); log.debug( "fileToAdd: {}", fileMetadata ); saveFile( repositoryId, fileMetadata, generatePom && !pomGenerated, groupId, artifactId, version, packaging ); pomGenerated = true; deleteFile( fileMetadata.getServerFileName() ); } filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>() { @Override public boolean apply( FileMetadata fileMetadata ) { return fileMetadata != null && fileMetadata.isPomFile(); } } ); iterator = filesToAdd.iterator(); while ( iterator.hasNext() ) { FileMetadata fileMetadata = iterator.next(); log.debug( "fileToAdd: {}", fileMetadata ); savePomFile( repositoryId, fileMetadata, groupId, artifactId, version, packaging ); deleteFile( fileMetadata.getServerFileName() ); } return Boolean.TRUE; } protected void savePomFile( String repositoryId, FileMetadata fileMetadata, String groupId, String artifactId, String version, String packaging ) throws ArchivaRestServiceException { try { boolean fixChecksums = !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) ); ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId ); ArtifactReference artifactReference = new ArtifactReference(); artifactReference.setArtifactId( artifactId ); artifactReference.setGroupId( groupId ); artifactReference.setVersion( version ); artifactReference.setClassifier( fileMetadata.getClassifier() ); artifactReference.setType( packaging ); ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId ); String artifactPath = repository.toPath( artifactReference ); int lastIndex = artifactPath.lastIndexOf( '/' ); String path = artifactPath.substring( 0, lastIndex ); File targetPath = new File( repoConfig.getLocation(), path ); String pomFilename = artifactPath.substring( lastIndex + 1 ); if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) ) { pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() ); } pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom"; copyFile( new File( fileMetadata.getServerFileName() ), targetPath, pomFilename, fixChecksums ); triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE ); queueRepositoryTask( repoConfig.getId(), new File( targetPath, pomFilename ) ); } catch ( IOException ie ) { throw new ArchivaRestServiceException( "Error encountered while uploading pom file: " + ie.getMessage(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie ); } catch ( RepositoryException rep ) { throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep ); } catch ( RepositoryAdminException e ) { throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e ); } } protected void saveFile( String repositoryId, FileMetadata fileMetadata, boolean generatePom, String groupId, String artifactId, String version, String packaging ) throws ArchivaRestServiceException { try { ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId ); ArtifactReference artifactReference = new ArtifactReference(); artifactReference.setArtifactId( artifactId ); artifactReference.setGroupId( groupId ); artifactReference.setVersion( version ); artifactReference.setClassifier( fileMetadata.getClassifier() ); artifactReference.setType( StringUtils.isEmpty( fileMetadata.getPackaging() ) ? packaging : fileMetadata.getPackaging() ); ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId ); String artifactPath = repository.toPath( artifactReference ); int lastIndex = artifactPath.lastIndexOf( '/' ); String path = artifactPath.substring( 0, lastIndex ); File targetPath = new File( repoConfig.getLocation(), path ); log.debug( "artifactPath: {} found targetPath: {}", artifactPath, targetPath ); Date lastUpdatedTimestamp = Calendar.getInstance().getTime(); int newBuildNumber = -1; String timestamp = null; File versionMetadataFile = new File( targetPath, MetadataTools.MAVEN_METADATA ); ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetadataFile ); if ( VersionUtil.isSnapshot( version ) ) { TimeZone timezone = TimeZone.getTimeZone( "UTC" ); DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" ); fmt.setTimeZone( timezone ); timestamp = fmt.format( lastUpdatedTimestamp ); if ( versionMetadata.getSnapshotVersion() != null ) { newBuildNumber = versionMetadata.getSnapshotVersion().getBuildNumber() + 1; } else { newBuildNumber = 1; } } if ( !targetPath.exists() ) { targetPath.mkdirs(); } String filename = artifactPath.substring( lastIndex + 1 ); if ( VersionUtil.isSnapshot( version ) ) { filename = filename.replaceAll( VersionUtil.SNAPSHOT, timestamp + "-" + newBuildNumber ); } boolean fixChecksums = !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) ); try { File targetFile = new File( targetPath, filename ); if ( targetFile.exists() && !VersionUtil.isSnapshot( version ) && repoConfig.isBlockRedeployments() ) { throw new ArchivaRestServiceException( "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.", Response.Status.BAD_REQUEST.getStatusCode(), null ); } else { copyFile( new File( fileMetadata.getServerFileName() ), targetPath, filename, fixChecksums ); triggerAuditEvent( repository.getId(), path + "/" + filename, AuditEvent.UPLOAD_FILE ); queueRepositoryTask( repository.getId(), targetFile ); } } catch ( IOException ie ) { log.error( "IOException copying file: {}", ie.getMessage(), ie ); throw new ArchivaRestServiceException( "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.", Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie ); } if ( generatePom ) { String pomFilename = filename; if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) ) { pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() ); } pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom"; try { File generatedPomFile = createPom( targetPath, pomFilename, fileMetadata, groupId, artifactId, version, packaging ); triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE ); if ( fixChecksums ) { fixChecksums( generatedPomFile ); } queueRepositoryTask( repoConfig.getId(), generatedPomFile ); } catch ( IOException ie ) { throw new ArchivaRestServiceException( "Error encountered while writing pom file: " + ie.getMessage(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie ); } } // explicitly update only if metadata-updater consumer is not enabled! if ( !archivaAdministration.getKnownContentConsumers().contains( "metadata-updater" ) ) { updateProjectMetadata( targetPath.getAbsolutePath(), lastUpdatedTimestamp, timestamp, newBuildNumber, fixChecksums, fileMetadata, groupId, artifactId, version, packaging ); if ( VersionUtil.isSnapshot( version ) ) { updateVersionMetadata( versionMetadata, versionMetadataFile, lastUpdatedTimestamp, timestamp, newBuildNumber, fixChecksums, fileMetadata, groupId, artifactId, version, packaging ); } } } catch ( RepositoryNotFoundException re ) { throw new ArchivaRestServiceException( "Target repository cannot be found: " + re.getMessage(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), re ); } catch ( RepositoryException rep ) { throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep ); } catch ( RepositoryAdminException e ) { throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e ); } } private ArchivaRepositoryMetadata getMetadata( File metadataFile ) throws RepositoryMetadataException { ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata(); if ( metadataFile.exists() ) { try { metadata = MavenMetadataReader.read( metadataFile ); } catch ( XMLException e ) { throw new RepositoryMetadataException( e.getMessage(), e ); } } return metadata; } private File createPom( File targetPath, String filename, FileMetadata fileMetadata, String groupId, String artifactId, String version, String packaging ) throws IOException { Model projectModel = new Model(); projectModel.setModelVersion( "4.0.0" ); projectModel.setGroupId( groupId ); projectModel.setArtifactId( artifactId ); projectModel.setVersion( version ); projectModel.setPackaging( packaging ); File pomFile = new File( targetPath, filename ); MavenXpp3Writer writer = new MavenXpp3Writer(); try (FileWriter w = new FileWriter( pomFile )) { writer.write( w, projectModel ); } return pomFile; } private void fixChecksums( File file ) { ChecksummedFile checksum = new ChecksummedFile( file ); checksum.fixChecksums( algorithms ); } private void queueRepositoryTask( String repositoryId, File localFile ) { RepositoryTask task = new RepositoryTask(); task.setRepositoryId( repositoryId ); task.setResourceFile( localFile ); task.setUpdateRelatedArtifacts( true ); task.setScanAll( false ); try { scheduler.queueTask( task ); } catch ( TaskQueueException e ) { log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName() + "']." ); } } private void copyFile( File sourceFile, File targetPath, String targetFilename, boolean fixChecksums ) throws IOException { Files.copy( sourceFile.toPath(), new File( targetPath, targetFilename ).toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES ); if ( fixChecksums ) { fixChecksums( new File( targetPath, targetFilename ) ); } } /** * Update artifact level metadata. If it does not exist, create the metadata and fix checksums if necessary. */ private void updateProjectMetadata( String targetPath, Date lastUpdatedTimestamp, String timestamp, int buildNumber, boolean fixChecksums, FileMetadata fileMetadata, String groupId, String artifactId, String version, String packaging ) throws RepositoryMetadataException { List<String> availableVersions = new ArrayList<>(); String latestVersion = version; File projectDir = new File( targetPath ).getParentFile(); File projectMetadataFile = new File( projectDir, MetadataTools.MAVEN_METADATA ); ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetadataFile ); if ( projectMetadataFile.exists() ) { availableVersions = projectMetadata.getAvailableVersions(); Collections.sort( availableVersions, VersionComparator.getInstance() ); if ( !availableVersions.contains( version ) ) { availableVersions.add( version ); } latestVersion = availableVersions.get( availableVersions.size() - 1 ); } else { availableVersions.add( version ); projectMetadata.setGroupId( groupId ); projectMetadata.setArtifactId( artifactId ); } if ( projectMetadata.getGroupId() == null ) { projectMetadata.setGroupId( groupId ); } if ( projectMetadata.getArtifactId() == null ) { projectMetadata.setArtifactId( artifactId ); } projectMetadata.setLatestVersion( latestVersion ); projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp ); projectMetadata.setAvailableVersions( availableVersions ); if ( !VersionUtil.isSnapshot( version ) ) { projectMetadata.setReleasedVersion( latestVersion ); } RepositoryMetadataWriter.write( projectMetadata, projectMetadataFile ); if ( fixChecksums ) { fixChecksums( projectMetadataFile ); } } /** * Update version level metadata for snapshot artifacts. If it does not exist, create the metadata and fix checksums * if necessary. */ private void updateVersionMetadata( ArchivaRepositoryMetadata metadata, File metadataFile, Date lastUpdatedTimestamp, String timestamp, int buildNumber, boolean fixChecksums, FileMetadata fileMetadata, String groupId, String artifactId, String version, String packaging ) throws RepositoryMetadataException { if ( !metadataFile.exists() ) { metadata.setGroupId( groupId ); metadata.setArtifactId( artifactId ); metadata.setVersion( version ); } if ( metadata.getSnapshotVersion() == null ) { metadata.setSnapshotVersion( new SnapshotVersion() ); } metadata.getSnapshotVersion().setBuildNumber( buildNumber ); metadata.getSnapshotVersion().setTimestamp( timestamp ); metadata.setLastUpdatedTimestamp( lastUpdatedTimestamp ); RepositoryMetadataWriter.write( metadata, metadataFile ); if ( fixChecksums ) { fixChecksums( metadataFile ); } } }