/*
*
* * Licensed to the Apache Software Foundation (ASF) under one or more
* * contributor license agreements. 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. For additional information regarding
* * copyright in this work, please see the NOTICE file in the top level
* * directory of this distribution.
*
*/
package org.apache.usergrid.management;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.apache.usergrid.corepersistence.util.CpEntityMapUtils;
import org.apache.usergrid.corepersistence.util.CpNamingUtils;
import org.apache.usergrid.persistence.EntityFactory;
import org.apache.usergrid.persistence.EntityManager;
import org.apache.usergrid.persistence.EntityManagerFactory;
import org.apache.usergrid.persistence.EntityRef;
import org.apache.usergrid.persistence.Schema;
import org.apache.usergrid.persistence.collection.EntityCollectionManager;
import org.apache.usergrid.persistence.collection.EntityCollectionManagerFactory;
import org.apache.usergrid.persistence.core.migration.data.MigrationInfoSerialization;
import org.apache.usergrid.persistence.core.migration.data.MigrationPlugin;
import org.apache.usergrid.persistence.core.migration.data.PluginPhase;
import org.apache.usergrid.persistence.core.migration.data.ProgressObserver;
import org.apache.usergrid.persistence.core.scope.ApplicationScope;
import org.apache.usergrid.persistence.entities.Group;
import org.apache.usergrid.persistence.exceptions.ApplicationAlreadyExistsException;
import org.apache.usergrid.persistence.graph.GraphManager;
import org.apache.usergrid.persistence.graph.GraphManagerFactory;
import org.apache.usergrid.persistence.graph.MarkedEdge;
import org.apache.usergrid.persistence.graph.SearchByEdgeType;
import org.apache.usergrid.persistence.graph.impl.SimpleSearchByEdge;
import org.apache.usergrid.persistence.graph.impl.SimpleSearchByEdgeType;
import org.apache.usergrid.persistence.model.entity.Entity;
import org.apache.usergrid.persistence.model.entity.Id;
import org.apache.usergrid.utils.UUIDUtils;
import com.google.common.base.Optional;
import com.google.inject.Inject;
import rx.Observable;
import static org.apache.usergrid.corepersistence.util.CpNamingUtils.getApplicationScope;
import static org.apache.usergrid.persistence.Schema.PROPERTY_NAME;
/**
* Migration of appinfos collection to application_info collection.
*
* Part of USERGRID-448 "Remove redundant appinfos collections in ManagementServiceImpl"
* https://issues.apache.org/jira/browse/USERGRID-448
*/
public class AppInfoMigrationPlugin implements MigrationPlugin {
/** Old and deprecated SYSTEM_APP */
public static final UUID SYSTEM_APP_ID = UUID.fromString( "b6768a08-b5d5-11e3-a495-10ddb1de66c3" );
private static final Logger logger = LoggerFactory.getLogger( AppInfoMigrationPlugin.class );
public static String PLUGIN_NAME = "appinfo-migration";
@Inject
final private MigrationInfoSerialization migrationInfoSerialization;
@Inject
final private EntityManagerFactory emf;
@Inject
final private EntityCollectionManagerFactory entityCollectionManagerFactory;
@Inject
final private GraphManagerFactory graphManagerFactory;
private final ManagementService managementService;
@Inject
public AppInfoMigrationPlugin( EntityManagerFactory emf, MigrationInfoSerialization migrationInfoSerialization,
EntityCollectionManagerFactory entityCollectionManagerFactory,
GraphManagerFactory graphManagerFactory, BeanFactory beanFactory ) {
this.emf = emf;
this.migrationInfoSerialization = migrationInfoSerialization;
this.entityCollectionManagerFactory = entityCollectionManagerFactory;
this.graphManagerFactory = graphManagerFactory;
this.managementService = beanFactory.getBean( ManagementService.class );
}
public AppInfoMigrationPlugin( EntityManagerFactory emf, MigrationInfoSerialization migrationInfoSerialization,
EntityCollectionManagerFactory entityCollectionManagerFactory,
GraphManagerFactory graphManagerFactory, ManagementService managementService ) {
this.emf = emf;
this.migrationInfoSerialization = migrationInfoSerialization;
this.entityCollectionManagerFactory = entityCollectionManagerFactory;
this.graphManagerFactory = graphManagerFactory;
this.managementService = managementService;
}
@Override
public String getName() {
return PLUGIN_NAME;
}
@Override
public int getMaxVersion() {
return 2; // standalone plugin, happens once
}
@Override
public PluginPhase getPhase() {
return PluginPhase.MIGRATE;
}
@Override
public void run( ProgressObserver observer ) {
final int version = migrationInfoSerialization.getVersion( getName() );
if ( version == getMaxVersion() ) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping Migration Plugin: {}", getName());
}
return;
}
observer.start();
AtomicInteger count = new AtomicInteger();
//get old app infos to migrate
final Observable<Entity> oldAppInfos = getOldAppInfos();
oldAppInfos.doOnNext( oldAppInfoEntity -> {
migrateAppInfo( oldAppInfoEntity, observer );
count.incrementAndGet();
} )
//we want a doOnError to catch something going wrong, otherwise we'll mark as complete
.doOnError( error -> {
logger.error( "Unable to migrate applications, an error occurred. Please try again", error );
observer.failed( getMaxVersion(), "Unable to migrate applications", error );
} )
//if we complete successfully, set the version and notify the observer
.doOnCompleted( () -> {
migrationInfoSerialization.setVersion( getName(), getMaxVersion() );
observer.complete();
} ).subscribe();//let this run through since it handles errors
}
private void migrateAppInfo( org.apache.usergrid.persistence.model.entity.Entity oldAppInfoEntity,
ProgressObserver observer ) {
// Get appinfos from the Graph, we don't expect many so use iterator
final EntityManager managementEm = emf.getManagementEntityManager();
Map oldAppInfoMap = CpEntityMapUtils.toMap( oldAppInfoEntity );
final String name = ( String ) oldAppInfoMap.get( PROPERTY_NAME );
logger.info( "Attempting to migrate app {}", name );
try {
final String orgName = name.split( "/" )[0];
final String appName = name.split( "/" )[1];
UUID applicationId = getUuid( oldAppInfoMap, "applicationUuid" );
applicationId = applicationId == null ? getUuid( oldAppInfoMap, "appUuid" ) : applicationId;
//get app info from graph to see if it has been migrated already
// create org->app connections, but not for apps in dummy "usergrid" internal organization
//avoid management org
EntityRef orgRef = managementEm.getAlias( Group.ENTITY_TYPE, orgName );
/**
* No op, we couldn't find the org, so we can't roll the app forward
*/
if(orgRef == null){
logger.error( "Unable to retrieve ref for org {}. Not migrating app {}", orgName, appName );
return;
}
// create and connect new APPLICATION_INFO oldAppInfo to Organization
managementService.createApplication( orgRef.getUuid(), name, applicationId, null, true);
observer.update( getMaxVersion(), "Created application_info for " + appName );
}
//swallow
catch ( ApplicationAlreadyExistsException appExists ) {
logger.info( "Application {} already migrated. Ignoring.", name );
observer.update( getMaxVersion(), "Skipping application " + name + " it already exists" );
}
catch ( Exception e ) {
throw new RuntimeException( e );
}
}
private UUID getUuid( Map oldAppInfoMap, String key ) {
UUID applicationId;
Object uuidObject = oldAppInfoMap.get( key );
if ( uuidObject instanceof UUID ) {
applicationId = ( UUID ) uuidObject;
}
else {
applicationId = uuidObject == null ? null : UUIDUtils.tryExtractUUID( uuidObject.toString() );
}
return applicationId;
}
/**
* TODO: Use Graph to get application_info for an specified Application.
*/
private org.apache.usergrid.persistence.Entity getApplicationInfo( final UUID appId ) throws Exception {
final ApplicationScope managementAppScope = getApplicationScope( CpNamingUtils.MANAGEMENT_APPLICATION_ID );
final EntityCollectionManager managementCollectionManager =
entityCollectionManagerFactory.createCollectionManager( managementAppScope );
Observable<MarkedEdge> edgesObservable = getApplicationInfoEdges( appId );
//get the graph for all app infos
Observable<org.apache.usergrid.persistence.model.entity.Entity> entityObs = edgesObservable.flatMap( edge -> {
final Id appInfoId = edge.getTargetNode();
return managementCollectionManager.load( appInfoId ).filter( entity -> {
//check for app id
return entity != null ? entity.getId().getUuid().equals( appId ) : false;
} );
} );
// don't expect many applications, so we block
org.apache.usergrid.persistence.model.entity.Entity applicationInfo =
entityObs.toBlocking().lastOrDefault( null );
if ( applicationInfo == null ) {
return null;
}
Class clazz = Schema.getDefaultSchema().getEntityClass( applicationInfo.getId().getType() );
org.apache.usergrid.persistence.Entity entity =
EntityFactory.newEntity( applicationInfo.getId().getUuid(), applicationInfo.getId().getType(), clazz );
entity.setProperties( CpEntityMapUtils.toMap( applicationInfo ) );
return entity;
}
/**
* Use Graph to get old appinfos from the old and deprecated System App.
*/
public Observable<org.apache.usergrid.persistence.model.entity.Entity> getOldAppInfos() {
final ApplicationScope systemAppScope = getApplicationScope( SYSTEM_APP_ID );
final EntityCollectionManager systemCollectionManager =
entityCollectionManagerFactory.createCollectionManager( systemAppScope );
final GraphManager gm = graphManagerFactory.createEdgeManager( systemAppScope );
String edgeType = CpNamingUtils.getEdgeTypeFromCollectionName( "appinfos" );
Id rootAppId = systemAppScope.getApplication();
final SimpleSearchByEdgeType simpleSearchByEdgeType =
new SimpleSearchByEdgeType( rootAppId, edgeType, Long.MAX_VALUE, SearchByEdgeType.Order.DESCENDING,
Optional.absent() );
Observable<org.apache.usergrid.persistence.model.entity.Entity> entityObs =
gm.loadEdgesFromSource( simpleSearchByEdgeType ).flatMap( edge -> {
final Id appInfoId = edge.getTargetNode();
return systemCollectionManager.load( appInfoId ).filter( entity -> ( entity != null ) );
} );
return entityObs;
}
public Observable<MarkedEdge> getApplicationInfoEdges( final UUID applicationId ) {
final ApplicationScope managementAppScope = getApplicationScope( CpNamingUtils.MANAGEMENT_APPLICATION_ID );
final GraphManager gm = graphManagerFactory.createEdgeManager( managementAppScope );
String edgeType = CpNamingUtils.getEdgeTypeFromCollectionName( CpNamingUtils.APPLICATION_INFOS );
final SimpleSearchByEdge simpleSearchByEdgeType =
new SimpleSearchByEdge( CpNamingUtils.generateApplicationId( CpNamingUtils.MANAGEMENT_APPLICATION_ID ),
edgeType, CpNamingUtils.generateApplicationId( applicationId ), Long.MAX_VALUE,
SearchByEdgeType.Order.DESCENDING, Optional.absent() );
return gm.loadEdgeVersions( simpleSearchByEdgeType );
}
}