/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.usergrid.tools; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import com.datastax.driver.core.BatchStatement; import com.datastax.driver.core.ConsistencyLevel; import com.datastax.driver.core.Session; import org.apache.usergrid.persistence.EntityManager; import org.apache.usergrid.persistence.collection.MvccEntity; import org.apache.usergrid.persistence.collection.serialization.MvccEntitySerializationStrategy; import org.apache.usergrid.persistence.collection.serialization.UniqueValue; import org.apache.usergrid.persistence.collection.serialization.UniqueValueSerializationStrategy; import org.apache.usergrid.persistence.collection.serialization.UniqueValueSet; import org.apache.usergrid.persistence.collection.serialization.impl.*; import org.apache.usergrid.persistence.core.scope.ApplicationScope; import org.apache.usergrid.persistence.core.scope.ApplicationScopeImpl; import org.apache.usergrid.persistence.model.entity.SimpleId; import org.apache.usergrid.persistence.model.field.StringField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.usergrid.persistence.core.astyanax.MultiTenantColumnFamily; import org.apache.usergrid.persistence.core.astyanax.ScopedRowKey; import org.apache.usergrid.persistence.core.astyanax.ScopedRowKeySerializer; public class UniqueValueManager extends ToolBase { private static final Logger logger = LoggerFactory.getLogger( UniqueValueManager.class ); private static final String OPERATION_ARG = "op"; private static final String CONFIRM_DELETE_ARG = "confirmDelete"; private static final String CONFIRM_UPDATE_ARG = "confirmUpdate"; private static final String SERIALIZATION_REPAIR_ARG = "useSerializationRepair"; private static final String FILEPATH_ARG = "file"; //copied shamelessly from unique value serialization strat. private static final ScopedRowKeySerializer<TypeField> ROW_KEY_SER = new ScopedRowKeySerializer<>( UniqueTypeFieldRowKeySerializer.get() ); private final EntityVersionSerializer ENTITY_VERSION_SER = new EntityVersionSerializer(); private final MultiTenantColumnFamily<ScopedRowKey<TypeField>, EntityVersion> CF_UNIQUE_VALUES = new MultiTenantColumnFamily<>( "Unique_Values_V2", ROW_KEY_SER, ENTITY_VERSION_SER ); private Session session; private MvccEntitySerializationStrategy mvccEntitySerializationStrategy; private UniqueValueSerializationStrategy uniqueValueSerializationStrategy; private EntityManager em; @Override @SuppressWarnings( "static-access" ) public Options createOptions() { Options options = super.createOptions(); Option opOption = OptionBuilder.withArgName(OPERATION_ARG).hasArg().isRequired( false ).withDescription( "operation" ) .create(OPERATION_ARG); options.addOption( opOption ); Option confirmDeleteOption = OptionBuilder.withArgName(CONFIRM_DELETE_ARG).isRequired( false ).withDescription( "confirm delete" ) .create(CONFIRM_DELETE_ARG); options.addOption( confirmDeleteOption ); Option confirmUpdateOption = OptionBuilder.withArgName(CONFIRM_UPDATE_ARG).isRequired( false ).withDescription( "confirm update" ) .create(CONFIRM_UPDATE_ARG); options.addOption( confirmUpdateOption ); Option useSerializationRepair = OptionBuilder.withArgName(SERIALIZATION_REPAIR_ARG).isRequired( false ).withDescription( "use unique value serialization repair to keep oldest index" ) .create(SERIALIZATION_REPAIR_ARG); options.addOption( useSerializationRepair ); Option filepathOption = OptionBuilder.withArgName(FILEPATH_ARG).hasArg().isRequired( true ) .withDescription( "path to file containing UV info" ).create(FILEPATH_ARG); options.addOption( filepathOption ); return options; } /* * (non-Javadoc) * * @see * org.apache.usergrid.tools.ToolBase#runTool(org.apache.commons.cli.CommandLine) */ @Override public void runTool( CommandLine line ) throws Exception { startSpring(); logger.info("Staring Tool: UniqueValueManager"); logger.info("Using Cassandra consistency level: {}", System.getProperty("usergrid.read.cl", "CL_LOCAL_QUORUM")); String operation = line.getOptionValue(OPERATION_ARG) != null ? line.getOptionValue(OPERATION_ARG) : "get"; boolean deleteOp = operation.toLowerCase().equals("delete"); boolean updateOp = operation.toLowerCase().equals("update"); if (deleteOp && !line.hasOption(CONFIRM_DELETE_ARG)) { throw new RuntimeException("Must add confirmDelete option to use delete."); }else if( updateOp && !line.hasOption(CONFIRM_UPDATE_ARG) ){ throw new RuntimeException("Must add confirmUpdate option to use update."); } String filepath = line.getOptionValue(FILEPATH_ARG); if (filepath == null || filepath.isEmpty()) { throw new RuntimeException("File is required -- should contain one row per entity formatted like " + "'{uuid}|{entityType}|{fieldType}|{fieldValue}'. " + "Example: 'b9398e88-ef7f-11e5-9e41-0a2cb9e6caa9|user|email|baasadmins@apigee.com'"); } session = injector.getInstance(Session.class); mvccEntitySerializationStrategy = injector.getInstance(MvccEntitySerializationStrategy.class); uniqueValueSerializationStrategy = injector.getInstance(UniqueValueSerializationStrategy.class); AtomicInteger count = new AtomicInteger(0); File listFile = new File(filepath); boolean useSerializationRepair = false; if(line.hasOption(SERIALIZATION_REPAIR_ARG)){ useSerializationRepair = true; } try (BufferedReader br = new BufferedReader(new FileReader(listFile))) { String fileLine; while ((fileLine = br.readLine()) != null) { String[] valuesArray = fileLine.trim().split("\\|"); if (valuesArray.length != 4 && valuesArray.length != 5) { logger.info("Line: >"+fileLine+"<"); throw new RuntimeException("Invalid file -- should contain one row per entity formatted like " + "'{uuid}|{entityType}|{fieldType}|{fieldValue}|{newEntityUUID}'. " + "Example: 'b9398e88-ef7f-11e5-9e41-0a2cb9e6caa9|user|email|whatever@usergrid.com|newEntityUUID'. " + "Param {newEntityUUID} is optional."); } UUID appUuid = UUID.fromString(valuesArray[0]); String entityType = valuesArray[1]; String fieldType = valuesArray[2]; String fieldValue = valuesArray[3]; UniqueValueSet uniqueValueSet = uniqueValueSerializationStrategy.load( new ApplicationScopeImpl(new SimpleId(appUuid, "application")), ConsistencyLevel.valueOf(System.getProperty("usergrid.read.cl", "CL_LOCAL_QUORUM")), entityType, Collections.singletonList(new StringField(fieldType, fieldValue)), useSerializationRepair); if( updateOp) { if(valuesArray.length!=5){ throw new RuntimeException("Missing param {newEntityUUID}"); } String updateUUID = valuesArray[4]; ApplicationScope applicationScope = new ApplicationScopeImpl(new SimpleId(appUuid, "application")); com.google.common.base.Optional<MvccEntity> entity = mvccEntitySerializationStrategy.load(applicationScope, new SimpleId(UUID.fromString(updateUUID), entityType)); if( !entity.isPresent() || !entity.get().getEntity().isPresent() ){ throw new RuntimeException("Unable to update unique value index because supplied UUID "+updateUUID+" does not exist"); } logger.info("Delete unique value: {}", uniqueValueSet.getValue(fieldType)); session.execute(uniqueValueSerializationStrategy.deleteCQL(applicationScope, uniqueValueSet.getValue(fieldType))); UniqueValue newUniqueValue = new UniqueValueImpl(new StringField(fieldType, fieldValue), entity.get().getId(), entity.get().getVersion()); logger.info("Writing new unique value: {}", newUniqueValue); session.execute(uniqueValueSerializationStrategy.writeCQL(applicationScope, newUniqueValue, -1)); logger.info("Re-loading unique value set for field"); } uniqueValueSet = uniqueValueSerializationStrategy.load( new ApplicationScopeImpl(new SimpleId(appUuid, "application")), ConsistencyLevel.valueOf(System.getProperty("usergrid.read.cl", "CL_LOCAL_QUORUM")), entityType, Collections.singletonList(new StringField(fieldType, fieldValue)), useSerializationRepair); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("["); uniqueValueSet.forEach(uniqueValue -> { String entry = "fieldName=" + uniqueValue.getField().getName() + ", fieldValue=" + uniqueValue.getField().getValue() + ", uuid=" + uniqueValue.getEntityId().getUuid() + ", type=" + uniqueValue.getEntityId().getType() + ", version=" + uniqueValue.getEntityVersion(); stringBuilder.append("{").append(entry).append("},"); }); stringBuilder.deleteCharAt(stringBuilder.length() - 1); stringBuilder.append("]"); logger.info("Returned unique value set from serialization load = {}", stringBuilder.toString()); if (deleteOp) { uniqueValueSet.forEach(uniqueValue -> { logger.info("DELETING UNIQUE VALUE"); try { BatchStatement batchStatement = uniqueValueSerializationStrategy. deleteCQL(new ApplicationScopeImpl(new SimpleId(appUuid, "application")), uniqueValue); session.execute(batchStatement); } catch (Exception e) { logger.error("Exception thrown for UV delete: " + e.getMessage()); } }); } } } } }