You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by sn...@apache.org on 2015/03/30 21:58:13 UTC

[09/50] incubator-usergrid git commit: Many fixes to application delete and restore logic, ApplicationDeleteTests now complete and all passing.

Many fixes to application delete and restore logic, ApplicationDeleteTests now complete and all passing.


Project: http://git-wip-us.apache.org/repos/asf/incubator-usergrid/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-usergrid/commit/14f45ed1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-usergrid/tree/14f45ed1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-usergrid/diff/14f45ed1

Branch: refs/heads/two-dot-o-dev
Commit: 14f45ed1455abea62b534da8662f246dcbb8d6e9
Parents: b7e41f2
Author: Dave Johnson <dm...@apigee.com>
Authored: Fri Mar 6 16:35:52 2015 -0500
Committer: Dave Johnson <dm...@apigee.com>
Committed: Fri Mar 6 16:35:52 2015 -0500

----------------------------------------------------------------------
 .../corepersistence/CpEntityManagerFactory.java | 124 +++++------
 .../corepersistence/util/CpNamingUtils.java     |   3 -
 .../persistence/EntityManagerFactory.java       |   7 +-
 .../rest/applications/ApplicationResource.java  |  21 +-
 .../exceptions/ConflictExceptionMapper.java     |  35 +++
 .../applications/ApplicationsResource.java      |  34 +--
 .../organizations/OrganizationResource.java     |  11 +-
 .../applications/ApplicationDeleteTest.java     | 214 ++++++++++++++++---
 .../endpoints/mgmt/ApplicationResource.java     |  46 +++-
 .../usergrid/management/ManagementService.java  |   6 +
 .../cassandra/ManagementServiceImpl.java        | 110 ++++++++--
 .../exceptions/ConflictException.java           |  36 ++++
 12 files changed, 492 insertions(+), 155 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/core/src/main/java/org/apache/usergrid/corepersistence/CpEntityManagerFactory.java
----------------------------------------------------------------------
diff --git a/stack/core/src/main/java/org/apache/usergrid/corepersistence/CpEntityManagerFactory.java b/stack/core/src/main/java/org/apache/usergrid/corepersistence/CpEntityManagerFactory.java
index 0144ea8..e1a7c60 100644
--- a/stack/core/src/main/java/org/apache/usergrid/corepersistence/CpEntityManagerFactory.java
+++ b/stack/core/src/main/java/org/apache/usergrid/corepersistence/CpEntityManagerFactory.java
@@ -31,6 +31,7 @@ import org.apache.usergrid.corepersistence.rx.AllEntitiesInSystemObservable;
 import org.apache.usergrid.corepersistence.util.CpNamingUtils;
 import org.apache.usergrid.persistence.*;
 
+import static org.apache.usergrid.persistence.Schema.PROPERTY_PATH;
 import static org.apache.usergrid.persistence.Schema.PROPERTY_NAME;
 import static org.apache.usergrid.persistence.Schema.PROPERTY_UUID;
 import static org.apache.usergrid.persistence.Schema.TYPE_APPLICATION;
@@ -221,41 +222,20 @@ public class CpEntityManagerFactory implements EntityManagerFactory, Application
     public UUID initializeApplication( String organizationName, UUID applicationId, String name,
                                        Map<String, Object> properties ) throws Exception {
 
-
         EntityManager em = getEntityManager( CpNamingUtils.MANAGEMENT_APPLICATION_ID);
 
         final String appName = buildAppName( organizationName, name );
 
         // check for pre-existing application
+
         if ( lookupApplication( appName ) != null ) {
             throw new ApplicationAlreadyExistsException( appName );
         }
 
-        getSetup().setupApplicationKeyspace( applicationId, appName );
-
-        UUID orgUuid = lookupOrganization( organizationName );
-        if ( orgUuid == null ) {
-
-            // create new org because the specified one does not exist
-            final String orgName = organizationName;
-
-            final Entity orgInfo;
+        // create application info entity in the management app
 
-            try {
-                orgInfo = em.create( "organization", new HashMap<String, Object>() {{
-                    put( PROPERTY_NAME, orgName );
-                }} );
-            }
-            catch ( DuplicateUniquePropertyExistsException e ) {
-                throw new OrganizationAlreadyExistsException( orgName );
-            }
-
-            em.refreshIndex();
-        }
-
-        // create appinfo entry in the management app
+        getSetup().setupApplicationKeyspace( applicationId, appName );
         final UUID appId = applicationId;
-        final UUID orgId = orgUuid;
         Map<String, Object> appInfoMap = new HashMap<String, Object>() {{
             put( PROPERTY_NAME, appName );
             put( PROPERTY_UUID, appId );
@@ -268,13 +248,13 @@ public class CpEntityManagerFactory implements EntityManagerFactory, Application
         }
         em.refreshIndex();
 
-        // create application oldAppInfo
+        // create application info entity
+
         if ( properties == null ) {
             properties = new TreeMap<String, Object>( CASE_INSENSITIVE_ORDER );
         }
         properties.put( PROPERTY_NAME, appName );
         EntityManager appEm = getEntityManager( applicationId );
-
         appEm.create( applicationId, TYPE_APPLICATION, properties );
         appEm.resetRoles();
         appEm.refreshIndex();
@@ -297,46 +277,76 @@ public class CpEntityManagerFactory implements EntityManagerFactory, Application
     @Override
     public void deleteApplication(UUID applicationId) throws Exception {
 
-        // remove old application info Entity, which is in the Management App's application infos collection
+        // make a copy of the app to delete application_info entity
+
         EntityManager em = getEntityManager( this.getManagementAppId() );
-        Entity applicationInfo = em.get(new SimpleEntityRef(CpNamingUtils.APPLICATION_INFO, applicationId));
-        em.delete( applicationInfo );
+        Entity appToDelete = em.get(new SimpleEntityRef(CpNamingUtils.APPLICATION_INFO, applicationId));
+
+        // and put it in a deleted_application_info collection
+
+        Entity deletedApp = em.create(
+            applicationId, CpNamingUtils.DELETED_APPLICATION_INFO, appToDelete.getProperties() );
+
+        // copy its connections too
+
+        final Set<String> connectionTypes = em.getConnectionTypes(appToDelete);
+        for ( String connType : connectionTypes ) {
+            final Results results =
+                em.getConnectedEntities(appToDelete, connType, null, Query.Level.ALL_PROPERTIES);
+            for ( Entity entity : results.getEntities() ) {
+                em.createConnection( deletedApp, connType, entity );
+            }
+        }
 
-        // create new Entity in deleted_application_infos collection,
-        // with same UUID and properties as deleted application info
-        em.create( applicationId, CpNamingUtils.DELETED_APPLICATION_INFO, applicationInfo.getProperties() );
+        // delete the app from the application_info collection and delete its index
 
+        em.delete( appToDelete);
         em.refreshIndex();
 
-        // delete the application's index
-        managerCache.getEntityIndex( new ApplicationScopeImpl(new SimpleId(applicationId, TYPE_APPLICATION)));
+        final EntityIndex entityIndex = managerCache.getEntityIndex(
+            new ApplicationScopeImpl(new SimpleId(applicationId, TYPE_APPLICATION)));
+        entityIndex.deleteIndex();
     }
 
 
     @Override
     public void restoreApplication(UUID applicationId) throws Exception {
 
-        // remove old delete_appinfos Entity
+        // get the deleted_application_info for the deleted app
+
         EntityManager em = getEntityManager(CpNamingUtils.MANAGEMENT_APPLICATION_ID);
-        Entity appToRestore = em.get( new SimpleEntityRef( CpNamingUtils.DELETED_APPLICATION_INFO, applicationId ));
+        Entity deletedApp = em.get(
+            new SimpleEntityRef( CpNamingUtils.DELETED_APPLICATION_INFO, applicationId ));
 
-        if ( appToRestore == null ) {
+        if ( deletedApp == null ) {
             throw new EntityNotFoundException("Cannot restore. Deleted Application not found: " + applicationId );
         }
 
-        em.delete( appToRestore );
+        // create application_info for restored app
 
-        // restore oldAppInfo in appinfo collection
-        em.create( appToRestore.getUuid(), CpNamingUtils.APPLICATION_INFO, appToRestore.getProperties());
-        em.refreshIndex();
+        Entity restoredApp = em.create(
+            deletedApp.getUuid(), CpNamingUtils.APPLICATION_INFO, deletedApp.getProperties());
+
+        // copy connections from deleted app entity
+
+        final Set<String> connectionTypes = em.getConnectionTypes(deletedApp);
+        for ( String connType : connectionTypes ) {
+            final Results results =
+                em.getConnectedEntities(deletedApp, connType, null, Query.Level.ALL_PROPERTIES);
+            for ( Entity entity : results.getEntities() ) {
+                em.createConnection( restoredApp, connType, entity );
+            }
+        }
+
+        // delete the deleted app entity rebuild the app index
+
+        em.delete( deletedApp );
 
-        // rebuild the apps index
         this.rebuildApplicationIndexes(applicationId, new ProgressObserver() {
             @Override
             public void onProgress(EntityRef entity) {
-                logger.info("Restored oldAppInfo {}:{}", entity.getType(), entity.getUuid());
+                logger.debug("Restored oldAppInfo {}:{}", entity.getType(), entity.getUuid());
             }
-
         });
     }
 
@@ -350,33 +360,11 @@ public class CpEntityManagerFactory implements EntityManagerFactory, Application
     }
 
 
-    public UUID lookupOrganization( String name ) throws Exception {
-        init();
-
-        // Query q = Query.fromQL(PROPERTY_NAME + " = '" + name + "'");
-        EntityManager em = getEntityManager( CpNamingUtils.MANAGEMENT_APPLICATION_ID );
-
-        final EntityRef alias = em.getAlias( "organizations", name );
-
-        if ( alias == null ) {
-            return null;
-        }
-
-        final Entity entity = em.get( alias );
-
-        if ( entity == null ) {
-            return null;
-        }
-
-        return entity.getUuid();
-    }
-
-
     @Override
     public UUID lookupApplication( final String name ) throws Exception {
         init();
 
-        // attempt to look up APPLICATION_INFO oldAppInfo by name
+        // attempt to look up APPLICATION_INFO by name
 
         UUID applicationId = null;
         EntityManager em = getEntityManager( CpNamingUtils.MANAGEMENT_APPLICATION_ID );
@@ -440,7 +428,7 @@ public class CpEntityManagerFactory implements EntityManagerFactory, Application
     @Override
     @Metered(group = "core", name = "EntityManagerFactory_getApplication")
     public Map<String, UUID> getApplications() throws Exception {
-        return getApplications( false );
+        return getApplications(false);
     }
 
 

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/core/src/main/java/org/apache/usergrid/corepersistence/util/CpNamingUtils.java
----------------------------------------------------------------------
diff --git a/stack/core/src/main/java/org/apache/usergrid/corepersistence/util/CpNamingUtils.java b/stack/core/src/main/java/org/apache/usergrid/corepersistence/util/CpNamingUtils.java
index 8f3327d..0adf9d7 100644
--- a/stack/core/src/main/java/org/apache/usergrid/corepersistence/util/CpNamingUtils.java
+++ b/stack/core/src/main/java/org/apache/usergrid/corepersistence/util/CpNamingUtils.java
@@ -38,9 +38,6 @@ import org.apache.usergrid.persistence.model.entity.SimpleId;
  */
 public class CpNamingUtils {
 
-    // TODO: Why do we have three internal apps?  Can we merge this into a single management app?
-    // It would make administration much easier and cleaner on the ES side
-
     /** Edge types for collection suffix */
     public static final String EDGE_COLL_SUFFIX = "zzzcollzzz";
 

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/core/src/main/java/org/apache/usergrid/persistence/EntityManagerFactory.java
----------------------------------------------------------------------
diff --git a/stack/core/src/main/java/org/apache/usergrid/persistence/EntityManagerFactory.java b/stack/core/src/main/java/org/apache/usergrid/persistence/EntityManagerFactory.java
index a077ea0..42caf02 100644
--- a/stack/core/src/main/java/org/apache/usergrid/persistence/EntityManagerFactory.java
+++ b/stack/core/src/main/java/org/apache/usergrid/persistence/EntityManagerFactory.java
@@ -76,6 +76,11 @@ public interface EntityManagerFactory {
      */
     public abstract void deleteApplication( UUID applicationId ) throws Exception;
 
+    /**
+     * Restore deleted application.
+     */
+    public void restoreApplication( UUID applicationId) throws Exception;
+
     public abstract UUID importApplication( String organization, UUID applicationId, String name,
                                             Map<String, Object> properties ) throws Exception;
 
@@ -169,8 +174,6 @@ public interface EntityManagerFactory {
 
     public Health getEntityStoreHealth();
 
-    void restoreApplication(UUID applicationId) throws Exception;
-
     public interface ProgressObserver {
 
         public void onProgress( EntityRef entity);

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/rest/src/main/java/org/apache/usergrid/rest/applications/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/main/java/org/apache/usergrid/rest/applications/ApplicationResource.java b/stack/rest/src/main/java/org/apache/usergrid/rest/applications/ApplicationResource.java
index 4f8002b..e62a9cb 100644
--- a/stack/rest/src/main/java/org/apache/usergrid/rest/applications/ApplicationResource.java
+++ b/stack/rest/src/main/java/org/apache/usergrid/rest/applications/ApplicationResource.java
@@ -30,6 +30,7 @@ import javax.ws.rs.core.PathSegment;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
+import com.sun.jersey.api.ConflictException;
 import org.apache.usergrid.persistence.exceptions.EntityNotFoundException;
 import org.apache.usergrid.rest.ApiResponse;
 import org.apache.usergrid.rest.security.annotations.RequireOrganizationAccess;
@@ -488,6 +489,9 @@ public class ApplicationResource extends ServiceResource {
     }
 
 
+    /**
+     * Put on application URL will restore application if it was deleted.
+     */
     @PUT
     @RequireOrganizationAccess
     @Override
@@ -498,12 +502,7 @@ public class ApplicationResource extends ServiceResource {
             throw new IllegalArgumentException("Application ID not specified in request");
         }
 
-        ApplicationInfo app = management.getApplicationInfo( applicationId );
-        if ( app == null ) {
-            throw new EntityNotFoundException("Application ID " + applicationId + " not found");
-        }
-
-        emf.restoreApplication( applicationId );
+        management.restoreApplication( applicationId );
 
         ApiResponse response = createApiResponse();
         response.setAction( "restore" );
@@ -532,15 +531,9 @@ public class ApplicationResource extends ServiceResource {
             throw new IllegalArgumentException("Application ID not specified in request");
         }
 
-        ApplicationInfo app = management.getApplicationInfo( applicationId );
-        if ( app == null ) {
-            throw new EntityNotFoundException("Application ID " + applicationId + " not found");
-        }
-
-        emf.deleteApplication( applicationId );
+        management.deleteApplication( applicationId );
 
-        LOG.debug( "ApplicationResource.delete() deleted appId = {} appName = {}",
-            applicationId, app.getName() );
+        LOG.debug( "ApplicationResource.delete() deleted appId = {}", applicationId);
 
         ApiResponse response = createApiResponse();
         response.setAction( "delete" );

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/rest/src/main/java/org/apache/usergrid/rest/exceptions/ConflictExceptionMapper.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/main/java/org/apache/usergrid/rest/exceptions/ConflictExceptionMapper.java b/stack/rest/src/main/java/org/apache/usergrid/rest/exceptions/ConflictExceptionMapper.java
new file mode 100644
index 0000000..34a813d
--- /dev/null
+++ b/stack/rest/src/main/java/org/apache/usergrid/rest/exceptions/ConflictExceptionMapper.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+package org.apache.usergrid.rest.exceptions;
+
+
+import org.apache.usergrid.management.exceptions.ConflictException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+import static javax.ws.rs.core.Response.Status.CONFLICT;
+
+
+@Provider
+public class ConflictExceptionMapper extends AbstractExceptionMapper<ConflictException> {
+
+    @Override
+    public Response toResponse( ConflictException e ) {
+        return toResponse( CONFLICT, e );
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/rest/src/main/java/org/apache/usergrid/rest/management/organizations/applications/ApplicationsResource.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/main/java/org/apache/usergrid/rest/management/organizations/applications/ApplicationsResource.java b/stack/rest/src/main/java/org/apache/usergrid/rest/management/organizations/applications/ApplicationsResource.java
index 1fe9745..5691e2e 100644
--- a/stack/rest/src/main/java/org/apache/usergrid/rest/management/organizations/applications/ApplicationsResource.java
+++ b/stack/rest/src/main/java/org/apache/usergrid/rest/management/organizations/applications/ApplicationsResource.java
@@ -132,31 +132,31 @@ public class ApplicationsResource extends AbstractContextResource {
 
     @RequireOrganizationAccess
     @Path(RootResource.APPLICATION_ID_PATH)
-    public ApplicationResource applicationFromOrganizationByApplicationId( @Context UriInfo ui,
-                                                                           @PathParam( "applicationId" )
-                                                                           String applicationIdStr ) throws Exception {
+    public ApplicationResource applicationFromOrganizationByApplicationId(
+        @Context UriInfo ui, @PathParam( "applicationId" ) String applicationIdStr ) throws Exception {
 
-        return getSubResource( ApplicationResource.class ).init( organization, UUID.fromString( applicationIdStr ) );
+        return getSubResource( ApplicationResource.class )
+            .init( organization, UUID.fromString( applicationIdStr ) );
     }
 
 
-//    @RequireOrganizationAccess
-//    @Path(RootResource.APPLICATION_ID_PATH)
-//    @PUT
-//    public ApplicationResource restoreApplicationFromOrganizationByApplicationId(
-//        @Context UriInfo ui,
-//        @PathParam( "applicationId" )
-//        String applicationIdStr ) throws Exception {
-//
-//        return getSubResource( ApplicationResource.class ).init( organization, UUID.fromString( applicationIdStr ) );
-//    }
+    @RequireOrganizationAccess
+    @Path(RootResource.APPLICATION_ID_PATH)
+    @PUT
+    public ApplicationResource restoreApplicationFromOrganizationByApplicationId(
+        @Context UriInfo ui,
+        @PathParam( "applicationId" )
+        String applicationIdStr ) throws Exception {
+
+        return getSubResource( ApplicationResource.class )
+            .init( organization, UUID.fromString( applicationIdStr ) );
+    }
 
 
     @RequireOrganizationAccess
     @Path( "{applicationName}" )
-    public ApplicationResource applicationFromOrganizationByApplicationName( @Context UriInfo ui,
-                                                                             @PathParam( "applicationName" )
-                                                                             String applicationName ) throws Exception {
+    public ApplicationResource applicationFromOrganizationByApplicationName(
+        @Context UriInfo ui, @PathParam( "applicationName" ) String applicationName ) throws Exception {
 
         String appName =
                 applicationName.contains( "/" ) ? applicationName : organization.getName() + "/" + applicationName;

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/rest/src/main/java/org/apache/usergrid/rest/organizations/OrganizationResource.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/main/java/org/apache/usergrid/rest/organizations/OrganizationResource.java b/stack/rest/src/main/java/org/apache/usergrid/rest/organizations/OrganizationResource.java
index 8aee185..695becc 100644
--- a/stack/rest/src/main/java/org/apache/usergrid/rest/organizations/OrganizationResource.java
+++ b/stack/rest/src/main/java/org/apache/usergrid/rest/organizations/OrganizationResource.java
@@ -100,9 +100,14 @@ public class OrganizationResource extends AbstractContextResource {
         if (organizationId == null) {
             return null;
         }
-        BiMap<UUID, String> apps = management.getApplicationsForOrganization( organizationId );
-        if ( apps.get( applicationId ) == null ) {
-            return null;
+
+        // don't look up app if request is a PUT because a PUT can be used to restore a deleted app
+        if ( !hc.getRequest().getMethod().equalsIgnoreCase("PUT") ) {
+
+            BiMap<UUID, String> apps = management.getApplicationsForOrganization(organizationId);
+            if (apps.get(applicationId) == null) {
+                return null;
+            }
         }
 
         return appResourceFor( applicationId );

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/rest/src/test/java/org/apache/usergrid/rest/applications/ApplicationDeleteTest.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/ApplicationDeleteTest.java b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/ApplicationDeleteTest.java
index 8ac449a..1826d59 100644
--- a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/ApplicationDeleteTest.java
+++ b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/ApplicationDeleteTest.java
@@ -36,6 +36,16 @@ public class ApplicationDeleteTest  extends AbstractRestIT {
     private static final Logger logger = LoggerFactory.getLogger(ApplicationDeleteTest.class);
 
 
+    /**
+     * Test most common use cases.
+     * <pre>
+     *  - create app with collection of things
+     *  - delete the app
+     *  - test that attempts to get the app, its collections and entities throw 400 with message
+     *  - test that we cannot delete the app a second time
+     *  - test that we can create a new app with the same name as the deleted app
+     * </pre>
+     */
     @Test
     public void testBasicOperation() throws Exception {
 
@@ -45,30 +55,9 @@ public class ApplicationDeleteTest  extends AbstractRestIT {
         String appToDeleteName = clientSetup.getAppName() + "_appToDelete";
         Token orgAdminToken = getAdminToken( clientSetup.getUsername(), clientSetup.getUsername());
 
-        ApiResponse appCreateResponse = clientSetup.getRestClient()
-            .management().orgs().organization( orgName ).app().getResource()
-            .queryParam( "access_token", orgAdminToken.getAccessToken() )
-            .type( MediaType.APPLICATION_JSON )
-            .post( ApiResponse.class, new Application( appToDeleteName ) );
-        UUID appToDeleteId = appCreateResponse.getEntities().get(0).getUuid();
-
         List<Entity> entities = new ArrayList<>();
-        for ( int i=0; i<10; i++ ) {
 
-            final String entityName = "entity" + i;
-            Entity entity = new Entity();
-            entity.setProperties(new HashMap<String, Object>() {{
-                put("name", entityName );
-            }});
-
-            ApiResponse createResponse = clientSetup.getRestClient()
-                .org(orgName).app( appToDeleteName ).collection("things").getResource()
-                .queryParam("access_token", orgAdminToken.getAccessToken())
-                .type(MediaType.APPLICATION_JSON)
-                .post( ApiResponse.class, entity );
-
-            entities.add( createResponse.getEntities().get(0) );
-        }
+        UUID appToDeleteId = createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities);
 
         // delete the app
 
@@ -130,7 +119,8 @@ public class ApplicationDeleteTest  extends AbstractRestIT {
             Assert.assertEquals("organization_application_not_found", node.get("error").textValue());
         }
 
-        // test that we cannot see the application in the list of applications
+        // test that we cannot see the application in the list of applications returned
+        // by the management resource's get organization's applications end-point
 
         refreshIndex();
 
@@ -171,33 +161,195 @@ public class ApplicationDeleteTest  extends AbstractRestIT {
     }
 
 
+   /**
+    * Test restore of deleted app.
+    * <pre>
+    *  - create app with collection of things
+    *  - delete the app
+    *  - restore the app
+    *  - test that we can get the app, its collections and an entity
+    * </pre>
+    */
     @Test
     public void testAppRestore() throws Exception {
 
-        // create and delete app
+        // create app with a collection of "things"
+
+        String orgName = clientSetup.getOrganization().getName();
+        String appToDeleteName = clientSetup.getAppName() + "_appToDelete";
+        Token orgAdminToken = getAdminToken( clientSetup.getUsername(), clientSetup.getUsername());
+
+        List<Entity> entities = new ArrayList<>();
+
+        UUID appToDeleteId = createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities);
+
+        // delete the app
+
+        clientSetup.getRestClient()
+            .org(orgName).app(appToDeleteId.toString() ).getResource()
+            .queryParam("access_token", orgAdminToken.getAccessToken() )
+            .delete();
 
         // restore the app
 
-        // test that app appears in list of apps
+        clientSetup.getRestClient()
+            .org(orgName).app(appToDeleteId.toString() ).getResource()
+            .queryParam("access_token", orgAdminToken.getAccessToken() )
+            .put();
+
 
-        // test that application's collection exists
-    }
+        // test that we can see the application in the list of applications
+
+        ManagementResponse orgAppResponse = clientSetup.getRestClient()
+            .management().orgs().organization( orgName ).apps().getOrganizationApplications();
+
+        boolean found = false;
+        for ( String appName : orgAppResponse.getData().keySet() ) {
+            if ( orgAppResponse.getData().get( appName ).equals( appToDeleteId.toString() )) {
+                found = true;
+                break;
+            }
+        }
+
+        Assert.assertTrue( found );
 
+        // test that we can get an app entity
 
+        UUID entityId = entities.get(0).getUuid();
+        ApiResponse entityResponse = clientSetup.getRestClient()
+            .org(orgName).app(appToDeleteName).collection("things").entity( entityId ).getResource()
+            .queryParam("access_token", orgAdminToken.getAccessToken())
+            .type(MediaType.APPLICATION_JSON)
+            .get(ApiResponse.class);
+        Assert.assertEquals( entityId, entityResponse.getEntities().get(0).getUuid() );
 
+        // test that we can get deleted app's collection
+
+        ApiResponse collectionReponse = clientSetup.getRestClient()
+            .org(orgName).app(appToDeleteName).collection("things").getResource()
+            .queryParam("access_token", orgAdminToken.getAccessToken())
+            .type(MediaType.APPLICATION_JSON)
+            .get(ApiResponse.class);
+        Assert.assertEquals( entities.size(), collectionReponse.getEntityCount() );
+    }
+
+
+    /**
+     * Test that we cannot restore deleted app with same name as
+     */
     @Test
     public void testAppRestoreConflict() throws Exception {
 
-        // create and delete app
+        // create app with a collection of "things"
+
+        String orgName = clientSetup.getOrganization().getName();
+        String appToDeleteName = clientSetup.getAppName() + "_appToDelete";
+        Token orgAdminToken = getAdminToken( clientSetup.getUsername(), clientSetup.getUsername());
+
+        List<Entity> entities = new ArrayList<>();
+
+        UUID appToDeleteId = createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities);
+
+        // delete the app
+
+        clientSetup.getRestClient()
+            .org( orgName ).app(appToDeleteId.toString() ).getResource()
+            .queryParam( "access_token", orgAdminToken.getAccessToken() )
+            .delete();
+
+        // create new app with same name
+
+        createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities);
+
+        // attempt to restore original app, should get 409
+
+        try {
+
+            clientSetup.getRestClient()
+                .org(orgName).app(appToDeleteId.toString()).getResource()
+                .queryParam("access_token", orgAdminToken.getAccessToken())
+                .put();
+
+            Assert.fail("Must fail to restore app with same name as existing app");
+
+        } catch ( UniformInterfaceException e ) {
+            Assert.assertEquals(409, e.getResponse().getStatus());
+        }
+    }
+
+
+    /**
+     * Test that we cannot delete an app with same name as an app that is already deleted.
+     * TODO: investigate way to support this, there should be no such restriction.
+     */
+    @Test
+    public void testAppDeleteConflict() throws Exception {
+
+        // create app with a collection of "things"
+
+        String orgName = clientSetup.getOrganization().getName();
+        String appToDeleteName = clientSetup.getAppName() + "_appToDelete";
+        Token orgAdminToken = getAdminToken( clientSetup.getUsername(), clientSetup.getUsername());
+
+        List<Entity> entities = new ArrayList<>();
+
+        UUID appToDeleteId = createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities);
+
+        // delete the app
+
+        clientSetup.getRestClient()
+            .org( orgName ).app(appToDeleteId.toString() ).getResource()
+            .queryParam( "access_token", orgAdminToken.getAccessToken() )
+            .delete();
 
         // create new app with same name
 
-        // attempt to restore original app
+        UUID newAppId = createAppWithCollection(orgName, appToDeleteName, orgAdminToken, entities);
+
+        // attempt to delete new app, it should fail
+
+        try {
+
+            clientSetup.getRestClient()
+                .org(orgName).app( newAppId.toString() ).getResource()
+                .queryParam("access_token", orgAdminToken.getAccessToken())
+                .delete();
+
+            Assert.fail("Must fail to delete app with same name as deleted app");
+
+        } catch ( UniformInterfaceException e ) {
+            Assert.assertEquals( 409, e.getResponse().getStatus() );
+        }
+    }
+
+
+    private UUID createAppWithCollection(
+        String orgName, String appName, Token orgAdminToken, List<Entity> entities) {
+
+        ApiResponse appCreateResponse = clientSetup.getRestClient()
+            .management().orgs().organization( orgName ).app().getResource()
+            .queryParam( "access_token", orgAdminToken.getAccessToken() )
+            .type( MediaType.APPLICATION_JSON )
+            .post( ApiResponse.class, new Application( appName ) );
+        UUID appId = appCreateResponse.getEntities().get(0).getUuid();
+
+        for ( int i=0; i<10; i++ ) {
 
-        // test that HTTP 409 CONFLICT and informative error message is received
+            final String entityName = "entity" + i;
+            Entity entity = new Entity();
+            entity.setProperties(new HashMap<String, Object>() {{
+                put("name", entityName );
+            }});
 
-        // create a collection with two entities
+            ApiResponse createResponse = clientSetup.getRestClient()
+                .org(orgName).app( appName ).collection("things").getResource()
+                .queryParam("access_token", orgAdminToken.getAccessToken())
+                .type(MediaType.APPLICATION_JSON)
+                .post( ApiResponse.class, entity );
 
+            entities.add( createResponse.getEntities().get(0) );
+        }
+        return appId;
     }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/rest/src/test/java/org/apache/usergrid/rest/test/resource2point0/endpoints/mgmt/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/test/resource2point0/endpoints/mgmt/ApplicationResource.java b/stack/rest/src/test/java/org/apache/usergrid/rest/test/resource2point0/endpoints/mgmt/ApplicationResource.java
index 6b7742f..034c302 100644
--- a/stack/rest/src/test/java/org/apache/usergrid/rest/test/resource2point0/endpoints/mgmt/ApplicationResource.java
+++ b/stack/rest/src/test/java/org/apache/usergrid/rest/test/resource2point0/endpoints/mgmt/ApplicationResource.java
@@ -22,17 +22,28 @@ package org.apache.usergrid.rest.test.resource2point0.endpoints.mgmt;
 
 import javax.ws.rs.core.MediaType;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.usergrid.rest.test.resource2point0.endpoints.NamedResource;
 import org.apache.usergrid.rest.test.resource2point0.endpoints.UrlResource;
 import org.apache.usergrid.rest.test.resource2point0.model.Application;
 import org.apache.usergrid.rest.test.resource2point0.model.*;
 import org.apache.usergrid.rest.test.resource2point0.state.ClientContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.StringReader;
 
 
 /**
  * Classy class class.
  */
 public class ApplicationResource extends NamedResource {
+
+    private static final Logger logger = LoggerFactory.getLogger(ApplicationResource.class);
+
+    ObjectMapper mapper = new ObjectMapper();
+
     public ApplicationResource(ClientContext context, UrlResource parent) {
         super("applications", context, parent);
     }
@@ -51,16 +62,41 @@ public class ApplicationResource extends NamedResource {
             .accept(MediaType.APPLICATION_JSON).post(application);
     }
 
-    public Entity post(Entity payload){
-        ApiResponse response = getResource(true).type( MediaType.APPLICATION_JSON_TYPE ).accept(MediaType.APPLICATION_JSON)
-            .post(ApiResponse.class, payload);
+    public Entity post(Entity payload) {
+
+        String responseString = getResource(true)
+            .type( MediaType.APPLICATION_JSON_TYPE )
+            .accept(MediaType.APPLICATION_JSON)
+            .post(String.class, payload);
+
+        logger.debug("Response from post: " + responseString);
+
+        ApiResponse response;
+        try {
+            response = mapper.readValue(new StringReader(responseString), ApiResponse.class);
+        } catch (IOException e) {
+            throw new RuntimeException("Error parsing response", e);
+        }
+
         return new Entity(response);
     }
 
 
     public Entity get() {
-        ApiResponse response = getResource(true).type(MediaType.APPLICATION_JSON_TYPE ).accept(MediaType.APPLICATION_JSON)
-            .get(ApiResponse.class);
+
+        String responseString = getResource(true)
+            .type( MediaType.APPLICATION_JSON_TYPE )
+            .accept(MediaType.APPLICATION_JSON)
+            .get(String.class);
+
+        logger.debug("Response from post: " + responseString);
+
+        ApiResponse response;
+        try {
+            response = mapper.readValue(new StringReader(responseString), ApiResponse.class);
+        } catch (IOException e) {
+            throw new RuntimeException("Error parsing response", e);
+        }
 
         return new Entity(response);
     }

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
----------------------------------------------------------------------
diff --git a/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java b/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
index 97d23c2..aff9e15 100644
--- a/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
+++ b/stack/services/src/main/java/org/apache/usergrid/management/ManagementService.java
@@ -149,6 +149,8 @@ public interface ManagementService {
 
     public ApplicationInfo getApplicationInfo( UUID applicationId ) throws Exception;
 
+    ApplicationInfo getDeletedApplicationInfo(UUID applicationId) throws Exception;
+
     public ApplicationInfo getApplicationInfo( Identifier id ) throws Exception;
 
     public ApplicationInfo getApplicationInfoFromAccessToken( String token ) throws Exception;
@@ -318,4 +320,8 @@ public interface ManagementService {
 
     /** For testing purposes only */
     public Properties getProperties();
+
+    public void deleteApplication(UUID applicationId) throws Exception;
+
+    public ApplicationInfo restoreApplication(UUID applicationId) throws Exception;
 }

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
----------------------------------------------------------------------
diff --git a/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java b/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
index d8fc0f5..a33c324 100644
--- a/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
+++ b/stack/services/src/main/java/org/apache/usergrid/management/cassandra/ManagementServiceImpl.java
@@ -29,7 +29,10 @@ import java.util.Set;
 import java.util.UUID;
 
 import org.apache.usergrid.corepersistence.util.CpNamingUtils;
+import org.apache.usergrid.management.exceptions.*;
+import org.apache.usergrid.persistence.core.scope.ApplicationScopeImpl;
 import org.apache.usergrid.persistence.index.query.Query;
+import org.apache.usergrid.persistence.model.entity.SimpleId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -45,15 +48,6 @@ import org.apache.usergrid.management.ManagementService;
 import org.apache.usergrid.management.OrganizationInfo;
 import org.apache.usergrid.management.OrganizationOwnerInfo;
 import org.apache.usergrid.management.UserInfo;
-import org.apache.usergrid.management.exceptions.DisabledAdminUserException;
-import org.apache.usergrid.management.exceptions.DisabledAppUserException;
-import org.apache.usergrid.management.exceptions.IncorrectPasswordException;
-import org.apache.usergrid.management.exceptions.ManagementException;
-import org.apache.usergrid.management.exceptions.RecentlyUsedPasswordException;
-import org.apache.usergrid.management.exceptions.UnableToLeaveOrganizationException;
-import org.apache.usergrid.management.exceptions.UnactivatedAdminUserException;
-import org.apache.usergrid.management.exceptions.UnactivatedAppUserException;
-import org.apache.usergrid.management.exceptions.UnconfirmedAdminUserException;
 import org.apache.usergrid.persistence.CredentialsInfo;
 import org.apache.usergrid.persistence.Entity;
 import org.apache.usergrid.persistence.EntityManager;
@@ -250,8 +244,7 @@ public class ManagementServiceImpl implements ManagementService {
         return properties.properties;
     }
 
-
-    @Autowired
+   @Autowired
     public void setTokenService( TokenService tokens ) {
         this.tokens = tokens;
     }
@@ -1662,7 +1655,7 @@ public class ManagementServiceImpl implements ManagementService {
         }
         catch ( UnavailableSecurityManagerException e ) {
             // occurs in the rare case that this is called before the full stack is initialized
-            logger.warn("Error getting user, organization created activity will not be created", e);
+            logger.warn("Error getting user, application created activity will not be created", e);
         }
         if ( ( user != null ) && user.isAdminUser() ) {
             postOrganizationActivity( organizationId, user, "create", appInfo, "Application", applicationName,
@@ -1677,6 +1670,82 @@ public class ManagementServiceImpl implements ManagementService {
 
 
     @Override
+    public void deleteApplication(UUID applicationId) throws Exception {
+
+        // make sure there is not already a delete app with the same name
+
+        EntityManager em = emf.getEntityManager(CpNamingUtils.MANAGEMENT_APPLICATION_ID);
+        Entity appToDelete = em.get(
+            new SimpleEntityRef( CpNamingUtils.APPLICATION_INFO, applicationId ));
+
+        final EntityRef alias = em.getAlias(
+            CpNamingUtils.DELETED_APPLICATION_INFO, appToDelete.getName() );
+
+        if ( alias != null ) {
+            throw new ConflictException("Cannot delete app with same name as already deleted app");
+        }
+
+        emf.deleteApplication( applicationId );
+    }
+
+
+    @Override
+    public ApplicationInfo restoreApplication(UUID applicationId) throws Exception {
+
+        ApplicationInfo app = getDeletedApplicationInfo( applicationId );
+        if ( app == null ) {
+            throw new EntityNotFoundException("Deleted application ID " + applicationId + " not found");
+        }
+
+        if ( emf.lookupApplication( app.getName() ) != null ) {
+            throw new ConflictException("Cannot restore application, one with that name already exists.");
+        }
+
+        // restore application_info entity
+
+        EntityManager em = emf.getEntityManager( CpNamingUtils.MANAGEMENT_APPLICATION_ID );
+        emf.restoreApplication(applicationId);
+
+        // restore token
+
+        Entity appInfo = em.get(
+            new SimpleEntityRef( CpNamingUtils.APPLICATION_INFO, applicationId ));
+
+        writeUserToken( smf.getManagementAppId(), appInfo,
+            encryptionService.plainTextCredentials(
+                generateOAuthSecretKey( AuthPrincipalType.APPLICATION ),
+                null,
+                smf.getManagementAppId() ) );
+
+        String orgName = appInfo.getName().split("/")[0];
+        EntityRef alias = em.getAlias( Group.ENTITY_TYPE, orgName );
+        Entity orgEntity = em.get( alias );
+
+        addApplicationToOrganization( orgEntity.getUuid(), applicationId, appInfo );
+
+        // create activity
+
+        UserInfo user = null;
+        try {
+            user = SubjectUtils.getUser();
+        }
+        catch ( UnavailableSecurityManagerException e ) {
+            // occurs in the rare case that this is called before the full stack is initialized
+            logger.warn("Error getting user, application restored created activity will not be created", e);
+        }
+        if ( ( user != null ) && user.isAdminUser() ) {
+            postOrganizationActivity( orgEntity.getUuid(), user, "restore", appInfo, "Application", appInfo.getName(),
+                "<a href=\"mailto:" + user.getEmail() + "\">" + user.getName() + " (" + user.getEmail()
+                    + ")</a> restored an application named " + appInfo.getName(), null );
+        }
+
+        em.refreshIndex();
+
+        return new ApplicationInfo( applicationId, appInfo.getName() );
+    }
+
+
+    @Override
     public OrganizationInfo getOrganizationForApplication( UUID applicationInfoId ) throws Exception {
 
         if ( applicationInfoId == null ) {
@@ -1804,6 +1873,22 @@ public class ManagementServiceImpl implements ManagementService {
 
 
     @Override
+    public ApplicationInfo getDeletedApplicationInfo(UUID applicationId) throws Exception {
+        if ( applicationId == null ) {
+            return null;
+        }
+        EntityManager em = emf.getEntityManager( smf.getManagementAppId() );
+        Entity entity = em.get( new SimpleEntityRef(
+            CpNamingUtils.DELETED_APPLICATION_INFO, applicationId ));
+
+        if ( entity != null ) {
+            return new ApplicationInfo( applicationId, entity.getName() );
+        }
+        return null;
+    }
+
+
+    @Override
     public ApplicationInfo getApplicationInfo( Identifier id ) throws Exception {
         if ( id == null ) {
             return null;
@@ -2979,4 +3064,5 @@ public class ManagementServiceImpl implements ManagementService {
         else
             return Boolean.parseBoolean(obj);
     }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/14f45ed1/stack/services/src/main/java/org/apache/usergrid/management/exceptions/ConflictException.java
----------------------------------------------------------------------
diff --git a/stack/services/src/main/java/org/apache/usergrid/management/exceptions/ConflictException.java b/stack/services/src/main/java/org/apache/usergrid/management/exceptions/ConflictException.java
new file mode 100644
index 0000000..2ec14c8
--- /dev/null
+++ b/stack/services/src/main/java/org/apache/usergrid/management/exceptions/ConflictException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
+
+package org.apache.usergrid.management.exceptions;
+
+public class ConflictException extends ManagementException {
+
+    public ConflictException() {
+        super();
+    }
+
+    public ConflictException(String arg0, Throwable arg1) {
+        super(arg0, arg1);
+    }
+
+    public ConflictException(String arg0) {
+        super(arg0);
+    }
+
+    public ConflictException(Throwable arg0) {
+        super(arg0);
+    }
+}