1 package org.apache.archiva.web.api;
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
21 import com.google.common.base.Predicate;
22 import com.google.common.collect.Iterables;
23 import org.apache.archiva.admin.model.RepositoryAdminException;
24 import org.apache.archiva.admin.model.admin.ArchivaAdministration;
25 import org.apache.archiva.admin.model.beans.ManagedRepository;
26 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
27 import org.apache.archiva.metadata.model.facets.AuditEvent;
28 import org.apache.archiva.checksum.ChecksumAlgorithm;
29 import org.apache.archiva.checksum.ChecksummedFile;
30 import org.apache.archiva.common.utils.VersionComparator;
31 import org.apache.archiva.common.utils.VersionUtil;
32 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
33 import org.apache.archiva.model.ArchivaRepositoryMetadata;
34 import org.apache.archiva.model.ArtifactReference;
35 import org.apache.archiva.model.SnapshotVersion;
36 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
37 import org.apache.archiva.repository.ManagedRepositoryContent;
38 import org.apache.archiva.repository.RepositoryContentFactory;
39 import org.apache.archiva.repository.RepositoryException;
40 import org.apache.archiva.repository.RepositoryNotFoundException;
41 import org.apache.archiva.repository.metadata.MetadataTools;
42 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
43 import org.apache.archiva.repository.metadata.RepositoryMetadataWriter;
44 import org.apache.archiva.rest.api.services.ArchivaRestServiceException;
45 import org.apache.archiva.rest.services.AbstractRestService;
46 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
47 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
48 import org.apache.archiva.web.model.FileMetadata;
49 import org.apache.archiva.xml.XMLException;
50 import org.apache.commons.io.FilenameUtils;
51 import org.apache.commons.io.IOUtils;
52 import org.apache.commons.lang.BooleanUtils;
53 import org.apache.commons.lang.StringUtils;
54 import org.apache.commons.lang.SystemUtils;
55 import org.apache.cxf.jaxrs.ext.multipart.Attachment;
56 import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
57 import org.apache.maven.model.Model;
58 import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.springframework.stereotype.Service;
63 import javax.inject.Inject;
64 import javax.inject.Named;
65 import javax.servlet.http.HttpServletRequest;
66 import javax.ws.rs.core.Context;
67 import javax.ws.rs.core.Response;
69 import java.io.FileOutputStream;
70 import java.io.FileWriter;
71 import java.io.IOException;
72 import java.nio.file.Files;
73 import java.nio.file.Paths;
74 import java.nio.file.StandardCopyOption;
75 import java.text.DateFormat;
76 import java.text.SimpleDateFormat;
77 import java.util.ArrayList;
78 import java.util.Calendar;
79 import java.util.Collections;
80 import java.util.Date;
81 import java.util.Iterator;
82 import java.util.List;
83 import java.util.TimeZone;
84 import java.util.concurrent.CopyOnWriteArrayList;
87 * @author Olivier Lamy
89 @Service("fileUploadService#rest")
90 public class DefaultFileUploadService
91 extends AbstractRestService
92 implements FileUploadService
94 private Logger log = LoggerFactory.getLogger( getClass() );
97 private HttpServletRequest httpServletRequest;
100 private ManagedRepositoryAdmin managedRepositoryAdmin;
103 private RepositoryContentFactory repositoryFactory;
106 private ArchivaAdministration archivaAdministration;
108 private ChecksumAlgorithm[] algorithms = new ChecksumAlgorithm[]{ ChecksumAlgorithm.SHA1, ChecksumAlgorithm.MD5 };
111 @Named(value = "archivaTaskScheduler#repository")
112 private ArchivaTaskScheduler scheduler;
114 private String getStringValue( MultipartBody multipartBody, String attachmentId )
117 Attachment attachment = multipartBody.getAttachment( attachmentId );
118 return attachment == null ? "" : IOUtils.toString( attachment.getDataHandler().getInputStream() );
122 public FileMetadata post( MultipartBody multipartBody )
123 throws ArchivaRestServiceException
129 String classifier = getStringValue( multipartBody, "classifier" );
130 String packaging = getStringValue( multipartBody, "packaging" );
131 // skygo: http header form pomFile was once sending 1 for true and void for false
132 // leading to permanent false value for pomFile if using toBoolean(); use , "1", ""
133 boolean pomFile = BooleanUtils.toBoolean( getStringValue( multipartBody, "pomFile" ) );
135 Attachment file = multipartBody.getAttachment( "files[]" );
137 //Content-Disposition: form-data; name="files[]"; filename="org.apache.karaf.features.command-2.2.2.jar"
138 String fileName = file.getContentDisposition().getParameter( "filename" );
140 File tmpFile = File.createTempFile( "upload-artifact", ".tmp" );
141 tmpFile.deleteOnExit();
142 IOUtils.copy( file.getDataHandler().getInputStream(), new FileOutputStream( tmpFile ) );
143 FileMetadata fileMetadata = new FileMetadata( fileName, tmpFile.length(), "theurl" );
144 fileMetadata.setServerFileName( tmpFile.getPath() );
145 fileMetadata.setClassifier( classifier );
146 fileMetadata.setDeleteUrl( tmpFile.getName() );
147 fileMetadata.setPomFile( pomFile );
148 fileMetadata.setPackaging( packaging );
150 log.info( "uploading file: {}", fileMetadata );
152 List<FileMetadata> fileMetadatas = getSessionFilesList();
154 fileMetadatas.add( fileMetadata );
158 catch ( IOException e )
160 throw new ArchivaRestServiceException( e.getMessage(),
161 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
167 * FIXME must be per session synchronized not globally
171 protected synchronized List<FileMetadata> getSessionFilesList()
173 List<FileMetadata> fileMetadatas =
174 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
175 if ( fileMetadatas == null )
177 fileMetadatas = new CopyOnWriteArrayList<>();
178 httpServletRequest.getSession().setAttribute( FILES_SESSION_KEY, fileMetadatas );
180 return fileMetadatas;
184 public Boolean deleteFile( String fileName )
185 throws ArchivaRestServiceException
187 // we make sure, that there are no other path components in the filename:
188 String checkedFileName = Paths.get(fileName).getFileName().toString();
189 File file = new File( SystemUtils.getJavaIoTmpDir(), checkedFileName );
190 log.debug( "delete file:{},exists:{}", file.getPath(), file.exists() );
191 boolean removed = getSessionFileMetadatas().remove( new FileMetadata( fileName ) );
192 // try with full name as ui only know the file name
195 removed = getSessionFileMetadatas().remove( new FileMetadata( file.getPath() ) );
197 if (removed && file.exists() )
199 return file.delete();
201 return Boolean.FALSE;
205 public Boolean clearUploadedFiles()
206 throws ArchivaRestServiceException
208 List<FileMetadata> fileMetadatas = new ArrayList( getSessionFileMetadatas() );
209 for ( FileMetadata fileMetadata : fileMetadatas )
211 deleteFile( new File( fileMetadata.getServerFileName() ).getPath() );
213 getSessionFileMetadatas().clear();
218 public List<FileMetadata> getSessionFileMetadatas()
219 throws ArchivaRestServiceException
221 List<FileMetadata> fileMetadatas =
222 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
224 return fileMetadatas == null ? Collections.<FileMetadata>emptyList() : fileMetadatas;
228 public Boolean save( String repositoryId, String groupId, String artifactId, String version, String packaging,
229 boolean generatePom )
230 throws ArchivaRestServiceException
232 repositoryId = StringUtils.trim( repositoryId );
233 groupId = StringUtils.trim( groupId );
234 artifactId = StringUtils.trim( artifactId );
235 version = StringUtils.trim( version );
236 packaging = StringUtils.trim( packaging );
238 List<FileMetadata> fileMetadatas = getSessionFilesList();
239 if ( fileMetadatas == null || fileMetadatas.isEmpty() )
241 return Boolean.FALSE;
246 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
248 if ( managedRepository == null )
251 throw new ArchivaRestServiceException( "Cannot find managed repository with id " + repositoryId,
252 Response.Status.BAD_REQUEST.getStatusCode(), null );
255 if ( VersionUtil.isSnapshot( version ) && !managedRepository.isSnapshots() )
258 throw new ArchivaRestServiceException(
259 "Managed repository with id " + repositoryId + " do not accept snapshots",
260 Response.Status.BAD_REQUEST.getStatusCode(), null );
263 catch ( RepositoryAdminException e )
265 throw new ArchivaRestServiceException( e.getMessage(),
266 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
269 // get from the session file with groupId/artifactId
271 Iterable<FileMetadata> filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
273 public boolean apply( FileMetadata fileMetadata )
275 return fileMetadata != null && !fileMetadata.isPomFile();
278 Iterator<FileMetadata> iterator = filesToAdd.iterator();
279 boolean pomGenerated = false;
280 while ( iterator.hasNext() )
282 FileMetadata fileMetadata = iterator.next();
283 log.debug( "fileToAdd: {}", fileMetadata );
284 saveFile( repositoryId, fileMetadata, generatePom && !pomGenerated, groupId, artifactId, version,
287 deleteFile( fileMetadata.getServerFileName() );
290 filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
293 public boolean apply( FileMetadata fileMetadata )
295 return fileMetadata != null && fileMetadata.isPomFile();
299 iterator = filesToAdd.iterator();
300 while ( iterator.hasNext() )
302 FileMetadata fileMetadata = iterator.next();
303 log.debug( "fileToAdd: {}", fileMetadata );
304 savePomFile( repositoryId, fileMetadata, groupId, artifactId, version, packaging );
305 deleteFile( fileMetadata.getServerFileName() );
311 protected void savePomFile( String repositoryId, FileMetadata fileMetadata, String groupId, String artifactId,
312 String version, String packaging )
313 throws ArchivaRestServiceException
318 boolean fixChecksums =
319 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
321 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
323 ArtifactReference artifactReference = new ArtifactReference();
324 artifactReference.setArtifactId( artifactId );
325 artifactReference.setGroupId( groupId );
326 artifactReference.setVersion( version );
327 artifactReference.setClassifier( fileMetadata.getClassifier() );
328 artifactReference.setType( packaging );
330 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
332 String artifactPath = repository.toPath( artifactReference );
334 int lastIndex = artifactPath.lastIndexOf( '/' );
336 String path = artifactPath.substring( 0, lastIndex );
337 File targetPath = new File( repoConfig.getLocation(), path );
339 String pomFilename = artifactPath.substring( lastIndex + 1 );
340 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
342 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
344 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
346 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, pomFilename, fixChecksums );
347 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
348 queueRepositoryTask( repoConfig.getId(), new File( targetPath, pomFilename ) );
350 catch ( IOException ie )
352 throw new ArchivaRestServiceException( "Error encountered while uploading pom file: " + ie.getMessage(),
353 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
355 catch ( RepositoryException rep )
357 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
358 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
360 catch ( RepositoryAdminException e )
362 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
363 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
367 protected void saveFile( String repositoryId, FileMetadata fileMetadata, boolean generatePom, String groupId,
368 String artifactId, String version, String packaging )
369 throws ArchivaRestServiceException
374 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
376 ArtifactReference artifactReference = new ArtifactReference();
377 artifactReference.setArtifactId( artifactId );
378 artifactReference.setGroupId( groupId );
379 artifactReference.setVersion( version );
380 artifactReference.setClassifier( fileMetadata.getClassifier() );
381 artifactReference.setType(
382 StringUtils.isEmpty( fileMetadata.getPackaging() ) ? packaging : fileMetadata.getPackaging() );
384 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
386 String artifactPath = repository.toPath( artifactReference );
388 int lastIndex = artifactPath.lastIndexOf( '/' );
390 String path = artifactPath.substring( 0, lastIndex );
391 File targetPath = new File( repoConfig.getLocation(), path );
393 log.debug( "artifactPath: {} found targetPath: {}", artifactPath, targetPath );
395 Date lastUpdatedTimestamp = Calendar.getInstance().getTime();
396 int newBuildNumber = -1;
397 String timestamp = null;
399 File versionMetadataFile = new File( targetPath, MetadataTools.MAVEN_METADATA );
400 ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetadataFile );
402 if ( VersionUtil.isSnapshot( version ) )
404 TimeZone timezone = TimeZone.getTimeZone( "UTC" );
405 DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
406 fmt.setTimeZone( timezone );
407 timestamp = fmt.format( lastUpdatedTimestamp );
408 if ( versionMetadata.getSnapshotVersion() != null )
410 newBuildNumber = versionMetadata.getSnapshotVersion().getBuildNumber() + 1;
418 if ( !targetPath.exists() )
423 String filename = artifactPath.substring( lastIndex + 1 );
424 if ( VersionUtil.isSnapshot( version ) )
426 filename = filename.replaceAll( VersionUtil.SNAPSHOT, timestamp + "-" + newBuildNumber );
429 boolean fixChecksums =
430 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
434 File targetFile = new File( targetPath, filename );
435 if ( targetFile.exists() && !VersionUtil.isSnapshot( version ) && repoConfig.isBlockRedeployments() )
437 throw new ArchivaRestServiceException(
438 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
439 Response.Status.BAD_REQUEST.getStatusCode(), null );
443 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, filename, fixChecksums );
444 triggerAuditEvent( repository.getId(), path + "/" + filename, AuditEvent.UPLOAD_FILE );
445 queueRepositoryTask( repository.getId(), targetFile );
448 catch ( IOException ie )
450 log.error( "IOException copying file: {}", ie.getMessage(), ie );
451 throw new ArchivaRestServiceException(
452 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
453 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
458 String pomFilename = filename;
459 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
461 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
463 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
467 File generatedPomFile =
468 createPom( targetPath, pomFilename, fileMetadata, groupId, artifactId, version, packaging );
469 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
472 fixChecksums( generatedPomFile );
474 queueRepositoryTask( repoConfig.getId(), generatedPomFile );
476 catch ( IOException ie )
478 throw new ArchivaRestServiceException(
479 "Error encountered while writing pom file: " + ie.getMessage(),
480 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
484 // explicitly update only if metadata-updater consumer is not enabled!
485 if ( !archivaAdministration.getKnownContentConsumers().contains( "metadata-updater" ) )
487 updateProjectMetadata( targetPath.getAbsolutePath(), lastUpdatedTimestamp, timestamp, newBuildNumber,
488 fixChecksums, fileMetadata, groupId, artifactId, version, packaging );
490 if ( VersionUtil.isSnapshot( version ) )
492 updateVersionMetadata( versionMetadata, versionMetadataFile, lastUpdatedTimestamp, timestamp,
493 newBuildNumber, fixChecksums, fileMetadata, groupId, artifactId, version,
498 catch ( RepositoryNotFoundException re )
500 throw new ArchivaRestServiceException( "Target repository cannot be found: " + re.getMessage(),
501 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), re );
503 catch ( RepositoryException rep )
505 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
506 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
508 catch ( RepositoryAdminException e )
510 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
511 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
515 private ArchivaRepositoryMetadata getMetadata( File metadataFile )
516 throws RepositoryMetadataException
518 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
519 if ( metadataFile.exists() )
523 metadata = MavenMetadataReader.read( metadataFile );
525 catch ( XMLException e )
527 throw new RepositoryMetadataException( e.getMessage(), e );
533 private File createPom( File targetPath, String filename, FileMetadata fileMetadata, String groupId,
534 String artifactId, String version, String packaging )
537 Model projectModel = new Model();
538 projectModel.setModelVersion( "4.0.0" );
539 projectModel.setGroupId( groupId );
540 projectModel.setArtifactId( artifactId );
541 projectModel.setVersion( version );
542 projectModel.setPackaging( packaging );
544 File pomFile = new File( targetPath, filename );
545 MavenXpp3Writer writer = new MavenXpp3Writer();
547 try (FileWriter w = new FileWriter( pomFile ))
549 writer.write( w, projectModel );
555 private void fixChecksums( File file )
557 ChecksummedFile checksum = new ChecksummedFile( file );
558 checksum.fixChecksums( algorithms );
561 private void queueRepositoryTask( String repositoryId, File localFile )
563 RepositoryTask task = new RepositoryTask();
564 task.setRepositoryId( repositoryId );
565 task.setResourceFile( localFile );
566 task.setUpdateRelatedArtifacts( true );
567 task.setScanAll( false );
571 scheduler.queueTask( task );
573 catch ( TaskQueueException e )
575 log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
580 private void copyFile( File sourceFile, File targetPath, String targetFilename, boolean fixChecksums )
584 Files.copy( sourceFile.toPath(), new File( targetPath, targetFilename ).toPath(), StandardCopyOption.REPLACE_EXISTING,
585 StandardCopyOption.COPY_ATTRIBUTES );
589 fixChecksums( new File( targetPath, targetFilename ) );
594 * Update artifact level metadata. If it does not exist, create the metadata and fix checksums if necessary.
596 private void updateProjectMetadata( String targetPath, Date lastUpdatedTimestamp, String timestamp, int buildNumber,
597 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
598 String artifactId, String version, String packaging )
599 throws RepositoryMetadataException
601 List<String> availableVersions = new ArrayList<>();
602 String latestVersion = version;
604 File projectDir = new File( targetPath ).getParentFile();
605 File projectMetadataFile = new File( projectDir, MetadataTools.MAVEN_METADATA );
607 ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetadataFile );
609 if ( projectMetadataFile.exists() )
611 availableVersions = projectMetadata.getAvailableVersions();
613 Collections.sort( availableVersions, VersionComparator.getInstance() );
615 if ( !availableVersions.contains( version ) )
617 availableVersions.add( version );
620 latestVersion = availableVersions.get( availableVersions.size() - 1 );
624 availableVersions.add( version );
626 projectMetadata.setGroupId( groupId );
627 projectMetadata.setArtifactId( artifactId );
630 if ( projectMetadata.getGroupId() == null )
632 projectMetadata.setGroupId( groupId );
635 if ( projectMetadata.getArtifactId() == null )
637 projectMetadata.setArtifactId( artifactId );
640 projectMetadata.setLatestVersion( latestVersion );
641 projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
642 projectMetadata.setAvailableVersions( availableVersions );
644 if ( !VersionUtil.isSnapshot( version ) )
646 projectMetadata.setReleasedVersion( latestVersion );
649 RepositoryMetadataWriter.write( projectMetadata, projectMetadataFile );
653 fixChecksums( projectMetadataFile );
658 * Update version level metadata for snapshot artifacts. If it does not exist, create the metadata and fix checksums
661 private void updateVersionMetadata( ArchivaRepositoryMetadata metadata, File metadataFile,
662 Date lastUpdatedTimestamp, String timestamp, int buildNumber,
663 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
664 String artifactId, String version, String packaging )
665 throws RepositoryMetadataException
667 if ( !metadataFile.exists() )
669 metadata.setGroupId( groupId );
670 metadata.setArtifactId( artifactId );
671 metadata.setVersion( version );
674 if ( metadata.getSnapshotVersion() == null )
676 metadata.setSnapshotVersion( new SnapshotVersion() );
679 metadata.getSnapshotVersion().setBuildNumber( buildNumber );
680 metadata.getSnapshotVersion().setTimestamp( timestamp );
681 metadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
683 RepositoryMetadataWriter.write( metadata, metadataFile );
687 fixChecksums( metadataFile );