/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* 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.
*/
package org.kie.workbench.common.screens.search.backend.server;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.inject.Named;
import org.guvnor.common.services.backend.exceptions.ExceptionUtilities;
import org.guvnor.structure.organizationalunit.OrganizationalUnit;
import org.guvnor.structure.organizationalunit.OrganizationalUnitService;
import org.guvnor.structure.repositories.Repository;
import org.jboss.errai.bus.server.annotations.Service;
import org.jboss.errai.security.shared.api.identity.User;
import org.kie.workbench.common.screens.search.model.QueryMetadataPageRequest;
import org.kie.workbench.common.screens.search.model.SearchPageRow;
import org.kie.workbench.common.screens.search.model.SearchTermPageRequest;
import org.kie.workbench.common.screens.search.service.SearchService;
import org.kie.workbench.common.services.shared.project.KieProject;
import org.kie.workbench.common.services.shared.project.KieProjectService;
import org.uberfire.backend.server.util.Paths;
import org.uberfire.commons.validation.PortablePreconditions;
import org.uberfire.ext.metadata.model.KObject;
import org.uberfire.ext.metadata.search.DateRange;
import org.uberfire.ext.metadata.search.IOSearchService;
import org.uberfire.io.IOService;
import org.uberfire.io.attribute.DublinCoreView;
import org.uberfire.java.nio.base.version.VersionAttributeView;
import org.uberfire.java.nio.file.Path;
import org.uberfire.paging.PageResponse;
import org.uberfire.security.authz.AuthorizationManager;
import org.uberfire.workbench.type.ResourceTypeDefinition;
@Service
@ApplicationScoped
public class SearchServiceImpl implements SearchService {
private IOSearchService ioSearchService;
private IOService ioService;
private OrganizationalUnitService organizationalUnitService;
private KieProjectService projectService;
protected User identity;
private AuthorizationManager authorizationManager;
private Instance<ResourceTypeDefinition> typeRegister;
private Map<String, ResourceTypeDefinition> types = new HashMap<String, ResourceTypeDefinition>();
private PageResponse<SearchPageRow> emptyResponse = null;
public SearchServiceImpl() {
//Needed for CDI proxies
}
@Inject
public SearchServiceImpl( @Named("ioSearchStrategy") final IOSearchService ioSearchService,
@Named("ioStrategy") final IOService ioService,
final OrganizationalUnitService organizationalUnitService,
final KieProjectService projectService,
final User identity,
final AuthorizationManager authorizationManager,
@Any final Instance<ResourceTypeDefinition> typeRegister ) {
this.ioSearchService = PortablePreconditions.checkNotNull( "ioSearchService",
ioSearchService );
this.ioService = PortablePreconditions.checkNotNull( "ioService",
ioService );
this.organizationalUnitService = PortablePreconditions.checkNotNull( "organizationalUnitService",
organizationalUnitService );
this.projectService = PortablePreconditions.checkNotNull( "projectService",
projectService );
this.identity = PortablePreconditions.checkNotNull( "identity",
identity );
this.authorizationManager = PortablePreconditions.checkNotNull( "authorizationManager",
authorizationManager );
this.typeRegister = PortablePreconditions.checkNotNull( "typeRegister",
typeRegister );
}
@PostConstruct
void init() {
for ( ResourceTypeDefinition activeType : typeRegister ) {
types.put( activeType.getShortName().toLowerCase(), activeType );
}
emptyResponse = new PageResponse<SearchPageRow>();
emptyResponse.setPageRowList( Collections.<SearchPageRow>emptyList() );
emptyResponse.setStartRowIndex( 0 );
emptyResponse.setTotalRowSize( 0 );
emptyResponse.setLastPage( true );
emptyResponse.setTotalRowSizeExact( true );
}
@Override
public PageResponse<SearchPageRow> fullTextSearch( final SearchTermPageRequest pageRequest ) {
try {
//hits is an approximation at this stage, since we've not filtered by Authorised Project
final int totalNumHitsEstimate = ioSearchService.fullTextSearchHits( pageRequest.getTerm(),
getAuthorizedRepositoryRoots() );
if ( totalNumHitsEstimate > 0 ) {
final PagedCountingFilter filter = new PagedCountingFilter( pageRequest.getStartRowIndex(),
pageRequest.getPageSize() );
final List<Path> pathResult = ioSearchService.fullTextSearch( pageRequest.getTerm(),
filter,
getAuthorizedRepositoryRoots() );
return buildResponse( pathResult,
pageRequest.getPageSize(),
pageRequest.getStartRowIndex(),
filter.getHitsTotalCount() );
}
return emptyResponse;
} catch ( Exception e ) {
throw ExceptionUtilities.handleException( e );
}
}
@Override
public PageResponse<SearchPageRow> queryMetadata( final QueryMetadataPageRequest pageRequest ) {
try {
final Map<String, Object> attrs = new HashMap<String, Object>( pageRequest.getMetadata() );
if ( pageRequest.getCreatedAfter() != null || pageRequest.getCreatedBefore() != null ) {
attrs.put( "creationTime", toDateRange( pageRequest.getCreatedBefore(),
pageRequest.getCreatedAfter() ) );
}
if ( pageRequest.getLastModifiedAfter() != null || pageRequest.getLastModifiedBefore() != null ) {
attrs.put( "lastModifiedTime", toDateRange( pageRequest.getLastModifiedBefore(),
pageRequest.getLastModifiedAfter() ) );
}
//hits is an approximation at this stage, since we've not filtered by Authorised Project
final int totalNumHitsEstimate = ioSearchService.searchByAttrsHits( attrs,
getAuthorizedRepositoryRoots() );
if ( totalNumHitsEstimate > 0 ) {
final PagedCountingFilter filter = new PagedCountingFilter( pageRequest.getStartRowIndex(),
pageRequest.getPageSize() );
final List<Path> pathResult = ioSearchService.searchByAttrs( attrs,
filter,
getAuthorizedRepositoryRoots() );
return buildResponse( pathResult,
pageRequest.getPageSize(),
pageRequest.getStartRowIndex(),
filter.getHitsTotalCount() );
}
return emptyResponse;
} catch ( Exception e ) {
throw ExceptionUtilities.handleException( e );
}
}
private PageResponse<SearchPageRow> buildResponse( final List<Path> pathResult,
final int pageSize,
final int startRow,
final int hitsTotalCount ) {
final List<SearchPageRow> result = new ArrayList<SearchPageRow>( pathResult.size() );
for ( final Path path : pathResult ) {
final DublinCoreView dcoreView = ioService.getFileAttributeView( path,
DublinCoreView.class );
final VersionAttributeView versionAttributeView = ioService.getFileAttributeView( path,
VersionAttributeView.class );
final String creator = extractCreator( versionAttributeView );
final Date createdDate = extractCreatedDate( versionAttributeView );
final String lastContributor = extractLastContributor( versionAttributeView );
final Date lastModifiedDate = extractLastModifiedDate( versionAttributeView );
final String description = extractDescription( dcoreView );
final SearchPageRow row = new SearchPageRow( Paths.convert( path ),
creator,
createdDate,
lastContributor,
lastModifiedDate,
description );
result.add( row );
}
final PageResponse<SearchPageRow> response = new PageResponse<SearchPageRow>();
response.setTotalRowSize( hitsTotalCount );
response.setPageRowList( result );
response.setTotalRowSizeExact( true );
response.setStartRowIndex( startRow );
response.setLastPage( startRow > hitsTotalCount - pageSize );
return response;
}
private String extractCreator( final VersionAttributeView versionAttributeView ) {
if ( versionAttributeView.readAttributes().history().records().size() > 0 ) {
return versionAttributeView.readAttributes().history().records().get( 0 ).author();
}
return "";
}
private Date extractCreatedDate( final VersionAttributeView versionAttributeView ) {
return new Date( versionAttributeView.readAttributes().creationTime().toMillis() );
}
private String extractLastContributor( final VersionAttributeView versionAttributeView ) {
if ( versionAttributeView.readAttributes().history().records().size() > 0 ) {
final int lastIndex = versionAttributeView.readAttributes().history().records().size() - 1;
return versionAttributeView.readAttributes().history().records().get( lastIndex ).author();
}
return "";
}
private Date extractLastModifiedDate( final VersionAttributeView versionAttributeView ) {
return new Date( versionAttributeView.readAttributes().lastModifiedTime().toMillis() );
}
private String extractDescription( final DublinCoreView dcoreView ) {
if ( dcoreView.readAttributes().descriptions().size() > 0 ) {
return dcoreView.readAttributes().descriptions().get( 0 );
}
return "";
}
//Only search the Repositories for which the User has permission to access
Path[] getAuthorizedRepositoryRoots() {
//First get a collection of OU's to which the User has access
final Collection<OrganizationalUnit> organizationalUnits = organizationalUnitService.getOrganizationalUnits();
final Collection<OrganizationalUnit> authorizedOrganizationalUnits = new ArrayList<OrganizationalUnit>();
for ( OrganizationalUnit ou : organizationalUnits ) {
if ( authorizationManager.authorize( ou,
identity ) ) {
authorizedOrganizationalUnits.add( ou );
}
}
//Then check whether User has access to related Repositories
final Set<Path> authorizedRoots = new HashSet<Path>();
for ( OrganizationalUnit ou : authorizedOrganizationalUnits ) {
final Collection<Repository> repositories = ou.getRepositories();
for ( final Repository repository : repositories ) {
if ( authorizationManager.authorize( repository,
identity ) ) {
authorizedRoots.add( Paths.convert( repository.getRoot() ) );
}
}
}
return authorizedRoots.toArray( new Path[ authorizedRoots.size() ] );
}
private DateRange toDateRange( final Date before,
final Date after ) {
return new DateRange() {
@Override
public Date before() {
if ( before == null ) {
return new Date();
}
return before;
}
@Override
public Date after() {
if ( after == null ) {
return new Date( 0 );
}
return after;
}
};
}
class PagedCountingFilter implements IOSearchService.Filter {
private int hitsStartIndex = -1;
private int hitsPageCount = 0;
private int hitsTotalCount = 0;
private final int startRow;
private final int pageSize;
PagedCountingFilter( final int startRow,
final int pageSize ) {
this.startRow = startRow;
this.pageSize = pageSize;
}
@Override
public boolean accept( final KObject kObject ) {
final Path path = ioService.get( URI.create( kObject.getKey() ) );
final org.uberfire.backend.vfs.Path vfsPath = Paths.convert( path );
final KieProject project = projectService.resolveProject( vfsPath );
//All Users are granted access to Resources outside the Project structure
boolean authorized = true;
if ( project != null ) {
authorized = authorizationManager.authorize( project,
identity );
}
if ( authorized ) {
hitsTotalCount++;
hitsStartIndex++;
if ( hitsStartIndex >= startRow && hitsPageCount < pageSize ) {
hitsPageCount++;
return true;
}
}
return false;
}
public int getHitsTotalCount() {
return hitsTotalCount;
}
}
}