com.streamreduce.core.service.EventServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.streamreduce.core.service.EventServiceImpl.java

Source

/*
 * Copyright 2012 Nodeable Inc
 *
 *    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 com.streamreduce.core.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
import com.streamreduce.Constants;
import com.streamreduce.ProviderIdConstants;
import com.streamreduce.connections.ConnectionProvider;
import com.streamreduce.connections.ConnectionProviderFactory;
import com.streamreduce.core.dao.DAODatasourceType;
import com.streamreduce.core.dao.EventDAO;
import com.streamreduce.core.dao.GenericCollectionDAO;
import com.streamreduce.core.event.EventId;
import com.streamreduce.core.model.Account;
import com.streamreduce.core.model.Connection;
import com.streamreduce.core.model.Event;
import com.streamreduce.core.model.InventoryItem;
import com.streamreduce.core.model.ObjectWithId;
import com.streamreduce.core.model.SobaObject;
import com.streamreduce.core.model.User;
import com.streamreduce.core.model.messages.SobaMessage;
import com.streamreduce.core.service.exception.AccountNotFoundException;
import com.streamreduce.core.service.exception.UserNotFoundException;
import net.sf.json.JSONObject;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.authc.AuthenticationException;
import org.bson.types.ObjectId;
import org.jclouds.domain.LocationScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Implementation of {@link EventService}.
 */
@Service("eventService")
public class EventServiceImpl extends AbstractService implements EventService {

    @Autowired
    private EventDAO eventDAO;
    @Autowired
    private ConnectionProviderFactory connectionProviderFactory;
    @Autowired
    private GenericCollectionDAO genericCollectionDAO;
    @Autowired
    private SecurityService securityService;
    @Autowired
    private UserService userService;

    /**
     * {@inheritDoc}
     */
    @Override
    public <T extends ObjectWithId> Event createEvent(EventId eventId, T target,
            Map<String, Object> extraMetadata) {
        Account account = null;
        User user = null;

        try {
            user = securityService.getCurrentUser();
            account = user.getAccount();
        } catch (AuthenticationException ae) {
            // We will not persist any READ_* events when a user is not logged in
            if (eventId == EventId.READ) {
                logger.debug("Anonymous read events are not persisted (" + target + "): " + eventId);
                return null;
            }
        } catch (UnavailableSecurityManagerException e) {
            logger.warn("Unable to derive user from SecurityService.  A User will be derived from the target ("
                    + target + ") if possible.  If not, no event will be persisted", e);
        } catch (Exception nfi) {
            logger.warn("Unknown exception type in EventService", nfi);
        }

        if (extraMetadata == null) {
            extraMetadata = new HashMap<>();
        }

        // TODO: Figure out a way to make these automatically-generated metadata keys constants somewhere

        // Extend the context with T information
        if (target != null) {
            // Fill out ObjectWithId information
            extraMetadata.put("targetId", target.getId());
            extraMetadata.put("targetVersion", target.getVersion());
            // Fill out type (camel casing of the object type)
            extraMetadata.put("targetType", target.getClass().getSimpleName());

            // Fill out the SobaObject information
            if (target instanceof SobaObject) {
                SobaObject tSobaObject = (SobaObject) target;

                // Attempt to gather the user/account from the target of the event when there is no user logged in.
                // We can only do this for subclasses of SobaObject because the other two objects that create events
                // (Account/User) can only provide one piece of the puzzle.
                if (user == null) {
                    user = tSobaObject.getUser();
                    account = tSobaObject.getAccount();
                }

                extraMetadata.put("targetVisibility", tSobaObject.getVisibility());
                extraMetadata.put("targetAlias", tSobaObject.getAlias());
                extraMetadata.put("targetHashtags", tSobaObject.getHashtags());
            }

            // Fill in specific object information
            if (target instanceof Account) {
                Account tAccount = (Account) target;
                extraMetadata.put("targetFuid", tAccount.getFuid());
                extraMetadata.put("targetName", tAccount.getName());

            } else if (target instanceof InventoryItem) {
                InventoryItem inventoryItem = (InventoryItem) target;
                Connection connection = inventoryItem.getConnection();
                ConnectionProvider tConnectionProvider = connectionProviderFactory
                        .connectionProviderFromId(connection.getProviderId());

                extraMetadata.put("targetExternalId", inventoryItem.getExternalId());
                extraMetadata.put("targetExternalType", inventoryItem.getType());

                extraMetadata.put("targetConnectionId", connection.getId());
                extraMetadata.put("targetConnectionAlias", connection.getAlias());
                extraMetadata.put("targetConnectionHashtags", connection.getHashtags());
                extraMetadata.put("targetConnectionVersion", connection.getVersion());

                extraMetadata.put("targetProviderId", tConnectionProvider.getId());
                extraMetadata.put("targetProviderDisplayName", tConnectionProvider.getDisplayName());
                extraMetadata.put("targetProviderType", tConnectionProvider.getType());

                // Fill in the extra metadata stored in the nodeMetadata
                extraMetadata.putAll(getMetadataFromInventoryItem(inventoryItem));
            } else if (target instanceof Connection) {
                Connection tConnection = (Connection) target;
                ConnectionProvider tConnectionProvider = connectionProviderFactory
                        .connectionProviderFromId(tConnection.getProviderId());

                extraMetadata.put("targetProviderId", tConnectionProvider.getId());
                extraMetadata.put("targetProviderDisplayName", tConnectionProvider.getDisplayName());
                extraMetadata.put("targetProviderType", tConnectionProvider.getType());

            } else if (target instanceof User) {
                User tUser = (User) target;

                extraMetadata.put("targetFuid", tUser.getFuid());
                extraMetadata.put("targetFullname", tUser.getFullname());
                extraMetadata.put("targetUsername", tUser.getUsername());

            } else if (target instanceof SobaMessage) {
                // This is actually already handled in MessageServiceImpl.  This was just put here to help keep track
                // of the different types of objects we're supporting in case we want to do more later.
            }

            // If there is no user/account set and the event is not an Account/User/SobaMessage event, quick return.
            // Otherwise, set the user and/or account based on circumstances unique to each EventId.
            if (user == null) {
                if (!(target instanceof Account) && !(target instanceof User) && !(target instanceof SobaMessage)) {
                    logger.debug("Anonymous SobaObject events are not persisted (" + target + "): " + eventId);
                    return null;
                } else {
                    switch (eventId) {
                    case CREATE:
                        if (target instanceof Account) {
                            account = (Account) target;
                            user = null;
                        } else if (target instanceof User) {
                            account = user.getAccount();
                            user = null; // Nullify because this is a system event
                        } else if (target instanceof SobaMessage) {
                            // If this is a logged in user, no need to try and figure out the user/account
                            if (user != null) {
                                break;
                            }

                            ObjectId originalTargetId = extraMetadata.get("messageEventTargetId") != null
                                    ? (ObjectId) extraMetadata.get("messageEventTargetId")
                                    : null;
                            Event previousEvent = getLastEventForTarget(originalTargetId);

                            // Try to get the user
                            ObjectId originalEventUserId = extraMetadata.get("messageEventUserId") != null
                                    ? (ObjectId) extraMetadata.get("messageEventUserId")
                                    : null;

                            if (originalEventUserId != null) {
                                try {
                                    user = userService.getUserById(originalEventUserId);
                                } catch (UserNotFoundException unfe) {
                                    if (previousEvent != null) {
                                        try {
                                            user = userService.getUserById(previousEvent.getUserId());
                                        } catch (UserNotFoundException unfe2) {
                                            // There is nothing we can do at this point.  Let's log so we can keep
                                            // track of these unrecoverable events
                                            logger.error("Unable to identify the sender of the SobaMessage "
                                                    + "having an id of " + target.getId());
                                        }
                                    }
                                }
                            }

                            // Try to get the account
                            ObjectId originalEventAccountId = extraMetadata.get("messageEventAccountId") != null
                                    ? (ObjectId) extraMetadata.get("messageEventAccountId")
                                    : null;

                            if (originalEventAccountId != null) {
                                try {
                                    account = userService.getAccount(originalEventAccountId);
                                } catch (AccountNotFoundException anfe) {
                                    if (previousEvent != null) {
                                        try {
                                            account = userService.getAccount(previousEvent.getAccountId());
                                        } catch (AccountNotFoundException anfe2) {
                                            // There is nothing we can do at this point.  Let's log so we can
                                            // keep track of these unrecoverable events
                                            logger.error("Unable to identify the sender of the SobaMessage "
                                                    + "having an id of " + target.getId());
                                        }
                                    }
                                }
                            }
                        }
                        break;

                    case CREATE_USER_REQUEST:
                        account = null; // Nullify so this doesn't get associated with the admin user's account
                        break;

                    case USER_PASSWORD_RESET_REQUEST:
                        user = (User) target;
                        account = user.getAccount();
                        break;

                    case READ:
                    case UPDATE:
                    case DELETE:
                    case DELETE_USER_INVITE_REQUEST:
                    case CREATE_USER_INVITE_REQUEST:
                    case USER_MESSAGE:
                        if (eventId == EventId.DELETE && target instanceof Account) {
                            account = (Account) target;
                        } else {
                            // Both account and user should be set so if they aren't, quick return as these must be
                            // system events
                            logger.warn("Unexpected anonymous Account/User/SobaMessage event not persisted ("
                                    + target + "): " + eventId);
                            return null;
                        }
                    }
                }
            }
        }

        // Extend the context with User information
        if (user != null) {
            extraMetadata.put("sourceAlias", user.getAlias());
            extraMetadata.put("sourceFuid", user.getFuid());
            extraMetadata.put("sourceFullname", user.getFullname());
            extraMetadata.put("sourceUsername", user.getUsername());
            extraMetadata.put("sourceVersion", user.getVersion());
        }

        // Extend the context with Account information
        if (account != null) {
            extraMetadata.put("accountFuid", account.getFuid());
            extraMetadata.put("accountName", account.getName());
            extraMetadata.put("accountVersion", account.getVersion());
        }

        // Convert JSONObject entries to BasicDBObject to avoid MongoDB serialization issues
        for (Map.Entry<String, Object> metadataEntry : extraMetadata.entrySet()) {
            String key = metadataEntry.getKey();
            Object rawValue = metadataEntry.getValue();

            if (rawValue instanceof JSONObject) {
                extraMetadata.put(key, JSON.parse(rawValue.toString()));
            }
        }

        return logAndSaveEvent(new Event.Builder().eventId(eventId)
                .accountId(account != null ? account.getId() : null).actorId(user != null ? user.getId() : null)
                .targetId(target != null ? target.getId() : null).context(extraMetadata).build());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Event> getEventsForAccount(Account account) {
        return eventDAO.forAccount(account);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Event> getAllEvents() {
        return eventDAO.allEvents();
    }

    /**
     * Helper method to get the last {@link Event} based on an object id.
     *
     * @param targetId the target id
     * @return the event or null if there is none
     */
    private Event getLastEventForTarget(ObjectId targetId) {
        return eventDAO.previousTargetEvent(targetId);
    }

    /**
     * Helper method that returns all metadata for a {@link InventoryItem}.
     *
     * @param inventoryItem the cloud inventory item to retrieve metadata for/about
     * @return the metadata
     */
    private Map<String, Object> getMetadataFromInventoryItem(InventoryItem inventoryItem) {
        // NOTE: We're not using CloudService methods here for performance reasons
        Map<String, Object> civMetadata = new HashMap<>();

        // Right now, we are only creating extended metadata for AWS EC2 instance items
        if (inventoryItem.getConnection().getProviderId().equals(ProviderIdConstants.AWS_PROVIDER_ID)
                && inventoryItem.getType().equals(Constants.COMPUTE_INSTANCE_TYPE)) {
            DBObject cMetadata = genericCollectionDAO.getById(DAODatasourceType.BUSINESS,
                    Constants.INVENTORY_ITEM_METADATA_COLLECTION_NAME, inventoryItem.getMetadataId());

            if (cMetadata == null) {
                // Fill in the metadata based on the last event for this target
                Event previousEvent = getLastEventForTarget(inventoryItem.getId());

                if (previousEvent != null) {
                    Map<String, Object> peMetadata = previousEvent.getMetadata();

                    if (peMetadata != null) {
                        civMetadata.put("targetIP", peMetadata.get("targetIP"));
                        civMetadata.put("targetOS", peMetadata.get("targetOS"));
                        civMetadata.put("targetISO3166Code", peMetadata.get("targetISO3166Code"));
                        civMetadata.put("targetRegion", peMetadata.get("targetRegion"));
                        civMetadata.put("targetZone", peMetadata.get("targetZone"));
                    }
                }
            } else {
                // Fill in the metadata from the available node metadata

                // Get the IP address
                if (cMetadata.containsField("publicAddresses")) {
                    BasicDBList publicAddresses = (BasicDBList) cMetadata.get("publicAddresses");

                    // TODO: How do we want to handle multiple IP addresses?
                    if (publicAddresses.size() > 0) {
                        civMetadata.put("targetIP", publicAddresses.get(0));
                    }
                }

                // Get location information (ISO 3166 code, region and availability zone)
                if (cMetadata.containsField("location") && cMetadata.get("location") != null) {
                    BasicDBObject location = (BasicDBObject) cMetadata.get("location");
                    boolean regionProcessed = false;
                    boolean zoneProcessed = false;

                    while (location != null) {
                        if (regionProcessed && zoneProcessed) {
                            break;
                        }

                        String locationScope = location.containsField("scope") ? location.getString("scope") : null;

                        if (locationScope != null) {
                            LocationScope scope = LocationScope.valueOf(locationScope);

                            switch (scope) {
                            case REGION:
                                civMetadata.put("targetRegion", location.get("id"));
                                regionProcessed = true;
                                break;
                            case ZONE:
                                BasicDBList iso3166Codes = (BasicDBList) location.get("iso3166Codes");

                                civMetadata.put("targetISO3166Code", iso3166Codes.get(0));
                                civMetadata.put("targetZone", location.get("id"));
                                zoneProcessed = true;
                                break;
                            }
                        }

                        location = location.containsField("parent") && location.get("parent") != null
                                ? (BasicDBObject) location.get("parent")
                                : null;
                    }
                }

                // Get OS name
                if (cMetadata.containsField("operatingSystem")) {
                    BasicDBObject operatingSystem = (BasicDBObject) cMetadata.get("operatingSystem");

                    if (operatingSystem != null) {
                        if (operatingSystem.containsField("family")) {
                            civMetadata.put("targetOS", operatingSystem.get("family"));
                        }
                    }
                }
            }
        }

        return civMetadata;
    }

    /**
     * Logs the event (DEBUG) and then persists the {@link Event}.
     *
     * @param event the event to log and persist
     * @return the event after being persisted
     */
    private Event logAndSaveEvent(Event event) {
        logger.debug(event.toString());

        eventDAO.save(event);

        return event;
    }

}