/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2015, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.messaging;

import static java.util.Arrays.asList;
import static org.jboss.as.controller.OperationContext.Stage.MODEL;
import static org.jboss.as.controller.PathAddress.pathAddress;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXTENSION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MODULE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALUE;
import static org.jboss.as.controller.operations.common.Util.createAddOperation;
import static org.jboss.as.controller.operations.common.Util.createOperation;
import static org.jboss.as.controller.operations.common.Util.createRemoveOperation;
import static org.jboss.as.messaging.CommonAttributes.ACCEPTOR;
import static org.jboss.as.messaging.CommonAttributes.ADDRESS;
import static org.jboss.as.messaging.CommonAttributes.ALLOW_FAILBACK;
import static org.jboss.as.messaging.CommonAttributes.BACKUP;
import static org.jboss.as.messaging.CommonAttributes.BACKUP_GROUP_NAME;
import static org.jboss.as.messaging.CommonAttributes.BRIDGE;
import static org.jboss.as.messaging.CommonAttributes.BROADCAST_GROUP;
import static org.jboss.as.messaging.CommonAttributes.CHECK_FOR_LIVE_SERVER;
import static org.jboss.as.messaging.CommonAttributes.CLUSTER_CONNECTION;
import static org.jboss.as.messaging.CommonAttributes.CONNECTION_FACTORY;
import static org.jboss.as.messaging.CommonAttributes.CONNECTOR;
import static org.jboss.as.messaging.CommonAttributes.CONNECTOR_REF_STRING;
import static org.jboss.as.messaging.CommonAttributes.CONNECTOR_SERVICE;
import static org.jboss.as.messaging.CommonAttributes.DISCOVERY_GROUP;
import static org.jboss.as.messaging.CommonAttributes.DISCOVERY_GROUP_NAME;
import static org.jboss.as.messaging.CommonAttributes.ENTRIES;
import static org.jboss.as.messaging.CommonAttributes.FACTORY_CLASS;
import static org.jboss.as.messaging.CommonAttributes.FAILBACK_DELAY;
import static org.jboss.as.messaging.CommonAttributes.FAILOVER_ON_SHUTDOWN;
import static org.jboss.as.messaging.CommonAttributes.GROUP_ADDRESS;
import static org.jboss.as.messaging.CommonAttributes.GROUP_PORT;
import static org.jboss.as.messaging.CommonAttributes.HORNETQ_SERVER;
import static org.jboss.as.messaging.CommonAttributes.HTTP_ACCEPTOR;
import static org.jboss.as.messaging.CommonAttributes.HTTP_CONNECTOR;
import static org.jboss.as.messaging.CommonAttributes.JGROUPS_CHANNEL;
import static org.jboss.as.messaging.CommonAttributes.JGROUPS_STACK;
import static org.jboss.as.messaging.CommonAttributes.JMS_QUEUE;
import static org.jboss.as.messaging.CommonAttributes.JMS_TOPIC;
import static org.jboss.as.messaging.CommonAttributes.LOCAL_BIND_ADDRESS;
import static org.jboss.as.messaging.CommonAttributes.LOCAL_BIND_PORT;
import static org.jboss.as.messaging.CommonAttributes.MAX_SAVED_REPLICATED_JOURNAL_SIZE;
import static org.jboss.as.messaging.CommonAttributes.POOLED_CONNECTION_FACTORY;
import static org.jboss.as.messaging.CommonAttributes.REMOTE_ACCEPTOR;
import static org.jboss.as.messaging.CommonAttributes.REMOTE_CONNECTOR;
import static org.jboss.as.messaging.CommonAttributes.SHARED_STORE;
import static org.jboss.as.messaging.logging.MessagingLogger.ROOT_LOGGER;
import static org.jboss.dmr.ModelType.BOOLEAN;
import static org.jboss.dmr.ModelType.EXPRESSION;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.ProcessType;
import org.jboss.as.controller.RunningMode;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleMapAttributeDefinition;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
import org.jboss.as.controller.StringListAttributeDefinition;
import org.jboss.as.controller.access.management.SensitiveTargetAccessConstraintDefinition;
import org.jboss.as.controller.descriptions.ResourceDescriptionResolver;
import org.jboss.as.controller.operations.MultistepUtil;
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.OperationEntry;
import org.jboss.as.controller.registry.Resource;
import org.jboss.as.messaging.logging.MessagingLogger;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.dmr.Property;

/**
 * Operation to migrate from the legacy messaging subsystem to the new messaging-activemq subsystem.
 *
 * This operation must be performed when the server is in admin-only mode.
 * Internally, the operation:
 *
 *

 *
query the description of all the messaging subsystem by invoking the :describe operation.
 *     This returns a list of :add operations for each messaging resources.

 *
:add the new org.widlfy.extension.messaging-activemq extension

 *
for each messaging resources, transform the :add operations to add the
 *     corresponding resource to the new messaging-activemq subsystem.
 *     In this step, changes to the resources model are taken into account
 *     (e.g. cluster-connection's connector-ref is named connector-name in the new messaging-activemq subsystem.)

 *
:remove the messaging subsystem

 *

 *
 * The companion :describe-migration operation will return a list of all the actual operations that would be
 * performed during the invocation of the :migrate operation.
 *
 * @author Jeff Mesnil (c) 2015 Red Hat inc.
 */

public class MigrateOperation implements OperationStepHandler {

    private static final String MESSAGING_ACTIVEMQ_EXTENSION = "org.wildfly.extension.messaging-activemq";
    private static final String MESSAGING_ACTIVEMQ_MODULE = "org.wildfly.extension.messaging-activemq";
    private static final PathAddress MESSAGING_EXTENSION = pathAddress(PathElement.pathElement(EXTENSION, "org.jboss.as.messaging"));

    private static final String NEW_ENTRY_SUFFIX = "-new";
    private static final String HORNETQ_NETTY_CONNECTOR_FACTORY = "org.hornetq.core.remoting.impl.netty.NettyConnectorFactory";
    private static final String HORNETQ_NETTY_ACCEPTOR_FACTORY = "org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory";
    private static final String ARTEMIS_NETTY_CONNECTOR_FACTORY = "org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory";
    private static final String ARTEMIS_NETTY_ACCEPTOR_FACTORY = "org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory";

    public static final String MIGRATE = "migrate";
    public static final String MIGRATION_WARNINGS = "migration-warnings";
    public static final String MIGRATION_ERROR = "migration-error";
    public static final String MIGRATION_OPERATIONS = "migration-operations";
    public static final String DESCRIBE_MIGRATION = "describe-migration";

    public static final StringListAttributeDefinition MIGRATION_WARNINGS_ATTR = new StringListAttributeDefinition.Builder(MIGRATION_WARNINGS)
            .setRequired(false)
            .build();

    public static final SimpleMapAttributeDefinition MIGRATION_ERROR_ATTR = new SimpleMapAttributeDefinition.Builder(MIGRATION_ERROR, ModelType.OBJECT, true)
            .setValueType(ModelType.OBJECT)
            .setRequired(false)
            .build();

    private static final OperationStepHandler DESCRIBE_MIGRATION_INSTANCE = new MigrateOperation(true);
    private static final OperationStepHandler MIGRATE_INSTANCE = new MigrateOperation(false);

    private static final AttributeDefinition ADD_LEGACY_ENTRIES = SimpleAttributeDefinitionBuilder.create("add-legacy-entries", BOOLEAN)
            .setDefaultValue(new ModelNode(false))
            .build();
    public static final String HA_POLICY = "ha-policy";

    private final boolean describe;

    private MigrateOperation(boolean describe) {

        this.describe = describe;
    }

    static void registerOperations(ManagementResourceRegistration registry, ResourceDescriptionResolver resourceDescriptionResolver) {
        registry.registerOperationHandler(new SimpleOperationDefinitionBuilder(MIGRATE, resourceDescriptionResolver)
                        .setParameters(ADD_LEGACY_ENTRIES)
                        .setReplyParameters(MIGRATION_WARNINGS_ATTR, MIGRATION_ERROR_ATTR)
                        .setAccessConstraints(SensitiveTargetAccessConstraintDefinition.READ_WHOLE_CONFIG)
                        .build(),
                MigrateOperation.MIGRATE_INSTANCE);
        registry.registerOperationHandler(new SimpleOperationDefinitionBuilder(DESCRIBE_MIGRATION, resourceDescriptionResolver)
                        .addParameter(ADD_LEGACY_ENTRIES)
                        .setReplyParameters(MIGRATION_WARNINGS_ATTR)
                        .setAccessConstraints(SensitiveTargetAccessConstraintDefinition.READ_WHOLE_CONFIG)
                        .setReadOnly()
                        .build(),
                MigrateOperation.DESCRIBE_MIGRATION_INSTANCE);
    }

    @Override
    public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
        if (!describe && context.getRunningMode() != RunningMode.ADMIN_ONLY) {
            throw ROOT_LOGGER.migrateOperationAllowedOnlyInAdminOnly();
        }

        boolean addLegacyEntries = ADD_LEGACY_ENTRIES.resolveModelAttribute(context, operation).asBoolean();

        final List<String> warnings = new ArrayList<>();

        // node containing the description (list of add operations) of the legacy subsystem
        final ModelNode legacyModelAddOps = new ModelNode();
        // preserve the order of insertion of the add operations for the new subsystem.
        final Map<PathAddress, ModelNode> migrationOperations = new LinkedHashMap<PathAddress, ModelNode>();

        // invoke an OSH to describe the legacy messaging subsystem
        describeLegacyMessagingResources(context, legacyModelAddOps);
        // invoke an OSH to add the messaging-activemq extension
        // FIXME: this does not work it the extension :add is added to the migrationOperations directly (https://issues.jboss.org/browse/WFCORE-323)
        addMessagingActiveMQExtension(context, migrationOperations, describe);
        context.addStep(new OperationStepHandler() {
            @Override
            public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
                // transform the legacy add operations and put them in migrationOperations
                transformResources(context, legacyModelAddOps, migrationOperations, addLegacyEntries, warnings);

                // put the /subsystem=messaging:remove operation
                removeMessagingSubsystem(migrationOperations, context.getProcessType() == ProcessType.STANDALONE_SERVER);

                PathAddress parentAddress = context.getCurrentAddress().getParent();
                fixAddressesForDomainMode(parentAddress, migrationOperations);

                if (describe) {
                    // :describe-migration operation

                    // for describe-migration operation, do nothing and return the list of operations that would
                    // be executed in the composite operation
                    final Collection<ModelNode> values = migrationOperations.values();
                    ModelNode result = new ModelNode();
                    fillWarnings(result, warnings);
                    result.get(MIGRATION_OPERATIONS).set(values);

                    context.getResult().set(result);
                } else {
                    // :migrate operation
                    // invoke an OSH on a composite operation with all the migration operations
                    final Map<PathAddress, ModelNode> migrateOpResponses = migrateSubsystems(context, migrationOperations);

                    context.completeStep(new OperationContext.ResultHandler() {
                        @Override
                        public void handleResult(OperationContext.ResultAction resultAction, OperationContext context, ModelNode operation) {
                            final ModelNode result = new ModelNode();
                            fillWarnings(result, warnings);
                            if (resultAction == OperationContext.ResultAction.ROLLBACK) {
                                for (Map.Entry<PathAddress, ModelNode> entry : migrateOpResponses.entrySet()) {
                                    if (entry.getValue().hasDefined(FAILURE_DESCRIPTION)) {
                                        //we check for failure description, as every node has 'failed', but one
                                        //the real error has a failure description
                                        //we break when we find the first one, as there will only ever be one failure
                                        //as the op stops after the first failure
                                        ModelNode desc = new ModelNode();
                                        desc.get(OP).set(migrationOperations.get(entry.getKey()));
                                        desc.get(RESULT).set(entry.getValue());
                                        result.get(MIGRATION_ERROR).set(desc);
                                        break;
                                    }
                                }
                                context.getFailureDescription().set(ROOT_LOGGER.migrationFailed());
                            }

                            context.getResult().set(result);
                        }
                    });

                }
            }
        }, MODEL);
    }

    protected void fillWarnings(ModelNode result, List<String> warnings) {
        ModelNode rw = new ModelNode().setEmptyList();
        for (String warning : warnings) {
            rw.add(warning);
        }
        result.get(MIGRATION_WARNINGS).set(rw);
    }

    /**
     * In domain mode, the subsystem are under /profile=XXX.
     * This method fixes the address by prepending the addresses (that start with /subsystem) with the current
     * operation parent so that is works both in standalone (parent = EMPTY_ADDRESS) and domain mode
     * (parent = /profile=XXX)
     */
    private void fixAddressesForDomainMode(PathAddress parentAddress, Map<PathAddress, ModelNode> migrationOperations) {
        // in standalone mode, do nothing
        if (parentAddress.size() == 0) {
            return;
        }

        // use a linked hash map to preserve operations order
        Map<PathAddress, ModelNode> fixedMigrationOperations = new LinkedHashMap<>(migrationOperations);
        migrationOperations.clear();
        for (Map.Entry<PathAddress, ModelNode> entry : fixedMigrationOperations.entrySet()) {
            PathAddress fixedAddress = parentAddress.append(entry.getKey());
            entry.getValue().get(ADDRESS).set(fixedAddress.toModelNode());
            migrationOperations.put(fixedAddress, entry.getValue());
        }
    }

    /**
     * It's possible that the extension is already present. In that case, this method does nothing.
     */
    private void addMessagingActiveMQExtension(OperationContext context, Map<PathAddress, ModelNode> migrationOperations, boolean describe) {
        Resource root = context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS, false);
        if (root.getChildrenNames(EXTENSION).contains(MESSAGING_ACTIVEMQ_EXTENSION)) {
            // extension is already added, do nothing
            return;
        }
        PathAddress extensionAddress = pathAddress(EXTENSION, MESSAGING_ACTIVEMQ_EXTENSION);
        OperationEntry addEntry = context.getRootResourceRegistration().getOperationEntry(extensionAddress, ADD);
        ModelNode addOperation = createAddOperation(extensionAddress);
        addOperation.get(MODULE).set(MESSAGING_ACTIVEMQ_MODULE);
        if (describe) {
            migrationOperations.put(extensionAddress, addOperation);
        } else {
            context.addStep(context.getResult().get(extensionAddress.toString()), addOperation, addEntry.getOperationHandler(), MODEL);
        }
    }

    private void removeMessagingSubsystem(Map<PathAddress, ModelNode> migrationOperations, boolean standalone) {
        PathAddress subsystemAddress =  pathAddress(MessagingExtension.SUBSYSTEM_PATH);
        ModelNode removeOperation = createRemoveOperation(subsystemAddress);
        migrationOperations.put(subsystemAddress, removeOperation);
        if(standalone) {
            removeOperation = createRemoveOperation(MESSAGING_EXTENSION);
            migrationOperations.put(MESSAGING_EXTENSION, removeOperation);
        }
    }

    private Map<PathAddress, ModelNode> migrateSubsystems(OperationContext context, final Map<PathAddress, ModelNode> migrationOperations) throws OperationFailedException {
        final Map<PathAddress, ModelNode> result = new LinkedHashMap<>();
        MultistepUtil.recordOperationSteps(context, migrationOperations, result);
        return result;
    }

    private ModelNode transformAddress(ModelNode legacyAddress) {
        ModelNode newAddress = new ModelNode();
        for (Property segment : legacyAddress.asPropertyList()) {
            final Property newSegment;
            switch (segment.getName()) {
                case CommonAttributes.SUBSYSTEM:
                    newSegment = new Property(SUBSYSTEM, new ModelNode("messaging-activemq"));
                    break;
                case HORNETQ_SERVER:
                    newSegment = new Property("server", segment.getValue());
                    break;
                default:
                    newSegment = segment;
            }
            newAddress.add(newSegment);
        }
        return newAddress;
    }

    private void transformResources(OperationContext context, final ModelNode legacyModelDescription, final Map<PathAddress, ModelNode> newAddOperations, boolean addLegacyEntries, List<String> warnings) throws OperationFailedException {
        for (ModelNode legacyAddOp : legacyModelDescription.get(RESULT).asList()) {
            final ModelNode newAddOp = legacyAddOp.clone();
            ModelNode legacyAddress = legacyAddOp.get(OP_ADDR);

            ModelNode newAddress = transformAddress(legacyAddress.clone());
            newAddOp.get(OP_ADDR).set(newAddress);

            PathAddress address = PathAddress.pathAddress(newAddress);

            // migrate server resource
            if (address.size() == 2 && "server".equals(address.getLastElement().getKey())) {
                migrateServer(PathAddress.pathAddress(legacyAddress), newAddOp, newAddOperations, warnings);
                continue;
            }

            if (newAddress.asList().size() > 2) {
                // element 0 is subsystem=messaging-activemq
                String parentType = address.getElement(1).getKey();
                String resourceType = address.getElement(2).getKey();
                if ("server".equals(parentType)) {
                    switch (resourceType) {
                        case BROADCAST_GROUP:
                            migrateBroadcastGroup(newAddOp, warnings);
                            break;
                        case DISCOVERY_GROUP:
                            migrateDiscoveryGroup(newAddOp, warnings);
                            break;
                        case CONNECTION_FACTORY:
                            if (addLegacyEntries) {
                                if(connectionFactoryIsUsingInVMConnectors(context, legacyAddOp)) {
                                    warnings.add(ROOT_LOGGER.couldNotCreateLegacyConnectionFactoryUsingInVMConnector(address));
                                } else {
                                    PathAddress legacyConnectionFactoryAddress = address.getParent().append("legacy-connection-factory", address.getLastElement().getValue());
                                    final ModelNode addLegacyConnectionFactoryOp = legacyAddOp.clone();
                                    addLegacyConnectionFactoryOp.get(OP_ADDR).set(legacyConnectionFactoryAddress.toModelNode());
                                    migrateConnectionFactory(addLegacyConnectionFactoryOp, "");
                                    newAddOperations.put(legacyConnectionFactoryAddress, addLegacyConnectionFactoryOp);
                                }
                            }
                            migrateConnectionFactory(newAddOp, addLegacyEntries ? NEW_ENTRY_SUFFIX : "");
                            break;
                        case POOLED_CONNECTION_FACTORY:
                            migratePooledConnectionFactory(newAddOp);
                            break;
                        case CLUSTER_CONNECTION:
                            migrateClusterConnection(newAddOp, warnings);
                            break;
                        case BRIDGE:
                            migrateBridge(newAddOp);
                            break;
                        case JMS_QUEUE:
                        case JMS_TOPIC:
                            if (addLegacyEntries) {
                                addLegacyEntries(newAddOp);
                            }
                            break;
                        case ACCEPTOR:
                        case CONNECTOR:
                            migrateGenericTransport(newAddOp);
                            // no break on purpose, the acceptor and connector must also migrate their params.
                        case HTTP_ACCEPTOR:
                        case REMOTE_ACCEPTOR:
                        case HTTP_CONNECTOR:
                        case REMOTE_CONNECTOR:
                        case CONNECTOR_SERVICE:
                            if (address.size() == 4) {
                                // if there are any param resource underneath connectors, acceptors, and connector-services
                                // add them directly to their parent add operation in their params attribute
                                String name = address.getLastElement().getValue();
                                ModelNode value = newAddOp.get(VALUE);
                                ModelNode parentAddOp = newAddOperations.get(address.getParent());
                                if (name.equals("http-upgrade-endpoint") && address.getParent().getLastElement().getKey().equals("http-connector")) {
                                    parentAddOp.get("endpoint").set(value);
                                } else {
                                    if (parameterIsAllowed(name, resourceType)) {
                                        parentAddOp.get("params").add(new Property(name, value));
                                    } else {
                                        warnings.add(ROOT_LOGGER.couldNotMigrateUnsupportedAttribute(name, address.getParent()));
                                    }
                                }
                                continue;
                            }
                            break;
                    }
                }
            }

            newAddOperations.put(address, newAddOp);
        }
    }

    /**
     * Check if the name of the parameter is allowed for the given resourceType.
     */
    private boolean parameterIsAllowed(String name, String resourceType) {
        switch (resourceType) {
            case REMOTE_ACCEPTOR:
            case HTTP_ACCEPTOR:
            case REMOTE_CONNECTOR:
            case HTTP_CONNECTOR:
                // WFLY-5667 - for now remove only use-nio. Revisit this code when Artemis offers an API
                // to know which parameters are ignored.
                if ("use-nio".equals(name)) {
                    return false;
                } else {
                    return true;
                }
            default:
                // accept any parameter for other resources.
                return true;
        }
    }

    private boolean connectionFactoryIsUsingInVMConnectors(OperationContext context, ModelNode connectionFactoryAddOp) {
        ModelNode connector = connectionFactoryAddOp.get(CONNECTOR);
        if (connector.isDefined()) {

            PathAddress connectionFactoryAddress = pathAddress(connectionFactoryAddOp.get(OP_ADDR));
            PathElement relativeLegacyServerAddress = connectionFactoryAddress.getParent().getLastElement();
            // read the server resource related to the context current address (which is the messaging subsystem address).
            Resource serverResource = context.readResource(pathAddress(relativeLegacyServerAddress), false);
            Set<String> definedInVMConnectors = serverResource.getChildrenNames("in-vm-connector");

            // legacy connector is a property list where the name is the connector and the value is undefined
            List<Property> connectorProps = connector.asPropertyList();
            for (Property connectorProp : connectorProps) {
                String connectorName = connectorProp.getName();
                if (definedInVMConnectors.contains(connectorName)) {
                    return true;
                }
            }
        }
        return false;
    }

    private void migrateDiscoveryGroup(ModelNode newAddOp, List<String> warnings) {
        // These attributes are not present in the messaging-activemq subsystem.
        // Instead a socket-binding must be used to configure the broadcast-group.
        for (String property : asList(LOCAL_BIND_ADDRESS.getName(), GROUP_ADDRESS.getName(), GROUP_PORT.getName())) {
            if (newAddOp.has(property)) {
                newAddOp.remove(property);
                warnings.add(ROOT_LOGGER.couldNotMigrateDiscoveryGroupAttribute(property, pathAddress(newAddOp.get(OP_ADDR))));
            }
        }
        // These attributes no longer accept expressions in the messaging-activemq subsystem.
        removePropertiesWithExpression(newAddOp, warnings, JGROUPS_CHANNEL.getName(), JGROUPS_STACK.getName());
    }

    private void migrateBroadcastGroup(ModelNode newAddOp, List<String> warnings) {
        // These attributes are not present in the messaging-activemq subsystem.
        // Instead a socket-binding must be used to configure the broadcast-group.
        final Collection<String> unmigratedProperties = asList(LOCAL_BIND_ADDRESS.getName(),
                LOCAL_BIND_PORT.getName(),
                GROUP_ADDRESS.getName(),
                GROUP_PORT.getName());
        for (Property property : newAddOp.asPropertyList()) {
            if (unmigratedProperties.contains(property.getName())) {
                warnings.add(ROOT_LOGGER.couldNotMigrateBroadcastGroupAttribute(property.getName(), pathAddress(newAddOp.get(OP_ADDR))));
            }
        }
        // These attributes no longer accept expressions in the messaging-activemq subsystem.
        removePropertiesWithExpression(newAddOp, warnings, JGROUPS_CHANNEL.getName(), JGROUPS_STACK.getName());
    }

    private void removePropertiesWithExpression(ModelNode newAddOp, List<String> warnings, String... properties) {
        for (String property : properties) {
            if (newAddOp.hasDefined(property) && newAddOp.get(property).getType() == EXPRESSION) {
                newAddOp.remove(property);
                warnings.add(ROOT_LOGGER.couldNotMigrateResourceAttributeWithExpression(property, pathAddress(newAddOp.get(OP_ADDR))));
            }
        }
    }

    private void addLegacyEntries(ModelNode newAddOp) {
        newAddOp.get("legacy-entries").set(newAddOp.get(ENTRIES));
        newAddOp.remove(ENTRIES);
        for (ModelNode legacyEntry : newAddOp.get("legacy-entries").asList()) {
            String newEntry = legacyEntry.asString() + NEW_ENTRY_SUFFIX;
            newAddOp.get(ENTRIES).add(newEntry);
        }
    }

    private void describeLegacyMessagingResources(OperationContext context, ModelNode legacyModelDescription) {
        ModelNode describeLegacySubsystem = createOperation(GenericSubsystemDescribeHandler.DEFINITION, context.getCurrentAddress());
        context.addStep(legacyModelDescription, describeLegacySubsystem, GenericSubsystemDescribeHandler.INSTANCE, MODEL, true);
    }

    private void migrateConnectionFactory(ModelNode addOperation, String entrySuffix) {
        migrateConnectorAttribute(addOperation);
        migrateDiscoveryGroupNameAttribute(addOperation);

        if (!entrySuffix.isEmpty()) {
            List<ModelNode> entries = addOperation.get(ENTRIES).asList();
            addOperation.remove(ENTRIES);
            for (ModelNode entry : entries) {
                String newEntry = entry.asString() + entrySuffix;
                addOperation.get(ENTRIES).add(newEntry);
            }
        }
    }

    private void migratePooledConnectionFactory(ModelNode addOperation) {
        migrateConnectorAttribute(addOperation);
        migrateDiscoveryGroupNameAttribute(addOperation);
        // WFLY-8928 - allow local transacted JMS session
        addOperation.get("allow-local-transactions").set(new ModelNode(true));
    }

    private void migrateClusterConnection(ModelNode addOperation, List<String> warnings) {
        // connector-ref attribute has been renamed to connector-name
        addOperation.get("connector-name").set(addOperation.get(CONNECTOR_REF_STRING));
        addOperation.remove(CONNECTOR_REF_STRING);

        ModelNode forwardWhenNoConsumers = addOperation.get(ClusterConnectionDefinition.FORWARD_WHEN_NO_CONSUMERS.getName());
        if (forwardWhenNoConsumers.getType() == EXPRESSION) {
            warnings.add(ROOT_LOGGER.couldNotMigrateResourceAttributeWithExpression(ClusterConnectionDefinition.FORWARD_WHEN_NO_CONSUMERS.getName(), pathAddress(addOperation.get(OP_ADDR))));
        } else {
            boolean value = forwardWhenNoConsumers.asBoolean(ClusterConnectionDefinition.FORWARD_WHEN_NO_CONSUMERS.getDefaultValue().asBoolean());
            String messageLoadBalancingType = value ? "STRICT" : "ON_DEMAND";
            addOperation.get("message-load-balancing-type").set(messageLoadBalancingType);
        }
        addOperation.remove(ClusterConnectionDefinition.FORWARD_WHEN_NO_CONSUMERS.getName());

        migrateDiscoveryGroupNameAttribute(addOperation);
    }

    private void migrateConnectorAttribute(ModelNode addOperation) {
        ModelNode connector = addOperation.get(CONNECTOR);
        if (connector.isDefined()) {
            // legacy connector is a property list where the name is the connector and the value is undefined
            List<Property> connectorProps = connector.asPropertyList();
            for (Property connectorProp : connectorProps) {
                addOperation.get("connectors").add(connectorProp.getName());
            }
            addOperation.remove(CONNECTOR);
        }
    }
    private void migrateDiscoveryGroupNameAttribute(ModelNode addOperation) {
        ModelNode discoveryGroup = addOperation.get(DISCOVERY_GROUP_NAME);
        if (discoveryGroup.isDefined()) {
            // discovery-group-name attribute has been renamed to discovery-group
            addOperation.get("discovery-group").set(discoveryGroup);
            addOperation.remove(DISCOVERY_GROUP_NAME);
        }
    }

    private void migrateBridge(ModelNode addOperation) {
        migrateDiscoveryGroupNameAttribute(addOperation);
    }

    /**
     * For generic acceptor and connectors, migrate their factory-class attribute
     * if they are using the default Netty ones.
     */
    private void migrateGenericTransport(ModelNode addOperation) {
        String factoryClass = addOperation.get(FACTORY_CLASS.getName()).asString();
        final String newFactoryClass;
        switch (factoryClass) {
            case HORNETQ_NETTY_ACCEPTOR_FACTORY:
                newFactoryClass = ARTEMIS_NETTY_ACCEPTOR_FACTORY;
                break;
            case HORNETQ_NETTY_CONNECTOR_FACTORY:
                newFactoryClass = ARTEMIS_NETTY_CONNECTOR_FACTORY;
                break;
            default:
                newFactoryClass = factoryClass;
        }
        addOperation.get(FACTORY_CLASS.getName()).set(newFactoryClass);
    }

    private void migrateServer(PathAddress legacyAddress, ModelNode addOperation, Map<PathAddress, ModelNode> newAddOperations, List<String> warnings) {
        discardInterceptors(addOperation, CommonAttributes.REMOTING_INTERCEPTORS.getName(), warnings);
        discardInterceptors(addOperation, CommonAttributes.REMOTING_INCOMING_INTERCEPTORS.getName(), warnings);
        discardInterceptors(addOperation, CommonAttributes.REMOTING_OUTGOING_INTERCEPTORS.getName(), warnings);

        // add the server :add operation before eventually adding a ha-policy child :add operation in migrateHAPolicy.
        newAddOperations.put(pathAddress(addOperation.get(OP_ADDR)), addOperation);

        migrateHAPolicy(legacyAddress, addOperation, newAddOperations, warnings);
    }

    private void migrateHAPolicy(PathAddress legacyAddress, ModelNode serverAddOperation, Map<PathAddress, ModelNode> newAddOperations, List<String> warnings) {
        PathAddress serverAddress = PathAddress.pathAddress(serverAddOperation.get(OP_ADDR));

        ModelNode sharedStoreAttr = serverAddOperation.get(SHARED_STORE.getName());
        ModelNode backupAttr = serverAddOperation.get(BACKUP.getName());

        if (sharedStoreAttr.getType() == EXPRESSION || backupAttr.getType() == EXPRESSION) {
            warnings.add(ROOT_LOGGER.couldNotMigrateHA(legacyAddress));
            return;
        }

        boolean sharedStore = sharedStoreAttr.isDefined() ? sharedStoreAttr.asBoolean() : SHARED_STORE.getDefaultValue().asBoolean();
        boolean backup = backupAttr.isDefined() ? backupAttr.asBoolean() : BACKUP.getDefaultValue().asBoolean();

        ModelNode haPolicyAddOperation = createAddOperation();
        final PathAddress haPolicyAddress;

        if (sharedStore) {
            if (backup) {
                haPolicyAddress = serverAddress.append(HA_POLICY, "shared-store-slave");
                setAndDiscard(haPolicyAddOperation, serverAddOperation, ALLOW_FAILBACK, "allow-failback");
                setAndDiscard(haPolicyAddOperation, serverAddOperation, FAILOVER_ON_SHUTDOWN, "failover-on-server-shutdown");
                discardFailbackDelay(serverAddOperation, warnings);
            } else {
                haPolicyAddress = serverAddress.append(HA_POLICY, "shared-store-master");
                setAndDiscard(haPolicyAddOperation, serverAddOperation, FAILOVER_ON_SHUTDOWN, "failover-on-server-shutdown");
                discardFailbackDelay(serverAddOperation, warnings);
            }
        } else {
            if (backup) {
                haPolicyAddress = serverAddress.append(HA_POLICY, "replication-slave");
                setAndDiscard(haPolicyAddOperation, serverAddOperation, ALLOW_FAILBACK, "allow-failback");
                setAndDiscard(haPolicyAddOperation, serverAddOperation, MAX_SAVED_REPLICATED_JOURNAL_SIZE, "max-saved-replicated-journal-size");
                setAndDiscard(haPolicyAddOperation, serverAddOperation, BACKUP_GROUP_NAME, "group-name");
                discardFailbackDelay(serverAddOperation, warnings);
            } else {
                haPolicyAddress = serverAddress.append(HA_POLICY, "replication-master");
                setAndDiscard(haPolicyAddOperation, serverAddOperation, CHECK_FOR_LIVE_SERVER, "check-for-live-server");
                setAndDiscard(haPolicyAddOperation, serverAddOperation, BACKUP_GROUP_NAME, "group-name");
            }
        }
        haPolicyAddOperation.get(OP_ADDR).set(haPolicyAddress.toModelNode());

        newAddOperations.put(haPolicyAddress, haPolicyAddOperation);
    }

    private void discardInterceptors(ModelNode addOperation, String legacyInterceptorsAttributeName, List<String> warnings) {
        if (!addOperation.get(legacyInterceptorsAttributeName).isDefined()) {
            return;
        }
        warnings.add(ROOT_LOGGER.couldNotMigrateInterceptors(legacyInterceptorsAttributeName));
        addOperation.remove(legacyInterceptorsAttributeName);
    }

    /**
     * Discard from a node and set it to a new node if the attribute is defined. Use the {@code newAttributeName} as the messaging-activemq may
     * named differently its corresponding attribute.
     */
    private void setAndDiscard(ModelNode setNode, ModelNode discardNode, AttributeDefinition legacyAttributeDefinition, String newAttributeName) {
        ModelNode attribute = discardNode.get(legacyAttributeDefinition.getName());
        if (attribute.isDefined()) {
            setNode.get(newAttributeName).set(attribute);
            discardNode.remove(legacyAttributeDefinition.getName());
        }
    }

    private void discardUnsupportedAttribute(ModelNode newAddOp, AttributeDefinition legacyAttributeDefinition, List<String> warnings) {
        if (newAddOp.hasDefined(legacyAttributeDefinition.getName())) {
            newAddOp.remove(legacyAttributeDefinition.getName());
            warnings.add(MessagingLogger.ROOT_LOGGER.couldNotMigrateUnsupportedAttribute(legacyAttributeDefinition.getName(), pathAddress(newAddOp.get(OP_ADDR))));
        }
    }

    private void discardFailbackDelay(ModelNode newAddOp, List<String> warnings) {
        if (newAddOp.hasDefined(FAILBACK_DELAY.getName())) {
            newAddOp.remove(FAILBACK_DELAY.getName());
            warnings.add(MessagingLogger.ROOT_LOGGER.couldNotMigrateFailbackDelayAttribute(pathAddress(newAddOp.get(OP_ADDR))));
        }
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.ee.subsystem;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;

import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.persistence.SubsystemMarshallingContext;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.staxmapper.XMLElementWriter;
import org.jboss.staxmapper.XMLExtendedStreamWriter;

/**
 */
class EESubsystemXmlPersister implements XMLStreamConstants, XMLElementWriter<SubsystemMarshallingContext> {

    public static final EESubsystemXmlPersister INSTANCE = new EESubsystemXmlPersister();

    private EESubsystemXmlPersister() {

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeContent(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
        context.startSubsystemElement(Namespace.CURRENT.getUriString(), false);

        ModelNode eeSubSystem = context.getModelNode();
        GlobalModulesDefinition.INSTANCE.marshallAsElement(eeSubSystem, writer);
        EeSubsystemRootResource.EAR_SUBDEPLOYMENTS_ISOLATED.marshallAsElement(eeSubSystem, writer);
        EeSubsystemRootResource.SPEC_DESCRIPTOR_PROPERTY_REPLACEMENT.marshallAsElement(eeSubSystem, writer);
        EeSubsystemRootResource.JBOSS_DESCRIPTOR_PROPERTY_REPLACEMENT.marshallAsElement(eeSubSystem, writer);
        EeSubsystemRootResource.ANNOTATION_PROPERTY_REPLACEMENT.marshallAsElement(eeSubSystem, writer);
        writeConcurrentElement(writer,eeSubSystem);
        writeDefaultBindingsElement(writer,eeSubSystem);
        writer.writeEndElement();
    }

    private void writeConcurrentElement(XMLExtendedStreamWriter writer, ModelNode eeSubSystem) throws XMLStreamException {
        boolean started = false;
        if (eeSubSystem.hasDefined(EESubsystemModel.CONTEXT_SERVICE)) {
            writer.writeStartElement(Element.CONCURRENT.getLocalName());
            started = true;
            writeContextServices(writer, eeSubSystem.get(EESubsystemModel.CONTEXT_SERVICE));
        }
        if (eeSubSystem.hasDefined(EESubsystemModel.MANAGED_THREAD_FACTORY)) {
            if(!started) {
                writer.writeStartElement(Element.CONCURRENT.getLocalName());
                started = true;
            }
            writeManagedThreadFactories(writer, eeSubSystem.get(EESubsystemModel.MANAGED_THREAD_FACTORY));
        }
        if (eeSubSystem.hasDefined(EESubsystemModel.MANAGED_EXECUTOR_SERVICE)) {
            if(!started) {
                writer.writeStartElement(Element.CONCURRENT.getLocalName());
                started = true;
            }
            writeManagedExecutorServices(writer, eeSubSystem.get(EESubsystemModel.MANAGED_EXECUTOR_SERVICE));
        }
        if (eeSubSystem.hasDefined(EESubsystemModel.MANAGED_SCHEDULED_EXECUTOR_SERVICE)) {
            if(!started) {
                writer.writeStartElement(Element.CONCURRENT.getLocalName());
                started = true;
            }
            writeManagedScheduledExecutorServices(writer, eeSubSystem.get(EESubsystemModel.MANAGED_SCHEDULED_EXECUTOR_SERVICE));
        }
        if(started) {
            writer.writeEndElement();
        }
    }

    private void writeContextServices(final XMLExtendedStreamWriter writer, final ModelNode subModel) throws XMLStreamException {
        writer.writeStartElement(Element.CONTEXT_SERVICES.getLocalName());
        for (Property property : subModel.asPropertyList()) {
            writer.writeStartElement(Element.CONTEXT_SERVICE.getLocalName());
            writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
            for(SimpleAttributeDefinition ad : ContextServiceResourceDefinition.ATTRIBUTES) {
                ad.marshallAsAttribute(property.getValue(), writer);
            }
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }

    private void writeManagedThreadFactories(final XMLExtendedStreamWriter writer, final ModelNode subModel) throws XMLStreamException {
        writer.writeStartElement(Element.MANAGED_THREAD_FACTORIES.getLocalName());
        for (Property property : subModel.asPropertyList()) {
            writer.writeStartElement(Element.MANAGED_THREAD_FACTORY.getLocalName());
            writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
            for(SimpleAttributeDefinition ad : ManagedThreadFactoryResourceDefinition.ATTRIBUTES) {
                ad.marshallAsAttribute(property.getValue(), writer);
            }
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }

    private void writeManagedExecutorServices(final XMLExtendedStreamWriter writer, final ModelNode subModel) throws XMLStreamException {
        writer.writeStartElement(Element.MANAGED_EXECUTOR_SERVICES.getLocalName());
        for (Property property : subModel.asPropertyList()) {
            writer.writeStartElement(Element.MANAGED_EXECUTOR_SERVICE.getLocalName());
            writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
            for(SimpleAttributeDefinition ad : ManagedExecutorServiceResourceDefinition.ATTRIBUTES) {
                ad.marshallAsAttribute(property.getValue(), writer);
            }
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }

    private void writeManagedScheduledExecutorServices(final XMLExtendedStreamWriter writer, final ModelNode subModel) throws XMLStreamException {
        writer.writeStartElement(Element.MANAGED_SCHEDULED_EXECUTOR_SERVICES.getLocalName());
        for (Property property : subModel.asPropertyList()) {
            writer.writeStartElement(Element.MANAGED_SCHEDULED_EXECUTOR_SERVICE.getLocalName());
            writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
            for(SimpleAttributeDefinition ad : ManagedScheduledExecutorServiceResourceDefinition.ATTRIBUTES) {
                ad.marshallAsAttribute(property.getValue(), writer);
            }
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }

    private void writeDefaultBindingsElement(XMLExtendedStreamWriter writer, ModelNode eeSubSystem) throws XMLStreamException {
        if (eeSubSystem.hasDefined(EESubsystemModel.SERVICE) && eeSubSystem.get(EESubsystemModel.SERVICE).hasDefined(EESubsystemModel.DEFAULT_BINDINGS)) {
            ModelNode defaultBindingsNode = eeSubSystem.get(EESubsystemModel.SERVICE, EESubsystemModel.DEFAULT_BINDINGS);
            writer.writeStartElement(Element.DEFAULT_BINDINGS.getLocalName());
            for(SimpleAttributeDefinition ad : DefaultBindingsResourceDefinition.ATTRIBUTES) {
                ad.marshallAsAttribute(defaultBindingsNode,writer);
            }
            writer.writeEndElement();
        }
    }

}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.ejb3.deployment.processors.dd;

import org.jboss.as.ee.component.Attachments;
import org.jboss.as.ee.component.ComponentDescription;
import org.jboss.as.ee.component.DeploymentDescriptorEnvironment;
import org.jboss.as.ee.component.EEModuleDescription;
import org.jboss.as.ejb3.component.session.SessionBeanComponentDescription;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.metadata.ejb.spec.BusinessLocalsMetaData;
import org.jboss.metadata.ejb.spec.BusinessRemotesMetaData;
import org.jboss.metadata.ejb.spec.SessionBean31MetaData;
import org.jboss.metadata.ejb.spec.SessionBeanMetaData;

/**
 * @author Jaikiran Pai
 */
public class SessionBeanXmlDescriptorProcessor extends AbstractEjbXmlDescriptorProcessor<SessionBeanMetaData> {

    private final boolean appclient;

    public SessionBeanXmlDescriptorProcessor(final boolean appclient) {
        this.appclient = appclient;
    }

    @Override
    protected Class<SessionBeanMetaData> getMetaDataType() {
        return SessionBeanMetaData.class;
    }

    /**
     * Processes the passed {@link org.jboss.metadata.ejb.spec.SessionBeanMetaData} and creates appropriate {@link org.jboss.as.ejb3.component.session.SessionBeanComponentDescription} out of it.
     * The {@link org.jboss.as.ejb3.component.session.SessionBeanComponentDescription} is then added to the {@link org.jboss.as.ee.component.EEModuleDescription module description} available
     * in the deployment unit of the passed {@link DeploymentPhaseContext phaseContext}
     *
     * @param sessionBean  The session bean metadata
     * @param phaseContext
     * @throws DeploymentUnitProcessingException
     *
     */
    @Override
    protected void processBeanMetaData(final SessionBeanMetaData sessionBean, final DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        // get the module description
        final EEModuleDescription moduleDescription = deploymentUnit.getAttachment(org.jboss.as.ee.component.Attachments.EE_MODULE_DESCRIPTION);

        final String beanName = sessionBean.getName();

        ComponentDescription bean = moduleDescription.getComponentByName(beanName);
        if (appclient) {
            if (bean == null) {
                for (final ComponentDescription component : deploymentUnit.getAttachmentList(Attachments.ADDITIONAL_RESOLVABLE_COMPONENTS)) {
                    if (component.getComponentName().equals(beanName)) {
                        bean = component;
                        break;
                    }
                }
            }
        }
        if (!(bean instanceof SessionBeanComponentDescription)) {
            //TODO: this is a hack to deal with descriptor merging
            //if this is a GenericBeanMetadata it may actually represent an MDB
            return;
        }

        SessionBeanComponentDescription sessionBeanDescription = (SessionBeanComponentDescription) bean;

        sessionBeanDescription.setDeploymentDescriptorEnvironment(new DeploymentDescriptorEnvironment("java:comp/env/", sessionBean));

        // mapped-name
        sessionBeanDescription.setMappedName(sessionBean.getMappedName());
        // local business interface views
        final BusinessLocalsMetaData businessLocals = sessionBean.getBusinessLocals();
        if (businessLocals != null && !businessLocals.isEmpty()) {
            sessionBeanDescription.addLocalBusinessInterfaceViews(businessLocals);
        }

        final String local = sessionBean.getLocal();
        if (local != null) {
            sessionBeanDescription.addEjbLocalObjectView(local);
        }

        final String remote = sessionBean.getRemote();
        if (remote != null) {
            sessionBeanDescription.addEjbObjectView(remote);
        }

        // remote business interface views
        final BusinessRemotesMetaData businessRemotes = sessionBean.getBusinessRemotes();
        if (businessRemotes != null && !businessRemotes.isEmpty()) {
            sessionBeanDescription.addRemoteBusinessInterfaceViews(businessRemotes);
        }

        // process EJB3.1 specific session bean description
        if (sessionBean instanceof SessionBean31MetaData) {
            this.processSessionBean31((SessionBean31MetaData) sessionBean, sessionBeanDescription);
        }
    }

    private void processSessionBean31(final SessionBean31MetaData sessionBean31MetaData, final SessionBeanComponentDescription sessionBeanComponentDescription) {
        // no-interface view
        if (sessionBean31MetaData.isNoInterfaceBean()) {
            sessionBeanComponentDescription.addNoInterfaceView();
        }
    }

}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.jpa.processor;

import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jipijapa.plugin.spi.Platform;

/**
 * begin installation of persistence unit service and persistence providers in deployment
 *
 * @author Scott Marlow
 */
public class PersistenceBeginInstallProcessor implements DeploymentUnitProcessor {

    private final Platform platform;

    public PersistenceBeginInstallProcessor(Platform platform) {
        this.platform = platform;
    }
    /**
     * {@inheritDoc}
     */
    public void deploy(final DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {

        // deploy any persistence providers found in deployment
        PersistenceProviderHandler.deploy(phaseContext, platform);

        // start each PU service (except the PUs with property Configuration.JPA_CONTAINER_CLASS_TRANSFORMER = false)
        PersistenceUnitServiceHandler.deploy(phaseContext, true, platform);
    }

    /**
     * {@inheritDoc}
     */
    public void undeploy(final DeploymentUnit deploymentUnit) {
        PersistenceProviderHandler.undeploy(deploymentUnit);
    }

}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.ejb3.component.interceptors;

import java.util.concurrent.atomic.AtomicInteger;

import org.wildfly.common.Assert;

/**
 * An invocation cancellation flag.
 *
 * @author David M. Lloyd
 */
public final class CancellationFlag {

    private final AtomicInteger stateRef = new AtomicInteger(0);

    private static final int ST_WAITING             = 0;
    private static final int ST_STARTED             = 1;
    private static final int ST_STARTED_FLAG_SET    = 2;
    private static final int ST_CANCELLED           = 3;
    private static final int ST_CANCELLED_FLAG_SET  = 4;

    /**
     * Construct a new instance.
     */
    public CancellationFlag() {
    }

    /**
     * Attempt to cancel the corresponding invocation.
     *
     * @param setFlag {@code true} to set the EJB context cancellation flag (or equivalent), {@code false} otherwise
     * @return {@code true} if the invocation was definitely cancelled, or {@code false} if it was not cancelled or it could not be determined if it was cancelled
     */
    public boolean cancel(boolean setFlag) {
        final AtomicInteger stateRef = this.stateRef;
        int oldVal, newVal;
        do {
            oldVal = stateRef.get();
            if (oldVal == ST_WAITING) {
                newVal = ST_CANCELLED;
            } else if (oldVal == ST_CANCELLED) {
                if (! setFlag) {
                    return true;
                }
                newVal = ST_CANCELLED_FLAG_SET;
            } else if (oldVal == ST_CANCELLED_FLAG_SET) {
                // do nothing
                return true;
            } else if (oldVal == ST_STARTED) {
                if (! setFlag) {
                    return false;
                }
                newVal = ST_STARTED_FLAG_SET;
            } else {
                assert oldVal == ST_STARTED_FLAG_SET;
                return false;
            }
        } while (! stateRef.compareAndSet(oldVal, newVal));
        return newVal == ST_CANCELLED || newVal == ST_CANCELLED_FLAG_SET;
    }

    /**
     * Attempt to determine whether the invocation should proceed or whether it should be cancelled.  This method should only
     * be called once per flag instance.
     *
     * @return {@code true} if the invocation should proceed, or {@code false} if it was cancelled
     */
    public boolean runIfNotCancelled() {
        final AtomicInteger stateRef = this.stateRef;
        int oldVal;
        do {
            oldVal = stateRef.get();
            if (oldVal == ST_CANCELLED || oldVal == ST_CANCELLED_FLAG_SET) {
                return false;
            } else if (oldVal != ST_WAITING) {
                throw Assert.unreachableCode();
            }
        } while (! stateRef.compareAndSet(oldVal, ST_STARTED));
        return true;
    }

    /**
     * Determine if the cancel flag was set.
     *
     * @return {@code true} if the flag was set, {@code false} otherwise
     */
    public boolean isCancelFlagSet() {
        final int state = stateRef.get();
        return state == ST_STARTED_FLAG_SET || state == ST_CANCELLED_FLAG_SET;
    }

    /**
     * Determine if the request was cancelled before it could complete.
     *
     * @return {@code true} if the request was cancelled, {@code false} otherwise
     */
    public boolean isCancelled() {
        final int state = stateRef.get();
        return state == ST_CANCELLED || state == ST_CANCELLED_FLAG_SET;
    }
}


package com.orientechnologies.orient.core.metadata.schema;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.collate.OCollate;
import com.orientechnologies.orient.core.collate.ODefaultCollate;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.index.OIndexDefinition;
import com.orientechnologies.orient.core.index.OIndexManager;
import com.orientechnologies.orient.core.index.OIndexMetadata;
import com.orientechnologies.orient.core.metadata.security.ORole;
import com.orientechnologies.orient.core.metadata.security.ORule;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.OSQLEngine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

/**
 * Created by tglman on 14/06/17.
 */
public class OPropertyEmbedded extends OPropertyImpl {
  protected OPropertyEmbedded(OClassImpl owner) {
    super(owner);
  }

  protected OPropertyEmbedded(OClassImpl owner, ODocument document) {
    super(owner, document);
  }

  protected OPropertyEmbedded(OClassImpl oClassImpl, OGlobalProperty global) {
    super(oClassImpl, global);
  }

  public OPropertyImpl setType(final OType type) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    final ODatabaseDocumentInternal database = getDatabase();
    acquireSchemaWriteLock();
    try {
      setTypeInternal(type);
    } finally {
      releaseSchemaWriteLock();
    }
    owner.fireDatabaseMigration(database, globalRef.getName(), globalRef.getType());

    return this;
  }

  /**
   * Change the type. It checks for compatibility between the change of type.
   *
   * @param iType
   */
  protected void setTypeInternal(final OType iType) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      if (iType == globalRef.getType())
        // NO CHANGES
        return;

      if (!iType.getCastable().contains(globalRef.getType()))
        throw new IllegalArgumentException("Cannot change property type from " + globalRef.getType() + " to " + iType);

      this.globalRef = owner.owner.findOrCreateGlobalProperty(this.globalRef.getName(), iType);
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OProperty setName(final String name) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setNameInternal(name);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setNameInternal(final String name) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    String oldName = this.globalRef.getName();
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      owner.renameProperty(oldName, name);
      this.globalRef = owner.owner.findOrCreateGlobalProperty(name, this.globalRef.getType());
    } finally {
      releaseSchemaWriteLock();
    }
    owner.firePropertyNameMigration(getDatabase(), oldName, name, this.globalRef.getType());
  }

  @Override
  public OPropertyImpl setDescription(final String iDescription) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setDescriptionInternal(iDescription);
    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  protected void setDescriptionInternal(final String iDescription) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.description = iDescription;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OProperty setCollate(String collate) {
    if (collate == null)
      collate = ODefaultCollate.NAME;

    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setCollateInternal(collate);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected OProperty setCollateInternal(String iCollate) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      final OCollate oldCollate = this.collate;

      if (iCollate == null)
        iCollate = ODefaultCollate.NAME;

      collate = OSQLEngine.getCollate(iCollate);

      if ((this.collate != null && !this.collate.equals(oldCollate)) || (this.collate == null && oldCollate != null)) {
        final Set<OIndex<?>> indexes = owner.getClassIndexes();
        final List<OIndex<?>> indexesToRecreate = new ArrayList<OIndex<?>>();

        for (OIndex<?> index : indexes) {
          OIndexDefinition definition = index.getDefinition();

          final List<String> fields = definition.getFields();
          if (fields.contains(getName()))
            indexesToRecreate.add(index);
        }

        if (!indexesToRecreate.isEmpty()) {
          OLogManager.instance().info(this, "Collate value was changed, following indexes will be rebuilt %s", indexesToRecreate);

          final ODatabaseDocument database = getDatabase();
          final OIndexManager indexManager = database.getMetadata().getIndexManager();

          for (OIndex<?> indexToRecreate : indexesToRecreate) {
            final OIndexMetadata indexMetadata = indexToRecreate.getInternal().loadMetadata(indexToRecreate.getConfiguration());

            final ODocument metadata = indexToRecreate.getMetadata();
            final List<String> fields = indexMetadata.getIndexDefinition().getFields();
            final String[] fieldsToIndex = fields.toArray(new String[fields.size()]);

            indexManager.dropIndex(indexMetadata.getName());
            owner.createIndex(indexMetadata.getName(), indexMetadata.getType(), null, metadata, indexMetadata.getAlgorithm(),
                fieldsToIndex);
          }
        }
      }
    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  public void clearCustom() {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      clearCustomInternal();
    } finally {
      releaseSchemaWriteLock();
    }
  }

  protected void clearCustomInternal() {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      customFields = null;
    } finally {
      releaseSchemaWriteLock();
    }

  }

  public OPropertyImpl setCustom(final String name, final String value) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setCustomInternal(name, value);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setCustomInternal(final String iName, final String iValue) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      if (customFields == null)
        customFields = new HashMap<String, String>();
      if (iValue == null || "null".equalsIgnoreCase(iValue))
        customFields.remove(iName);
      else
        customFields.put(iName, iValue);
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OPropertyImpl setRegexp(final String regexp) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setRegexpInternal(regexp);
    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  protected void setRegexpInternal(final String regexp) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      this.regexp = regexp;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OPropertyImpl setLinkedClass(final OClass linkedClass) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    checkSupportLinkedClass(getType());

    acquireSchemaWriteLock();
    try {
      setLinkedClassInternal(linkedClass);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setLinkedClassInternal(final OClass iLinkedClass) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.linkedClass = iLinkedClass;

    } finally {
      releaseSchemaWriteLock();
    }

  }

  public OProperty setLinkedType(final OType linkedType) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    checkLinkTypeSupport(getType());

    acquireSchemaWriteLock();
    try {
      setLinkedTypeInternal(linkedType);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setLinkedTypeInternal(final OType iLinkedType) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);
    acquireSchemaWriteLock();
    try {
      checkEmbedded();
      this.linkedType = iLinkedType;

    } finally {
      releaseSchemaWriteLock();
    }

  }

  public OPropertyImpl setNotNull(final boolean isNotNull) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setNotNullInternal(isNotNull);
    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  protected void setNotNullInternal(final boolean isNotNull) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      notNull = isNotNull;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OPropertyImpl setDefaultValue(final String defaultValue) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setDefaultValueInternal(defaultValue);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setDefaultValueInternal(final String defaultValue) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.defaultValue = defaultValue;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OPropertyImpl setMax(final String max) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setMaxInternal(max);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setMaxInternal(final String max) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      checkForDateFormat(max);
      this.max = max;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OPropertyImpl setMin(final String min) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setMinInternal(min);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setMinInternal(final String min) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      checkForDateFormat(min);
      this.min = min;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OPropertyImpl setReadonly(final boolean isReadonly) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setReadonlyInternal(isReadonly);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setReadonlyInternal(final boolean isReadonly) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.readonly = isReadonly;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OPropertyImpl setMandatory(final boolean isMandatory) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      setMandatoryInternal(isMandatory);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  protected void setMandatoryInternal(final boolean isMandatory) {
    getDatabase().checkSecurity(ORule.ResourceGeneric.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.mandatory = isMandatory;
    } finally {
      releaseSchemaWriteLock();
    }
  }

}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  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.
 *  *
 *  * For more information: http://orientdb.com
 *
 */

package com.orientechnologies.orient.core.storage.impl.local.paginated.base;

import com.orientechnologies.common.serialization.types.OBinarySerializer;
import com.orientechnologies.common.serialization.types.OByteSerializer;
import com.orientechnologies.common.serialization.types.OIntegerSerializer;
import com.orientechnologies.common.serialization.types.OLongSerializer;
import com.orientechnologies.common.serialization.types.OShortSerializer;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.storage.cache.OCacheEntry;
import com.orientechnologies.orient.core.storage.cache.OCachePointer;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OLogSequenceNumber;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges;

import java.nio.ByteBuffer;

/**
 * Base page class for all durable data structures, that is data structures state of which can be consistently restored after system
 * crash but results of last operations in small interval before crash may be lost.
 *

 * This page has several booked memory areas with following offsets at the beginning:
 *

 *
from 0 to 7 - Magic number

 *
from 8 to 11 - crc32 of all page content, which is calculated by cache system just before save

 *
from 12 to 23 - LSN of last operation which was stored for given page

 *

 *

 * Developer which will extend this class should use all page memory starting from {@link #NEXT_FREE_POSITION} offset.
 * All data structures which use this kind of pages should be derived from
 * {@link com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent} class.
 *
 * @author Andrey Lomakin (a.lomakin-at-orientdb.com)
 * @since 16.08.13
 */
public class ODurablePage {

  public static final    int MAGIC_NUMBER_OFFSET = 0;
  protected static final int CRC32_OFFSET        = MAGIC_NUMBER_OFFSET + OLongSerializer.LONG_SIZE;

  public static final int WAL_SEGMENT_OFFSET  = CRC32_OFFSET + OIntegerSerializer.INT_SIZE;
  public static final int WAL_POSITION_OFFSET = WAL_SEGMENT_OFFSET + OLongSerializer.LONG_SIZE;
  public static final int MAX_PAGE_SIZE_BYTES = OGlobalConfiguration.DISK_CACHE_PAGE_SIZE.getValueAsInteger() * 1024;

  public static final int NEXT_FREE_POSITION = WAL_POSITION_OFFSET + OLongSerializer.LONG_SIZE;

  private final OWALChanges changes;

  private final OCacheEntry cacheEntry;

  private final OCachePointer pointer;

  public ODurablePage(final OCacheEntry cacheEntry) {
    assert cacheEntry != null;

    this.cacheEntry = cacheEntry;

    this.pointer = cacheEntry.getCachePointer();
    this.changes = cacheEntry.getChanges();
  }

  public static OLogSequenceNumber getLogSequenceNumberFromPage(final ByteBuffer buffer) {
    buffer.position(WAL_SEGMENT_OFFSET);
    final long segment = buffer.getLong();
    final long position = buffer.getLong();

    return new OLogSequenceNumber(segment, position);
  }

  /**
   * DO NOT DELETE THIS METHOD IT IS USED IN ENTERPRISE STORAGE
   *

   * Copies content of page into passed in byte array.
   *
   * @param buffer Buffer from which data will be copied
   * @param data   Byte array to which data will be copied
   * @param offset Offset of data inside page
   * @param length Length of data to be copied
   */
  @SuppressWarnings("unused")
  public static void getPageData(final ByteBuffer buffer, final byte[] data, final int offset, final int length) {
    buffer.position(0);
    buffer.get(data, offset, length);
  }

  /**
   * DO NOT DELETE THIS METHOD IT IS USED IN ENTERPRISE STORAGE
   *

   * Get value of LSN from the passed in offset in byte array.
   *
   * @param offset Offset inside of byte array from which LSN value will be read.
   * @param data   Byte array from which LSN value will be read.
   */
  @SuppressWarnings("unused")
  public static OLogSequenceNumber getLogSequenceNumber(final int offset, final byte[] data) {
    final long segment = OLongSerializer.INSTANCE.deserializeNative(data, offset + WAL_SEGMENT_OFFSET);
    final long position = OLongSerializer.INSTANCE.deserializeNative(data, offset + WAL_POSITION_OFFSET);

    return new OLogSequenceNumber(segment, position);
  }

  protected int getIntValue(final int pageOffset) {
    if (changes == null) {
      final ByteBuffer buffer = pointer.getBuffer();
      return buffer.getInt(pageOffset);
    }

    return changes.getIntValue(pointer.getBufferDuplicate(), pageOffset);
  }

  protected short getShortValue(final int pageOffset) {
    if (changes == null) {
      final ByteBuffer buffer = pointer.getBuffer();
      return buffer.getShort(pageOffset);
    }

    return changes.getShortValue(pointer.getBufferDuplicate(), pageOffset);
  }

  protected long getLongValue(final int pageOffset) {
    if (changes == null) {
      final ByteBuffer buffer = pointer.getBuffer();
      return buffer.getLong(pageOffset);
    }

    return changes.getLongValue(pointer.getBufferDuplicate(), pageOffset);
  }

  protected byte[] getBinaryValue(final int pageOffset, final int valLen) {
    final ByteBuffer buffer = pointer.getBufferDuplicate();
    if (changes == null) {
      final byte[] result = new byte[valLen];

      buffer.position(pageOffset);
      buffer.get(result);

      return result;
    }

    return changes.getBinaryValue(buffer, pageOffset, valLen);
  }

  protected int getObjectSizeInDirectMemory(final OBinarySerializer binarySerializer, final int offset) {
    final ByteBuffer buffer = pointer.getBufferDuplicate();
    if (changes == null) {
      buffer.position(offset);
      return binarySerializer.getObjectSizeInByteBuffer(buffer);
    }

    return binarySerializer.getObjectSizeInByteBuffer(buffer, changes, offset);
  }

  protected <T> T deserializeFromDirectMemory(final OBinarySerializer<T> binarySerializer, final int offset) {
    final ByteBuffer buffer = pointer.getBufferDuplicate();
    if (changes == null) {
      buffer.position(offset);
      return binarySerializer.deserializeFromByteBufferObject(buffer);
    }

    return binarySerializer.deserializeFromByteBufferObject(buffer, changes, offset);
  }

  protected byte getByteValue(final int pageOffset) {
    if (changes == null) {
      final ByteBuffer buffer = pointer.getBuffer();
      return buffer.get(pageOffset);
    }

    return changes.getByteValue(pointer.getBufferDuplicate(), pageOffset);
  }

  @SuppressWarnings("SameReturnValue")
  protected int setIntValue(final int pageOffset, final int value) {
    final ByteBuffer buffer = pointer.getBuffer();
    if (changes != null) {
      changes.setIntValue(buffer, value, pageOffset);
    } else {
      buffer.putInt(pageOffset, value);
    }

    return OIntegerSerializer.INT_SIZE;
  }

  protected int setShortValue(final int pageOffset, final short value) {
    final ByteBuffer buffer = pointer.getBuffer();
    if (changes != null) {
      changes.setIntValue(buffer, value, pageOffset);
    } else {
      buffer.putShort(pageOffset, value);
    }

    return OShortSerializer.SHORT_SIZE;
  }

  @SuppressWarnings("SameReturnValue")
  protected int setByteValue(final int pageOffset, final byte value) {
    final ByteBuffer buffer = pointer.getBuffer();
    if (changes != null) {
      changes.setByteValue(buffer, value, pageOffset);
    } else {
      buffer.put(pageOffset, value);
    }

    return OByteSerializer.BYTE_SIZE;
  }

  @SuppressWarnings("SameReturnValue")
  protected int setLongValue(final int pageOffset, final long value) {
    final ByteBuffer buffer = pointer.getBuffer();
    if (changes != null) {
      changes.setLongValue(buffer, value, pageOffset);
    } else {
      buffer.putLong(pageOffset, value);
    }

    return OLongSerializer.LONG_SIZE;
  }

  protected int setBinaryValue(final int pageOffset, final byte[] value) {
    if (value.length == 0) {
      return 0;
    }

    final ByteBuffer buffer = pointer.getBuffer();
    if (changes != null) {
      changes.setBinaryValue(buffer, value, pageOffset);
    } else {
      buffer.position(pageOffset);
      buffer.put(value);
    }

    return value.length;
  }

  protected void moveData(final int from, final int to, final int len) {
    if (len == 0) {
      return;
    }

    final ByteBuffer buffer = pointer.getBuffer();
    if (changes != null) {
      changes.moveData(buffer, from, to, len);
    } else {
      final ByteBuffer rb = buffer.asReadOnlyBuffer();
      rb.position(from);
      rb.limit(from + len);

      buffer.position(to);
      buffer.put(rb);
    }
  }

  public OWALChanges getChanges() {
    return changes;
  }

  public void restoreChanges(final OWALChanges changes) {
    final ByteBuffer buffer = cacheEntry.getCachePointer().getBuffer();

    buffer.position(0);
    changes.applyChanges(buffer);
  }

  public void setLsn(final OLogSequenceNumber lsn) {
    final ByteBuffer buffer = pointer.getBuffer();
    buffer.position(WAL_SEGMENT_OFFSET);

    buffer.putLong(lsn.getSegment());
    buffer.putLong(lsn.getPosition());
  }

  @Override
  public String toString() {
    if (cacheEntry != null) {
      return getClass().getSimpleName() + "{" + "fileId=" + cacheEntry.getFileId() + ", pageIndex=" + cacheEntry.getPageIndex()
          + '}';
    } else {
      return super.toString();
    }
  }
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  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.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.orient.graph.gremlin;

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.command.OCommandManager;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.record.ORecordInternal;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.tinkerpop.blueprints.impls.orient.OrientBaseGraph;
import com.tinkerpop.blueprints.impls.orient.OrientGraph;
import com.tinkerpop.blueprints.impls.orient.OrientGraphFactory;
import com.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine;
import com.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngineFactory;
import com.tinkerpop.gremlin.java.GremlinPipeline;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;

public class OGremlinHelper {
  private static final String PARAM_OUTPUT = "output";
  private static GremlinGroovyScriptEngineFactory factory;
  private static OGremlinHelper instance = new OGremlinHelper();

  private int maxPool = 50;

  public interface OGremlinCallback {
    boolean call(ScriptEngine iEngine, OrientBaseGraph iGraph);
  }

  public OGremlinHelper() {
    try {
      factory = new GremlinGroovyScriptEngineFactory();
    } catch (java.lang.NoClassDefFoundError e) {
      OLogManager.instance().warn(this, "GREMLIN language not available (not in classpath)");
    }
    OCommandManager.instance().registerRequester("gremlin", OCommandGremlin.class);
    OCommandManager.instance().registerExecutor(OCommandGremlin.class, OCommandGremlinExecutor.class);
  }

  public static boolean isGremlinAvailable() {
    return factory != null;
  }

  @SuppressWarnings("unchecked")
  public static Object execute(final ODatabaseDocumentInternal iDatabase, final String iText,
      final Map<Object, Object> iConfiguredParameters, Map<Object, Object> iCurrentParameters, final List<Object> iResult,
      final OGremlinCallback iBeforeExecution, final OGremlinCallback iAfterExecution) {
    return execute(OGremlinHelper.global().acquireGraph(iDatabase), iText, iConfiguredParameters, iCurrentParameters, iResult,
        iBeforeExecution, iAfterExecution);
  }

  public static Object execute(final OrientBaseGraph graph, final String iText, final Map<Object, Object> iConfiguredParameters,
      Map<Object, Object> iCurrentParameters, final List<Object> iResult, final OGremlinCallback iBeforeExecution,
      final OGremlinCallback iAfterExecution) {
    try {
      final ScriptEngine engine = getGremlinEngine(graph);

      try {
        final String output = OGremlinHelper.bindParameters(engine, iConfiguredParameters, iCurrentParameters);

        if (iBeforeExecution != null)
          if (!iBeforeExecution.call(engine, graph))
            return null;

        if (iText == null) {
          return null;
        }
        final Object scriptResult = engine.eval(iText);

        if (iAfterExecution != null)
          if (!iAfterExecution.call(engine, graph))
            return null;

        // Case of 1 output bound variable. Return as:
        // - Map -> ODocument
        if (output != null) {
          if (scriptResult instanceof GremlinPipeline) {
            Iterator<?> it = ((GremlinPipeline<?, ?>) scriptResult).iterator();
            while (it.hasNext())
              // ignore iCurrentRecord but traverse still required
              it.next();
          }
          final Map<String, Object> map = (Map<String, Object>) engine.get(output);
          ODocument oDocument = new ODocument(map);
          iResult.add(oDocument);
          return oDocument;
        }

        // Case of no bound variables. Return as:
        // - List<ODocument>
        // - ODocument
        // - Integer
        // returned for this call in the last pipe
        if (scriptResult instanceof GremlinPipeline) {
          final Iterator<?> it = ((GremlinPipeline<?, ?>) scriptResult).iterator();
          Object finalResult = null;
          List<Object> resultCollection = null;
          int cursor = -1;
          while (it.hasNext()) {
            Object current = it.next();

            // if (current instanceof OrientElement)
            // current = ((OrientElement) current).getRawElement();
            if (current instanceof Map) {
              ODocument doc = new ODocument().fromMap((Map<String, ? extends Object>) current);
              ORecordInternal.setIdentity(doc, new ORecordId(-2, cursor--));
              current = doc;
            } else if (current instanceof List) {
              ODocument doc = new ODocument().field("value", current);
              ORecordInternal.setIdentity(doc, new ORecordId(-2, cursor--));
              current = doc;
            }

            if (finalResult != null) {
              if (resultCollection == null) {
                // CONVERT IT INTO A COLLECTION
                resultCollection = new ArrayList<Object>();
                resultCollection.add(finalResult);
              }

              resultCollection.add(current);
            } else
              finalResult = current;
          }

          if (resultCollection != null) {
            iResult.addAll(resultCollection);
            return resultCollection;
          } else {
            if (finalResult != null)
              iResult.add(finalResult);
            return finalResult;
          }

        } else if (scriptResult != null)
          iResult.add(scriptResult);

        return scriptResult;
      } catch (Exception e) {
        throw OException.wrapException(new OCommandExecutionException("Error on execution of the GREMLIN script"), e);
      } finally {
        OGremlinHelper.global().releaseEngine(engine);
      }
    } finally {
      OGremlinHelper.global().releaseGraph(graph);
    }
  }

  protected static ScriptEngine getGremlinEngine(final OrientBaseGraph graph) {
    return OGremlinEngineThreadLocal.INSTANCE.get(graph);
  }

  public static String bindParameters(final ScriptEngine iEngine, final Map<Object, Object> iParameters,
      Map<Object, Object> iCurrentParameters) {
    if (iParameters != null && !iParameters.isEmpty())
      // Every call to the function is a execution itself. Therefore, it requires a fresh set of input parameters.
      // Therefore, clone the parameters map trying to recycle previous instances
      for (Entry<Object, Object> param : iParameters.entrySet()) {
        final String key = (String) param.getKey();
        final Object objectToClone = param.getValue();
        final Object previousItem = iCurrentParameters.get(key); // try to recycle it
        final Object newItem = OGremlinHelper.cloneObject(objectToClone, previousItem);
        iCurrentParameters.put(key, newItem);
      }

    String output = null;
    if (iCurrentParameters != null)
      for (Entry<Object, Object> param : iCurrentParameters.entrySet()) {
        final String paramName = param.getKey().toString().trim();
        if (paramName.equals(PARAM_OUTPUT)) {
          output = param.getValue().toString();
          continue;
        }
        iEngine.getBindings(ScriptContext.ENGINE_SCOPE).put(paramName, param.getValue());
      }
    return output;
  }

  /*
   * Tries to clone any Java object by using 3 techniques: - instanceof (most verbose but faster performance) - reflection (medium
   * performance) - serialization (applies for any object type but has a performance overhead)
   */
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public static Object cloneObject(final Object objectToClone, final Object previousClone) {

    // ***************************************************************************************************************************************
    // 1. Class by class cloning (only clones known types)
    // ***************************************************************************************************************************************
    // Clone any Map (shallow clone should be enough at this level)
    if (objectToClone instanceof Map) {
      Map recycledMap = (Map) previousClone;
      if (recycledMap == null)
        recycledMap = new HashMap();
      else
        recycledMap.clear();
      recycledMap.putAll((Map<?, ?>) objectToClone);
      return recycledMap;

      // Clone any collection (shallow clone should be enough at this level)
    } else if (objectToClone instanceof Collection) {
      Collection recycledCollection = (Collection) previousClone;
      if (recycledCollection == null)
        recycledCollection = new ArrayList();
      else
        recycledCollection.clear();
      recycledCollection.addAll((Collection<?>) objectToClone);
      return recycledCollection;

      // Clone String
    } else if (objectToClone instanceof String) {
      return objectToClone;
    } else if (objectToClone instanceof Number) {
      return objectToClone;
      // Clone Date
    } else if (objectToClone instanceof Date) {
      return (Date) ((Date) objectToClone).clone();

    } else {
      // ***************************************************************************************************************************************
      // 2. Polymorphic clone (by reflection, looks for a clone() method in hierarchy and invoke it)
      // ***************************************************************************************************************************************
      try {
        Object newClone;
        for (Class<?> obj = objectToClone.getClass(); !obj.equals(Object.class); obj = obj.getSuperclass()) {
          Method m[] = obj.getDeclaredMethods();
          for (int i = 0; i < m.length; i++) {
            if (m[i].getName().equals("clone")) {
              m[i].setAccessible(true);
              newClone = m[i].invoke(objectToClone);
              System.out.println(objectToClone.getClass()
                  + " cloned by Reflection. Performance can be improved by adding the class to the list of known types");
              return newClone;
            }
          }
        }
        throw new Exception("Method clone not found");

        // ***************************************************************************************************************************************
        // 3. Polymorphic clone (Deep cloning by Serialization)
        // ***************************************************************************************************************************************
      } catch (Exception e1) {
        try {
          final ByteArrayOutputStream bytes = new ByteArrayOutputStream() {
            public synchronized byte[] toByteArray() {
              return buf;
            }
          };
          final ObjectOutputStream out = new ObjectOutputStream(bytes);
          out.writeObject(objectToClone);
          out.close();
          final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()));
          System.out.println(objectToClone.getClass()
              + " cloned by Serialization. Performance can be improved by adding the class to the list of known types");
          return in.readObject();

          // ***************************************************************************************************************************************
          // 4. Impossible to clone
          // ***************************************************************************************************************************************
        } catch (Exception e2) {
          OLogManager.instance()
              .error(null, "[GremlinHelper] error on cloning object %s, previous %s", e2, objectToClone, previousClone);
          return null;
        }
      }
    }
  }

  public static OGremlinHelper global() {
    return instance;
  }

  public static ODatabaseDocumentInternal getGraphDatabase(final ODatabaseDocumentInternal iCurrentDatabase) {
    ODatabaseDocumentInternal currentDb = ODatabaseRecordThreadLocal.instance().get();
    if (currentDb == null && iCurrentDatabase != null)
      // GET FROM THE RECORD
      currentDb = iCurrentDatabase;

    if (currentDb != null)
      currentDb = (ODatabaseDocumentInternal) currentDb.getDatabaseOwner();

    return currentDb;
  }

  public static String getEngineVersion() {
    return factory.getEngineVersion();
  }

  /**
   * Initializes the pools.
   */
  public void create() {
  }

  /**
   * Destroys the helper by cleaning all the in memory objects.
   */
  public void destroy() {
  }

  public ScriptEngine acquireEngine() {
    return new GremlinGroovyScriptEngine();
  }

  public void releaseEngine(final ScriptEngine engine) {
  }

  public OrientGraph acquireGraph(final ODatabaseDocumentInternal database) {
    return (OrientGraph) OrientGraphFactory.getTxGraphImplFactory().getGraph(database);
  }

  public void releaseGraph(final OrientBaseGraph graph) {
    graph.shutdown(false);
  }

  public int getMaxPool() {
    return maxPool;
  }

  public OGremlinHelper setMaxGraphPool(final int maxGraphs) {
    this.maxPool = maxGraphs;
    return this;
  }

  protected ScriptEngine getGroovyEngine() {
    return factory.getScriptEngine();
  }

}