Skip to content

Commit

Permalink
Merge pull request #7544 from enonic/epic-revert
Browse files Browse the repository at this point in the history
Content Revert #7418
  • Loading branch information
GlennRicaud authored Oct 14, 2019
2 parents 666398d + 596e22b commit ce28a1e
Show file tree
Hide file tree
Showing 7 changed files with 441 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import com.enonic.xp.admin.impl.json.content.ContentSummaryJson;
import com.enonic.xp.admin.impl.json.content.ContentSummaryListJson;
import com.enonic.xp.admin.impl.json.content.ContentTreeSelectorListJson;
import com.enonic.xp.admin.impl.json.content.ContentVersionJson;
import com.enonic.xp.admin.impl.json.content.ContentsExistByPathJson;
import com.enonic.xp.admin.impl.json.content.ContentsExistJson;
import com.enonic.xp.admin.impl.json.content.DependenciesAggregationJson;
Expand Down Expand Up @@ -96,6 +97,7 @@
import com.enonic.xp.admin.impl.rest.resource.content.json.ReorderChildrenJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.ResolvePublishContentResultJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.ResolvePublishDependenciesJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.RevertContentJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.SetActiveVersionJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.SetChildOrderJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.UndoPendingDeleteContentJson;
Expand All @@ -111,7 +113,9 @@
import com.enonic.xp.admin.impl.rest.resource.content.task.UnpublishRunnableTask;
import com.enonic.xp.admin.impl.rest.resource.schema.content.ContentTypeIconResolver;
import com.enonic.xp.admin.impl.rest.resource.schema.content.ContentTypeIconUrlResolver;
import com.enonic.xp.attachment.Attachment;
import com.enonic.xp.attachment.AttachmentNames;
import com.enonic.xp.attachment.Attachments;
import com.enonic.xp.attachment.CreateAttachment;
import com.enonic.xp.attachment.CreateAttachments;
import com.enonic.xp.branch.Branches;
Expand All @@ -134,6 +138,8 @@
import com.enonic.xp.content.ContentService;
import com.enonic.xp.content.ContentValidityParams;
import com.enonic.xp.content.ContentValidityResult;
import com.enonic.xp.content.ContentVersion;
import com.enonic.xp.content.ContentVersionId;
import com.enonic.xp.content.Contents;
import com.enonic.xp.content.CreateMediaParams;
import com.enonic.xp.content.FindContentByParentParams;
Expand Down Expand Up @@ -183,12 +189,14 @@
import com.enonic.xp.security.auth.AuthenticationInfo;
import com.enonic.xp.task.TaskResultJson;
import com.enonic.xp.task.TaskService;
import com.enonic.xp.util.BinaryReference;
import com.enonic.xp.util.Exceptions;
import com.enonic.xp.web.HttpStatus;
import com.enonic.xp.web.multipart.MultipartForm;
import com.enonic.xp.web.multipart.MultipartItem;

import static java.lang.Math.toIntExact;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang.StringUtils.containsIgnoreCase;
import static org.apache.commons.lang.StringUtils.isBlank;

Expand Down Expand Up @@ -1417,6 +1425,125 @@ public List<ContentIdJson> listChildrenIds( @QueryParam("parentId") final String
return result.getContentIds().stream().map( contentId -> new ContentIdJson( contentId ) ).collect( Collectors.toList() );
}

@POST
@Path("revert")
public ContentVersionJson revert( final RevertContentJson params )
{
final ContentVersionId contentVersionId = ContentVersionId.from( params.getVersionId() );

final Content versionedContent =
params.getContentKey().startsWith( "/" )
? contentService.getByPathAndVersionId( ContentPath.from( params.getContentKey() ), contentVersionId )
: contentService.getByIdAndVersionId( ContentId.from( params.getContentKey() ), contentVersionId );

if ( versionedContent == null )
{
throw JaxRsExceptions.notFound( "Content with contentKey [%s] and versionId [%s] not found", params.getContentKey(),
params.getVersionId() );
}

final Content revertedContent = contentService.update( prepareUpdateContentParams( versionedContent, contentVersionId ) );

final ContentVersion contentVersion = contentService.getActiveVersion( GetActiveContentVersionsParams.create().
branches( Branches.from( ContentConstants.BRANCH_DRAFT ) ).
contentId( revertedContent.getId() ).
build() );

if ( contentVersion != null )
{
return new ContentVersionJson( contentVersion, principalsResolver );
}

return null;
}


private UpdateContentParams prepareUpdateContentParams( final Content versionedContent, final ContentVersionId contentVersionId )
{
final UpdateContentParams updateParams = new UpdateContentParams().
contentId( versionedContent.getId() ).
editor( edit -> {
edit.data = versionedContent.getData();
edit.extraDatas = versionedContent.getAllExtraData();
edit.displayName = versionedContent.getDisplayName();
edit.owner = versionedContent.getOwner();
edit.language = versionedContent.getLanguage();
edit.workflowInfo = WorkflowInfo.inProgress();
} );

updateAttachments( versionedContent, contentVersionId, updateParams );

return updateParams;
}

private void updateAttachments( final Content versionedContent, final ContentVersionId contentVersionId,
final UpdateContentParams updateParams )
{
final Content content = contentService.getById( versionedContent.getId() );

final List<BinaryReference> sourceAttachments =
ofNullable( content.getAttachments() ).orElse( Attachments.empty() ).stream().map( Attachment::getBinaryReference ).collect(
Collectors.toList() );

final List<BinaryReference> targetAttachments =
ofNullable( versionedContent.getAttachments() ).orElse( Attachments.empty() ).stream().map(
Attachment::getBinaryReference ).collect( Collectors.toList() );

List<BinaryReference> difference;
if ( sourceAttachments.size() > targetAttachments.size() )
{
difference = sourceAttachments.stream().filter( ref -> !targetAttachments.contains( ref ) ).collect( Collectors.toList() );
}
else
{
difference = targetAttachments.stream().filter( ref -> !sourceAttachments.contains( ref ) ).collect( Collectors.toList() );
}

if ( !difference.isEmpty() )
{
updateParams.clearAttachments( true );
updateParams.createAttachments(
createAttachments( versionedContent.getId(), contentVersionId, versionedContent.getAttachments() ) );
}
}

private CreateAttachments createAttachments( final ContentId contentId, final ContentVersionId contentVersionId,
final Attachments attachments )
{
final CreateAttachments.Builder createBuilder = CreateAttachments.create();

attachments.forEach( attachment -> {
final CreateAttachment createAttachment = createAttachment( contentId, contentVersionId, attachment );

if ( createAttachment != null )
{
createBuilder.add( createAttachment );
}
} );

final CreateAttachments createAttachments = createBuilder.build();

return createAttachments.isNotEmpty() ? createAttachments : null;
}

private CreateAttachment createAttachment( final ContentId contentId, final ContentVersionId contentVersionId,
final Attachment sourceAttachment )
{
final ByteSource sourceBinary = contentService.getBinary( contentId, contentVersionId, sourceAttachment.getBinaryReference() );

if ( sourceBinary != null )
{
return CreateAttachment.create().
name( sourceAttachment.getName() ).
mimeType( sourceAttachment.getMimeType() ).
byteSource( sourceBinary ).
text( sourceAttachment.getTextContent() ).
build();
}

return null;
}

private Content doCreateAttachment( final String attachmentName, final MultipartForm form )
{
final MultipartItem mediaFile = form.get( "file" );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.enonic.xp.admin.impl.rest.resource.content.json;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class RevertContentJson
{

private final String contentKey;

private final String versionId;

@JsonCreator
public RevertContentJson( final @JsonProperty(value = "contentKey", required = true) String contentKey,
final @JsonProperty(value = "versionId", required = true) String versionId )
{
this.contentKey = contentKey;
this.versionId = versionId;
}

public String getContentKey()
{
return contentKey;
}

public String getVersionId()
{
return versionId;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
import com.enonic.xp.admin.impl.rest.resource.content.json.MoveContentJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.PublishContentJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.ReorderChildrenJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.RevertContentJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.SetActiveVersionJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.UndoPendingDeleteContentJson;
import com.enonic.xp.admin.impl.rest.resource.content.json.UndoPendingDeleteContentResultJson;
Expand Down Expand Up @@ -182,6 +183,7 @@
import com.enonic.xp.task.TaskId;
import com.enonic.xp.task.TaskResultJson;
import com.enonic.xp.task.TaskService;
import com.enonic.xp.util.BinaryReference;
import com.enonic.xp.util.BinaryReferences;
import com.enonic.xp.web.HttpStatus;
import com.enonic.xp.web.multipart.MultipartForm;
Expand All @@ -194,6 +196,7 @@
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
Expand Down Expand Up @@ -2221,6 +2224,119 @@ public void testLoadImageWithMalformedUrl()
} );
}

@Test
public void testRevert_by_id()
{
// prepare
final ContentResource instance = getResourceInstance();
final RevertContentJson params = new RevertContentJson( "content-id", "versionKey" );
final Content updatedContent = createContent( "content-id", "content-name", "myapplication:content-type" );
final PrincipalKey principalKey = RoleKeys.ADMIN;

// mock
final Content content = Mockito.mock( Content.class );
final Content versionedContent = Mockito.mock( Content.class );
final ByteSource byteSource = Mockito.mock( ByteSource.class );
final ContentVersion contentVersion = Mockito.mock( ContentVersion.class );

final Attachments attachments = Attachments.create().add( Attachment.create().
name( "attachment" ).mimeType( "mimeType" ).size( 1000L ).build() ).build();

Mockito.when( versionedContent.getId() ).thenReturn( ContentId.from( "nodeId" ) );
Mockito.when( versionedContent.getAttachments() ).thenReturn( attachments );
Mockito.when( contentVersion.getModifier() ).thenReturn( principalKey );
Mockito.when( contentVersion.getId() ).thenReturn( ContentVersionId.from( "contentVersionId" ) );
Mockito.when( contentService.getByIdAndVersionId( any( ContentId.class ), any( ContentVersionId.class ) ) ).thenReturn( versionedContent );
Mockito.when( contentService.getById( any( ContentId.class ) ) ).thenReturn( content );
Mockito.when( contentService.update( any( UpdateContentParams.class ) ) ).thenReturn( updatedContent );
Mockito.when(
contentService.getBinary( any( ContentId.class ), any( ContentVersionId.class ), any( BinaryReference.class ) ) ).thenReturn(
byteSource );
Mockito.when( contentService.getActiveVersion( any( GetActiveContentVersionsParams.class ) ) ).thenReturn( contentVersion );

// test
final ContentVersionJson result = instance.revert( params );

// assert
assertNotNull( result );
assertEquals( "contentVersionId", result.getId() );

// verify
Mockito.verify( this.contentService, Mockito.times( 1 ) ).
getByIdAndVersionId( any( ContentId.class ), any( ContentVersionId.class ) );
Mockito.verify( this.contentService, Mockito.times( 1 ) ).
getBinary( any( ContentId.class ), any( ContentVersionId.class ), any( BinaryReference.class ) );
Mockito.verify( this.contentService, Mockito.times( 1 ) ).
update( any( UpdateContentParams.class ) );
Mockito.verify( this.contentService, Mockito.times( 1 ) ).
getById( any( ContentId.class ) );
Mockito.verify( this.contentService, Mockito.times( 1 ) ).
getActiveVersion( any( GetActiveContentVersionsParams.class ) );
Mockito.verifyNoMoreInteractions( contentService );
}

@Test
public void testRevert_by_path()
{
// prepare
final ContentResource instance = getResourceInstance();
final RevertContentJson params = new RevertContentJson( "/content-name", "versionKey" );
final Content updatedContent = createContent( "content-id", "content-name", "myapplication:content-type" );
final PrincipalKey principalKey = RoleKeys.ADMIN;

// mock
final Content content = Mockito.mock( Content.class );
final Content versionedContent = Mockito.mock( Content.class );
final ContentVersion contentVersion = Mockito.mock( ContentVersion.class );

Mockito.when( versionedContent.getId() ).thenReturn( ContentId.from( "nodeId" ) );
Mockito.when( contentVersion.getModifier() ).thenReturn( principalKey );
Mockito.when( contentVersion.getId() ).thenReturn( ContentVersionId.from( "contentVersionId" ) );
Mockito.when( contentService.getByPathAndVersionId( any( ContentPath.class ), any( ContentVersionId.class ) ) ).thenReturn( versionedContent );
Mockito.when( contentService.update( any( UpdateContentParams.class ) ) ).thenReturn( updatedContent );
Mockito.when( contentService.getById( any( ContentId.class ) ) ).thenReturn( content );
Mockito.when( contentService.getActiveVersion( any( GetActiveContentVersionsParams.class ) ) ).thenReturn( contentVersion );

// test
final ContentVersionJson result = instance.revert( params );

// assert
assertNotNull( result );
assertEquals( "contentVersionId", result.getId() );

// verify
Mockito.verify( this.contentService, Mockito.times( 1 ) ).
getByPathAndVersionId( any( ContentPath.class ), any( ContentVersionId.class ) );
Mockito.verify( this.contentService, Mockito.times( 1 ) ).
update( any( UpdateContentParams.class ) );
Mockito.verify( this.contentService, Mockito.times( 1 ) ).
getById( any( ContentId.class ) );
Mockito.verify( this.contentService, Mockito.times( 1 ) ).
getActiveVersion( any( GetActiveContentVersionsParams.class ) );
Mockito.verifyNoMoreInteractions( contentService );
}

@Test
public void testRevert_not_found()
{
// prepare
final ContentResource instance = getResourceInstance();
final RevertContentJson params = new RevertContentJson( "/content-name", "versionKey" );

// mock
Mockito.when( contentService.getByPathAndVersionId( any( ContentPath.class ), any( ContentVersionId.class ) ) ).thenReturn( null );

// test & assert
final WebApplicationException exception = assertThrows(WebApplicationException.class, () -> instance.revert( params ));

assertEquals( "Content with contentKey [/content-name] and versionId [versionKey] not found", exception.getMessage() );

// verify
Mockito.verify( this.contentService, Mockito.times( 1 ) ).
getByPathAndVersionId( any( ContentPath.class ), any( ContentVersionId.class ) );
Mockito.verifyNoMoreInteractions( contentService );
}

private ContentTreeSelectorQueryJson initContentTreeSelectorQueryJson( final ContentPath parentPath )
{
final ContentTreeSelectorQueryJson json = Mockito.mock( ContentTreeSelectorQueryJson.class );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,14 @@ public interface ContentService

GetActiveContentVersionsResult getActiveVersions( GetActiveContentVersionsParams params );

ContentVersion getActiveVersion(GetActiveContentVersionsParams params);

SetActiveContentVersionResult setActiveContentVersion( ContentId contentId, ContentVersionId versionId );

ByteSource getBinary( ContentId contentId, BinaryReference binaryReference );

ByteSource getBinary( ContentId contentId, ContentVersionId contentVersionId, BinaryReference binaryReference );

@Deprecated
InputStream getBinaryInputStream( ContentId contentId, BinaryReference binaryReference );

Expand Down
Loading

0 comments on commit ce28a1e

Please sign in to comment.