Search in sources :

Example 1 with GroupRef

use of org.voltdb.catalog.GroupRef in project voltdb by VoltDB.

the class ProcedureCompiler method compileSingleStmtProcedure.

static void compileSingleStmtProcedure(VoltCompiler compiler, HSQLInterface hsql, DatabaseEstimates estimates, Database db, ProcedureDescriptor procedureDescriptor) throws VoltCompiler.VoltCompilerException {
    final String className = procedureDescriptor.m_className;
    if (className.indexOf('@') != -1) {
        throw compiler.new VoltCompilerException("User procedure names can't contain \"@\".");
    }
    // get the short name of the class (no package if a user procedure)
    // use the Table.<builtin> name (allowing the period) if builtin.
    String shortName = className;
    if (procedureDescriptor.m_builtInStmt == false) {
        String[] parts = className.split("\\.");
        shortName = parts[parts.length - 1];
    }
    // add an entry to the catalog (using the full className)
    final Procedure procedure = db.getProcedures().add(shortName);
    for (String groupName : procedureDescriptor.m_authGroups) {
        final Group group = db.getGroups().get(groupName);
        if (group == null) {
            throw compiler.new VoltCompilerException("Procedure " + className + " allows access by a role " + groupName + " that does not exist");
        }
        final GroupRef groupRef = procedure.getAuthgroups().add(groupName);
        groupRef.setGroup(group);
    }
    procedure.setClassname(className);
    // sysprocs don't use the procedure compiler
    procedure.setSystemproc(false);
    procedure.setDefaultproc(procedureDescriptor.m_builtInStmt);
    procedure.setHasjava(false);
    procedure.setTransactional(true);
    // get the annotation
    // first try to get one that has been passed from the compiler
    ProcInfoData info = compiler.getProcInfoOverride(shortName);
    // and create a ProcInfo.Data instance for it
    if (info == null) {
        info = new ProcInfoData();
        if (procedureDescriptor.m_partitionString != null) {
            info.partitionInfo = procedureDescriptor.m_partitionString;
            info.singlePartition = true;
        }
    }
    assert (info != null);
    // ADD THE STATEMENT
    // add the statement to the catalog
    Statement catalogStmt = procedure.getStatements().add(VoltDB.ANON_STMT_NAME);
    // compile the statement
    StatementPartitioning partitioning = info.singlePartition ? StatementPartitioning.forceSP() : StatementPartitioning.forceMP();
    // default to FASTER detmode because stmt procs can't feed read output into writes
    StatementCompiler.compileFromSqlTextAndUpdateCatalog(compiler, hsql, db, estimates, catalogStmt, procedureDescriptor.m_singleStmt, procedureDescriptor.m_joinOrder, DeterminismMode.FASTER, partitioning);
    // if the single stmt is not read only, then the proc is not read only
    boolean procHasWriteStmts = (catalogStmt.getReadonly() == false);
    // set the read onlyness of a proc
    procedure.setReadonly(procHasWriteStmts == false);
    int seqs = catalogStmt.getSeqscancount();
    procedure.setHasseqscans(seqs > 0);
    // set procedure parameter types
    CatalogMap<ProcParameter> params = procedure.getParameters();
    CatalogMap<StmtParameter> stmtParams = catalogStmt.getParameters();
    // set the procedure parameter types from the statement parameter types
    int paramCount = 0;
    for (StmtParameter stmtParam : CatalogUtil.getSortedCatalogItems(stmtParams, "index")) {
        // name each parameter "param1", "param2", etc...
        ProcParameter procParam = params.add("param" + String.valueOf(paramCount));
        procParam.setIndex(stmtParam.getIndex());
        procParam.setIsarray(stmtParam.getIsarray());
        procParam.setType(stmtParam.getJavatype());
        paramCount++;
    }
    // parse the procinfo
    procedure.setSinglepartition(info.singlePartition);
    if (info.singlePartition) {
        parsePartitionInfo(compiler, db, procedure, info.partitionInfo);
        if (procedure.getPartitionparameter() >= params.size()) {
            String msg = "PartitionInfo parameter not a valid parameter for procedure: " + procedure.getClassname();
            throw compiler.new VoltCompilerException(msg);
        }
    // TODO: The planner does not currently validate that a single-statement plan declared as single-partition correctly uses
    // the designated parameter as a partitioning filter, maybe some day.
    // In theory, the PartitioningForStatement would confirm the use of (only) a parameter as a partition key --
    // or if the partition key was determined to be some other hard-coded constant (expression?) it might display a warning
    // message that the passed parameter is assumed to be equal to that constant (expression).
    } else {
        if (partitioning.getCountOfIndependentlyPartitionedTables() == 1) {
            AbstractExpression statementPartitionExpression = partitioning.singlePartitioningExpressionForReport();
            if (statementPartitionExpression != null) {
                // The planner has uncovered an overlooked opportunity to run the statement SP.
                String msg = "This procedure " + shortName + " would benefit from being partitioned, by ";
                String tableName = "tableName", partitionColumnName = "partitionColumnName";
                try {
                    assert (partitioning.getFullColumnName() != null);
                    String[] array = partitioning.getFullColumnName().split("\\.");
                    tableName = array[0];
                    partitionColumnName = array[1];
                } catch (Exception ex) {
                }
                if (statementPartitionExpression instanceof ParameterValueExpression) {
                    paramCount = ((ParameterValueExpression) statementPartitionExpression).getParameterIndex();
                } else {
                    String valueDescription = null;
                    Object partitionValue = partitioning.getInferredPartitioningValue();
                    if (partitionValue == null) {
                        // Statement partitioned on a runtime constant. This is likely to be cryptic, but hopefully gets the idea across.
                        valueDescription = "of " + statementPartitionExpression.explain("");
                    } else {
                        // A simple constant value COULD have been a parameter.
                        valueDescription = partitionValue.toString();
                    }
                    msg += "adding a parameter to be passed the value " + valueDescription + " and ";
                }
                msg += "adding a 'PARTITION ON TABLE " + tableName + " COLUMN " + partitionColumnName + " PARAMETER " + paramCount + "' clause to the " + "CREATE PROCEDURE statement. or using a separate PARTITION PROCEDURE statement";
                compiler.addWarn(msg);
            }
        }
    }
}
Also used : Group(org.voltdb.catalog.Group) Statement(org.voltdb.catalog.Statement) VoltCompilerException(org.voltdb.compiler.VoltCompiler.VoltCompilerException) VoltTypeException(org.voltdb.VoltTypeException) StmtParameter(org.voltdb.catalog.StmtParameter) AbstractExpression(org.voltdb.expressions.AbstractExpression) ProcInfoData(org.voltdb.ProcInfoData) StatementPartitioning(org.voltdb.planner.StatementPartitioning) VoltProcedure(org.voltdb.VoltProcedure) VoltNonTransactionalProcedure(org.voltdb.VoltNonTransactionalProcedure) Procedure(org.voltdb.catalog.Procedure) VoltCompilerException(org.voltdb.compiler.VoltCompiler.VoltCompilerException) GroupRef(org.voltdb.catalog.GroupRef) ParameterValueExpression(org.voltdb.expressions.ParameterValueExpression) ProcParameter(org.voltdb.catalog.ProcParameter)

Example 2 with GroupRef

use of org.voltdb.catalog.GroupRef in project voltdb by VoltDB.

the class SystemInformation method populateDeploymentProperties.

public static VoltTable populateDeploymentProperties(Cluster cluster, Database database, ClusterSettings clusterSettings, NodeSettings nodeSettings) {
    VoltTable results = new VoltTable(clusterInfoSchema);
    // it would be awesome if these property names could come
    // from the RestApiDescription.xml (or the equivalent thereof) someday --izzy
    results.addRow("voltdbroot", VoltDB.instance().getVoltDBRootPath());
    Deployment deploy = cluster.getDeployment().get("deployment");
    results.addRow("hostcount", Integer.toString(clusterSettings.hostcount()));
    results.addRow("kfactor", Integer.toString(deploy.getKfactor()));
    results.addRow("sitesperhost", Integer.toString(nodeSettings.getLocalSitesCount()));
    String http_enabled = "false";
    int http_port = VoltDB.instance().getConfig().m_httpPort;
    if (http_port != -1 && http_port != Integer.MAX_VALUE) {
        http_enabled = "true";
        results.addRow("httpport", Integer.toString(http_port));
    }
    results.addRow("httpenabled", http_enabled);
    String json_enabled = "false";
    if (cluster.getJsonapi()) {
        json_enabled = "true";
    }
    results.addRow("jsonenabled", json_enabled);
    SnapshotSchedule snaps = database.getSnapshotschedule().get("default");
    String snap_enabled = "false";
    if (snaps != null && snaps.getEnabled()) {
        snap_enabled = "true";
        String snap_freq = Integer.toString(snaps.getFrequencyvalue()) + snaps.getFrequencyunit();
        results.addRow("snapshotpath", VoltDB.instance().getSnapshotPath());
        results.addRow("snapshotprefix", snaps.getPrefix());
        results.addRow("snapshotfrequency", snap_freq);
        results.addRow("snapshotretain", Integer.toString(snaps.getRetain()));
    }
    results.addRow("snapshotenabled", snap_enabled);
    for (Connector export_conn : database.getConnectors()) {
        if (export_conn != null && export_conn.getEnabled()) {
            results.addRow("exportoverflowpath", VoltDB.instance().getExportOverflowPath());
            break;
        }
    }
    results.addRow("export", Boolean.toString(CatalogUtil.isExportEnabled()));
    String partition_detect_enabled = "false";
    if (cluster.getNetworkpartition()) {
        partition_detect_enabled = "true";
    }
    results.addRow("partitiondetection", partition_detect_enabled);
    results.addRow("heartbeattimeout", Integer.toString(cluster.getHeartbeattimeout()));
    results.addRow("adminport", Integer.toString(VoltDB.instance().getConfig().m_adminPort));
    String command_log_enabled = "false";
    // log name is MAGIC, you knoooow
    CommandLog command_log = cluster.getLogconfig().get("log");
    if (command_log.getEnabled()) {
        command_log_enabled = "true";
        String command_log_mode = "async";
        if (command_log.getSynchronous()) {
            command_log_mode = "sync";
        }
        String command_log_path = VoltDB.instance().getCommandLogPath();
        String command_log_snaps = VoltDB.instance().getCommandLogSnapshotPath();
        String command_log_fsync_interval = Integer.toString(command_log.getFsyncinterval());
        String command_log_max_txns = Integer.toString(command_log.getMaxtxns());
        results.addRow("commandlogmode", command_log_mode);
        results.addRow("commandlogfreqtime", command_log_fsync_interval);
        results.addRow("commandlogfreqtxns", command_log_max_txns);
        results.addRow("commandlogpath", command_log_path);
        results.addRow("commandlogsnapshotpath", command_log_snaps);
    }
    results.addRow("commandlogenabled", command_log_enabled);
    String users = "";
    for (User user : database.getUsers()) {
        users += addEscapes(user.getTypeName());
        if (user.getGroups() != null && user.getGroups().size() > 0) {
            users += ":";
            for (GroupRef gref : user.getGroups()) {
                users += addEscapes(gref.getGroup().getTypeName());
                users += ",";
            }
            users = users.substring(0, users.length() - 1);
        }
        users += ";";
    }
    results.addRow("users", users);
    // Add system setting information also
    // the attribute names follows the above naming rule
    Systemsettings sysSettings = deploy.getSystemsettings().get("systemsettings");
    results.addRow("elasticduration", Integer.toString(sysSettings.getElasticduration()));
    results.addRow("elasticthroughput", Integer.toString(sysSettings.getElasticthroughput()));
    results.addRow("snapshotpriority", Integer.toString(sysSettings.getSnapshotpriority()));
    results.addRow("temptablesmaxsize", Integer.toString(sysSettings.getTemptablemaxsize()));
    results.addRow("querytimeout", Integer.toString(sysSettings.getQuerytimeout()));
    return results;
}
Also used : Connector(org.voltdb.catalog.Connector) User(org.voltdb.catalog.User) Systemsettings(org.voltdb.catalog.Systemsettings) CommandLog(org.voltdb.catalog.CommandLog) Deployment(org.voltdb.catalog.Deployment) SnapshotSchedule(org.voltdb.catalog.SnapshotSchedule) GroupRef(org.voltdb.catalog.GroupRef) VoltTable(org.voltdb.VoltTable)

Example 3 with GroupRef

use of org.voltdb.catalog.GroupRef in project voltdb by VoltDB.

the class CatalogSchemaTools method toSchema.

/**
     * Convert a Catalog Procedure into a DDL string.
     * @param proc
     */
public static void toSchema(StringBuilder sb, Procedure proc) {
    // JAVA: hasJava (true, m_defaultproc (false)
    if (proc.getDefaultproc()) {
        return;
    }
    // Build the optional ALLOW clause.
    CatalogMap<GroupRef> roleList = proc.getAuthgroups();
    String add;
    String allowClause = new String();
    if (roleList.size() > 0) {
        add = "\n" + spacer + "ALLOW ";
        for (GroupRef role : roleList) {
            allowClause += add + role.getGroup().getTypeName();
            add = ", ";
        }
    }
    // Build the optional PARTITION clause.
    StringBuilder partitionClause = new StringBuilder();
    ProcedureAnnotation annot = (ProcedureAnnotation) proc.getAnnotation();
    if (proc.getSinglepartition()) {
        if (annot != null && annot.classAnnotated) {
            partitionClause.append("--Annotated Partitioning Takes Precedence Over DDL Procedure Partitioning Statement\n--");
        } else {
            partitionClause.append("\n");
        }
        partitionClause.append(spacer);
        partitionClause.append(String.format("PARTITION ON TABLE %s COLUMN %s", proc.getPartitiontable().getTypeName(), proc.getPartitioncolumn().getTypeName()));
        if (proc.getPartitionparameter() != 0) {
            partitionClause.append(String.format(" PARAMETER %s", String.valueOf(proc.getPartitionparameter())));
        }
    }
    // Build the appropriate CREATE PROCEDURE statement variant.
    if (!proc.getHasjava()) {
        // SQL Statement procedure
        sb.append(String.format("CREATE PROCEDURE %s%s%s\n%sAS\n%s%s", proc.getClassname(), allowClause, partitionClause.toString(), spacer, spacer, proc.getStatements().get("SQL").getSqltext().trim()));
    } else {
        // Java Class
        sb.append(String.format("CREATE PROCEDURE %s%s\n%sFROM CLASS %s", allowClause, partitionClause.toString(), spacer, proc.getClassname()));
    }
    // The SQL statement variant may have terminated the CREATE PROCEDURE statement.
    if (!sb.toString().endsWith(";")) {
        sb.append(";");
    }
    // Give me some space man.
    sb.append("\n\n");
}
Also used : ProcedureAnnotation(org.voltdb.compilereport.ProcedureAnnotation) GroupRef(org.voltdb.catalog.GroupRef)

Example 4 with GroupRef

use of org.voltdb.catalog.GroupRef in project voltdb by VoltDB.

the class ReportMaker method generateProcedureRow.

static String generateProcedureRow(CatalogMap<Table> tables, Procedure procedure) {
    StringBuilder sb = new StringBuilder();
    sb.append("<tr class='primaryrow'>");
    // column 1: procedure name
    String anchor = procedure.getTypeName().toLowerCase();
    sb.append("<td style='white-space: nowrap'><i id='p-" + anchor + "--icon' class='icon-chevron-right'></i> <a href='#p-");
    sb.append(anchor).append("' id='p-").append(anchor).append("' class='togglex'>");
    sb.append(procedure.getTypeName());
    sb.append("</a></td>");
    // column 2: parameter types
    sb.append("<td>");
    List<ProcParameter> params = CatalogUtil.getSortedCatalogItems(procedure.getParameters(), "index");
    List<String> paramTypes = new ArrayList<String>();
    for (ProcParameter param : params) {
        String paramType = VoltType.get((byte) param.getType()).name();
        if (param.getIsarray()) {
            paramType += "[]";
        }
        paramTypes.add(paramType);
    }
    if (paramTypes.size() == 0) {
        sb.append("<i>None</i>");
    }
    sb.append(StringUtils.join(paramTypes, ", "));
    sb.append("</td>");
    // column 3: partitioning
    sb.append("<td>");
    if (procedure.getSinglepartition()) {
        tag(sb, "success", "Single");
    } else {
        tag(sb, "warning", "Multi");
    }
    sb.append("</td>");
    // column 4: read/write
    sb.append("<td>");
    if (procedure.getReadonly()) {
        tag(sb, "success", "Read");
    } else {
        tag(sb, "warning", "Write");
    }
    sb.append("</td>");
    // column 5: access
    sb.append("<td>");
    List<String> groupNames = new ArrayList<String>();
    for (GroupRef groupRef : procedure.getAuthgroups()) {
        groupNames.add(groupRef.getGroup().getTypeName());
    }
    if (groupNames.size() == 0) {
        sb.append("<i>None</i>");
    }
    sb.append(StringUtils.join(groupNames, ", "));
    sb.append("</td>");
    // column 6: attributes
    sb.append("<td>");
    if (procedure.getHasjava()) {
        tag(sb, "info", "Java");
    } else {
        tag(sb, null, "Single-Stmt");
    }
    boolean isND = false;
    int scanCount = 0;
    for (Statement stmt : procedure.getStatements()) {
        scanCount += stmt.getSeqscancount();
        if (!stmt.getIscontentdeterministic() || !stmt.getIsorderdeterministic()) {
            isND = false;
        }
    }
    if (isND) {
        tag(sb, "inverse", "Determinism");
    }
    if (scanCount > 0) {
        tag(sb, "important", "Scans");
    }
    sb.append("</td>");
    sb.append("</tr>\n");
    // BUILD THE DROPDOWN FOR THE STATEMENT/DETAIL TABLE
    sb.append("<tr class='tablesorter-childRow'><td class='invert' colspan='6' id='p-" + procedure.getTypeName().toLowerCase() + "--dropdown'>\n");
    // output partitioning parameter info
    if (procedure.getSinglepartition()) {
        String pTable = procedure.getPartitioncolumn().getParent().getTypeName();
        String pColumn = procedure.getPartitioncolumn().getTypeName();
        int pIndex = procedure.getPartitionparameter();
        sb.append(String.format("<p>Partitioned on parameter %d which maps to column %s" + " of table <a class='invert' href='#s-%s'>%s</a>.</p>", pIndex, pColumn, pTable, pTable));
    }
    // get the annotation or ensure it's there
    ProcedureAnnotation annotation = (ProcedureAnnotation) procedure.getAnnotation();
    if (annotation == null) {
        annotation = new ProcedureAnnotation();
        procedure.setAnnotation(annotation);
    }
    // this needs to be run before the ProcedureAnnotation is used below
    // because it modifies it
    String statementsTable = generateStatementsTable(tables, procedure);
    // output what schema this interacts with
    // make sure tables appear in only one category
    annotation.tablesRead.removeAll(annotation.tablesUpdated);
    if (annotation.tablesRead.size() > 0) {
        sb.append("<p>Read-only access to tables: ");
        List<String> tableList = new ArrayList<String>();
        for (Table table : annotation.tablesRead) {
            tableList.add("<a href='#s-" + table.getTypeName() + "'>" + table.getTypeName() + "</a>");
        }
        sb.append(StringUtils.join(tableList, ", "));
        sb.append("</p>");
    }
    if (annotation.tablesUpdated.size() > 0) {
        sb.append("<p>Read/Write access to tables: ");
        List<String> tableList = new ArrayList<String>();
        for (Table table : annotation.tablesUpdated) {
            tableList.add("<a href='#s-" + table.getTypeName() + "'>" + table.getTypeName() + "</a>");
        }
        sb.append(StringUtils.join(tableList, ", "));
        sb.append("</p>");
    }
    if (annotation.indexesUsed.size() > 0) {
        sb.append("<p>Uses indexes: ");
        List<String> indexes = new ArrayList<String>();
        for (Index index : annotation.indexesUsed) {
            Table table = (Table) index.getParent();
            indexes.add("<a href='#s-" + table.getTypeName() + "-" + index.getTypeName() + "'>" + index.getTypeName() + "</a>");
        }
        sb.append(StringUtils.join(indexes, ", "));
        sb.append("</p>");
    }
    sb.append(statementsTable);
    sb.append("</td></tr>\n");
    return sb.toString();
}
Also used : Table(org.voltdb.catalog.Table) Statement(org.voltdb.catalog.Statement) ArrayList(java.util.ArrayList) Index(org.voltdb.catalog.Index) Constraint(org.voltdb.catalog.Constraint) GroupRef(org.voltdb.catalog.GroupRef) ProcParameter(org.voltdb.catalog.ProcParameter)

Example 5 with GroupRef

use of org.voltdb.catalog.GroupRef in project voltdb by VoltDB.

the class CatalogUtil method setUsersInfo.

/**
     * Set user info in the catalog.
     * @param catalog The catalog to be updated.
     * @param users A reference to the <users> element of the deployment.xml file.
     * @throws RuntimeException when there is an user with invalid masked password.
     */
private static void setUsersInfo(Catalog catalog, UsersType users) throws RuntimeException {
    if (users == null) {
        return;
    }
    // The database name is not available in deployment.xml (it is defined
    // in project.xml). However, it must always be named "database", so
    // I've temporarily hardcoded it here until a more robust solution is
    // available.
    Database db = catalog.getClusters().get("cluster").getDatabases().get("database");
    SecureRandom sr = new SecureRandom();
    for (UsersType.User user : users.getUser()) {
        Set<String> roles = extractUserRoles(user);
        String sha1hex = user.getPassword();
        String sha256hex = user.getPassword();
        if (user.isPlaintext()) {
            sha1hex = extractPassword(user.getPassword(), ClientAuthScheme.HASH_SHA1);
            sha256hex = extractPassword(user.getPassword(), ClientAuthScheme.HASH_SHA256);
        } else if (user.getPassword().length() == 104) {
            int sha1len = ClientAuthScheme.getHexencodedDigestLength(ClientAuthScheme.HASH_SHA1);
            sha1hex = sha1hex.substring(0, sha1len);
            sha256hex = sha256hex.substring(sha1len);
        } else {
            // if one user has invalid password, give a warn.
            hostLog.warn("User \"" + user.getName() + "\" has invalid masked password in deployment file.");
            // throw exception disable user with invalid masked password
            throw new RuntimeException("User \"" + user.getName() + "\" has invalid masked password in deployment file");
        }
        org.voltdb.catalog.User catUser = db.getUsers().add(user.getName());
        // generate salt only once for sha1 and sha256
        String saltGen = BCrypt.gensalt(BCrypt.GENSALT_DEFAULT_LOG2_ROUNDS, sr);
        String hashedPW = BCrypt.hashpw(sha1hex, saltGen);
        String hashedPW256 = BCrypt.hashpw(sha256hex, saltGen);
        catUser.setShadowpassword(hashedPW);
        catUser.setSha256shadowpassword(hashedPW256);
        // process the @groups and @roles comma separated list
        for (final String role : roles) {
            final Group catalogGroup = db.getGroups().get(role);
            // if the role doesn't exist, ignore it.
            if (catalogGroup != null) {
                final GroupRef groupRef = catUser.getGroups().add(role);
                groupRef.setGroup(catalogGroup);
            } else {
                hostLog.warn("User \"" + user.getName() + "\" is assigned to non-existent role \"" + role + "\" " + "and may not have the expected database permissions.");
            }
        }
    }
}
Also used : Group(org.voltdb.catalog.Group) Database(org.voltdb.catalog.Database) UsersType(org.voltdb.compiler.deploymentfile.UsersType) SecureRandom(java.security.SecureRandom) GroupRef(org.voltdb.catalog.GroupRef)

Aggregations

GroupRef (org.voltdb.catalog.GroupRef)7 Group (org.voltdb.catalog.Group)3 ProcParameter (org.voltdb.catalog.ProcParameter)3 Procedure (org.voltdb.catalog.Procedure)3 Statement (org.voltdb.catalog.Statement)3 ProcInfoData (org.voltdb.ProcInfoData)2 VoltNonTransactionalProcedure (org.voltdb.VoltNonTransactionalProcedure)2 VoltProcedure (org.voltdb.VoltProcedure)2 VoltTable (org.voltdb.VoltTable)2 VoltTypeException (org.voltdb.VoltTypeException)2 Database (org.voltdb.catalog.Database)2 VoltCompilerException (org.voltdb.compiler.VoltCompiler.VoltCompilerException)2 ProcedureAnnotation (org.voltdb.compilereport.ProcedureAnnotation)2 AbstractExpression (org.voltdb.expressions.AbstractExpression)2 ParameterValueExpression (org.voltdb.expressions.ParameterValueExpression)2 StatementPartitioning (org.voltdb.planner.StatementPartitioning)2 ImmutableMap (com.google_voltpatches.common.collect.ImmutableMap)1 Method (java.lang.reflect.Method)1 SecureRandom (java.security.SecureRandom)1 ArrayList (java.util.ArrayList)1