text:
package com.thinkbiganalytics.kylo.catalog.rest.controller;

import com.thinkbiganalytics.discovery.model.CatalogTableSchema;
import com.thinkbiganalytics.exceptions.ExceptionTransformer;
import com.thinkbiganalytics.feedmgr.security.FeedServicesAccessControl;
import com.thinkbiganalytics.feedmgr.service.security.SecurityService;
import com.thinkbiganalytics.hive.exceptions.ThriftConnectionException;
import com.thinkbiganalytics.hive.util.HiveUtils;
import com.thinkbiganalytics.kylo.catalog.CatalogException;
import com.thinkbiganalytics.kylo.catalog.ConnectorPluginManager;
import com.thinkbiganalytics.kylo.catalog.credential.api.DataSourceCredentialManager;
import com.thinkbiganalytics.kylo.catalog.dataset.DataSetUtil;
import com.thinkbiganalytics.kylo.catalog.datasource.DataSourceUtil;
import com.thinkbiganalytics.kylo.catalog.datasource.PotentialControllerServiceConflictException;
import com.thinkbiganalytics.kylo.catalog.file.CatalogFileManager;
import com.thinkbiganalytics.kylo.catalog.rest.model.CatalogModelTransform;
import com.thinkbiganalytics.kylo.catalog.rest.model.ConnectorTab;
import com.thinkbiganalytics.kylo.catalog.rest.model.CreateDataSourceEntity;
import com.thinkbiganalytics.kylo.catalog.rest.model.DataSet;
import com.thinkbiganalytics.kylo.catalog.rest.model.DataSetFile;
import com.thinkbiganalytics.kylo.catalog.rest.model.DataSetTable;
import com.thinkbiganalytics.kylo.catalog.rest.model.DataSetWithTableSchema;
import com.thinkbiganalytics.kylo.catalog.rest.model.DataSource;
import com.thinkbiganalytics.kylo.catalog.rest.model.DataSourceCredentials;
import com.thinkbiganalytics.kylo.catalog.rest.model.DefaultDataSetTemplate;
import com.thinkbiganalytics.kylo.catalog.spi.ConnectorPlugin;
import com.thinkbiganalytics.kylo.catalog.table.CatalogTableManager;
import com.thinkbiganalytics.metadata.api.MetadataAccess;
import com.thinkbiganalytics.metadata.api.catalog.DataSourceAlreadyExistsException;
import com.thinkbiganalytics.metadata.api.catalog.DataSourceProvider;
import com.thinkbiganalytics.rest.model.RestResponseStatus;
import com.thinkbiganalytics.rest.model.search.SearchResult;
import com.thinkbiganalytics.rest.model.search.SearchResultImpl;
import com.thinkbiganalytics.security.AccessController;
import com.thinkbiganalytics.security.context.SecurityContextUtil;
import com.thinkbiganalytics.security.rest.controller.SecurityModelTransform;
import com.thinkbiganalytics.security.rest.model.ActionGroup;
import com.thinkbiganalytics.security.rest.model.RoleMembershipChange;

import org.apache.commons.lang3.StringUtils;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;

import java.nio.file.AccessDeniedException;
import java.security.Principal;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;

@Component
@Api(tags = "Feed Manager - Catalog", produces = "application/json")
@Path(DataSourceController.BASE)
@Produces(MediaType.APPLICATION_JSON)
@SuppressWarnings("StringConcatenationArgumentToLogCall")  // SLF4J-334: Exceptions not logged with XLogger
public class DataSourceController extends AbstractCatalogController {

    private static final XLogger log = XLoggerFactory.getXLogger(DataSourceController.class);

    public static final String BASE = "/v1/catalog/datasource";

    public static final String PLUGIN_ID = "/v1/catalog/datasource/plugin-id";

    public enum CredentialMode {NONE, EMBED, ATTACH}

    static final ExceptionTransformer<ThriftConnectionException> exceptionTransformer =
        new ExceptionTransformer<>(
            ThriftConnectionException.class, SQLException.class, TTransportException.class);

    @Inject
    private AccessController accessController;

    @Inject
    private DataSourceProvider dataSourceProvider;

    @Inject
    private SecurityService securityService;

    @Inject
    private SecurityModelTransform securityTransform;

    @Inject
    private CatalogModelTransform modelTransform;

    @Inject
    private CatalogFileManager fileManager;

    @Inject
    private MetadataAccess metadataService;

    @Inject
    private CatalogTableManager tableManager;

    @Inject
    private DataSourceCredentialManager credentialManager;

    @Inject
    private ConnectorPluginController pluginController;

    @Inject
    private ConnectorPluginManager pluginManager;

    @Inject
    private DataSetController dataSetController;

    @Inject
    private com.thinkbiganalytics.kylo.catalog.dataset.DataSetProvider dataSetService;

    @Inject
    private com.thinkbiganalytics.kylo.catalog.datasource.DataSourceProvider dataSourceService;

    @POST
    @ApiOperation("Create a new data source")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Data source created", response = DataSource.class),
                      @ApiResponse(code = 400, message = "Invalid connector", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Internal server error", response = RestResponseStatus.class)
                  })
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createDataSource(@Nonnull final CreateDataSourceEntity source) {
        log.entry(source);
        final DataSource dataSource;
        try {
            // TODO: Remove this check for the ID and force updates to use the PUT to updateDataSource() for a more typical REST API
            if (StringUtils.isNotEmpty(source.getId())) {
                return updateDataSource(source);
            } else {
                try {
                    dataSource = dataSourceService.createDataSource(source, source.isDetectSimilarNiFiControllerServices());
                } catch (final CatalogException e) {
                    if (log.isDebugEnabled()) {
                        log.debug("Cannot create data source from request: " + source, e);
                    }
                    throw new BadRequestException(getMessage(e));
                }
            }
        }catch(PotentialControllerServiceConflictException e){
            throw new WebApplicationException(Response.status(Status.CONFLICT).entity(e.getControllerServiceConflictEntity()).build());
        }catch(DataSourceAlreadyExistsException e){
            throw new BadRequestException(e.getMessage());
        }

        return Response.ok(log.exit(dataSource)).build();
    }

    @POST
    @ApiOperation("Tests datasource")
    @ApiResponses({
                      @ApiResponse(code = 204, message = "Data source test successful", response = DataSource.class),
                      @ApiResponse(code = 400, message = "Invalid datasource", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Internal server error", response = RestResponseStatus.class)
                  })
    @Consumes(MediaType.APPLICATION_JSON)
    @Path("/test")
    public Response testDataSource(@Nonnull final DataSource dataSource) {
        log.entry(dataSource);

        String pluginId = dataSource.getConnector().getPluginId();
        Optional<ConnectorPlugin> plugin = this.pluginManager.getPlugin(pluginId);
        if (!plugin.isPresent()) {
            throw new BadRequestException(getMessage("catalog.datasource.testDataSource.connectorPluginNotFound", pluginId));
        }
        List<ConnectorTab> tabs = plugin.get().getDescriptor().getTabs();
        if (tabs == null) {
            throw new BadRequestException(getMessage("catalog.datasource.testDataSource.testNotAvailableForPlugin", pluginId));
        }
        ConnectorTab connectorTab = tabs.get(0);
        String sref = connectorTab.getSref();
        DataSource decrypted = modelTransform.decryptOptions(dataSource);

        if (".browse".equals(sref)) {
            doListFiles(DataSourceUtil.getPaths(decrypted).orElseThrow(IllegalStateException::new).get(0), decrypted);
        } else if (".connection".equals(sref)) {
            doListTables(null, null, decrypted);
        } else {
            throw new BadRequestException(getMessage("catalog.datasource.testDataSource.testNotAvailableForTab", sref));
        }

        log.exit();
        return Response.noContent().build();
    }

    @GET
    @ApiOperation("Gets the specified data source")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the data source", response = DataSource.class),
                      @ApiResponse(code = 404, message = "Data source was not found", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Internal server error", response = RestResponseStatus.class)
                  })
    @Path("{id}")
    public Response getDataSource(@PathParam("id") final String dataSourceId,
                                  @QueryParam("credentials") @DefaultValue("embed") final String credentialMode) {  // TODO Change default to be "none"
        log.entry(dataSourceId);
        CredentialMode mode;
        final boolean encryptCredentials = true;

        try {
            mode = CredentialMode.valueOf(credentialMode.toUpperCase());
        } catch (IllegalArgumentException e) {
            return Response.status(log.exit(Status.BAD_REQUEST)).entity(getMessage("catalog.datasource.credential.arg.invalid", credentialMode)).build();
        }

        final Set<Principal> principals = SecurityContextUtil.getCurrentPrincipals();
        DataSource dataSource = findDataSource(dataSourceId, encryptCredentials);  // TODO remove encrypt flag when real credential manager is in place

        switch (mode) {
            case NONE:
                dataSource = this.credentialManager.applyPlaceholders(dataSource, principals);
                break;
            case EMBED:
                dataSource = this.credentialManager.applyCredentials(dataSource, principals);
                break;
            case ATTACH:
                Map<String, String> credProps = this.credentialManager.getCredentials(dataSource, encryptCredentials, principals);
                DataSourceCredentials creds = new DataSourceCredentials(credProps, encryptCredentials);
                dataSource = this.credentialManager.applyPlaceholders(dataSource, principals);
                dataSource.setCredentials(creds);
        }

        return Response.ok(log.exit(dataSource)).build();
    }

    @PUT
    @ApiOperation("Updates an existing data source")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Data source updated", response = DataSource.class),
                      @ApiResponse(code = 400, message = "Invalid connector", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Internal server error", response = RestResponseStatus.class)
                  })
    @Path("{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response updateDataSource(@Nonnull final DataSource source) {
        log.entry(source);

        final DataSource dataSource;
        try {
            dataSource = dataSourceService.updateDataSource(source);
        } catch (final CatalogException e) {
            if (log.isDebugEnabled()) {
                log.debug("Cannot create data source from request: " + source, e);
            }
            throw new BadRequestException(getMessage(e));
        }

        return Response.ok(log.exit(dataSource)).build();
    }

    @DELETE
    @ApiOperation("Deletes the specified data source")
    @ApiResponses({
                      @ApiResponse(code = 204, message = "Data source deleted successfully", response = DataSource.class),
                      @ApiResponse(code = 404, message = "Data source was not found", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Internal server error", response = RestResponseStatus.class)
                  })
    @Path("{id}")
    public Response deleteDataSource(@PathParam("id") final String dataSourceId) {
        log.entry(dataSourceId);

        metadataService.commit(() -> {
            final com.thinkbiganalytics.metadata.api.catalog.DataSource.ID domainId = dataSourceProvider.resolveId(dataSourceId);
            final com.thinkbiganalytics.metadata.api.catalog.DataSource dataSource = dataSourceProvider.find(domainId).orElse(null);
            if (dataSource != null) {
                if (dataSource.getDataSets().stream().anyMatch(dataSet -> !dataSet.getFeedSources().isEmpty() || !dataSet.getFeedTargets().isEmpty())) {
                    throw new WebApplicationException(Response.status(Status.CONFLICT).build());
                } else {
                    dataSourceProvider.deleteById(domainId);
                }
            }
        });

        log.exit();
        return Response.noContent().build();
    }

    @GET
    @ApiOperation("Lists all data sources")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the data sources", response = SearchResult.class),
                      @ApiResponse(code = 400, message = "", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Internal server error", response = RestResponseStatus.class)
                  })
    public Response getDataSources(@QueryParam("connector") final String connectorId,
                                   @QueryParam("filter") final String filter,
                                   @QueryParam("limit") final Integer limit,
                                   @QueryParam("start") final Integer start) {
        log.entry(connectorId, filter, limit, start);

        final boolean encryptCredentials = true;

        // Validate parameters
        if (start != null && start < 0) {
            throw new BadRequestException(getMessage("catalog.datasource.getDataSources.invalidStart"));
        }
        if (limit != null && limit < 1) {
            throw new BadRequestException(getMessage("catalog.datasource.getDataSources.invalidLimit"));
        }

        // Fetch page
        final PageRequest pageRequest = new PageRequest((start != null) ? start : 0, (limit != null) ? limit : Integer.MAX_VALUE);

        return metadataService.read(() -> {
            // Require admin permission if the results should include unencrypted credentials.
            accessController.checkPermission(AccessController.SERVICES, encryptCredentials ? FeedServicesAccessControl.ACCESS_DATASOURCES : FeedServicesAccessControl.ADMIN_DATASOURCES);

            Page<com.thinkbiganalytics.metadata.api.catalog.DataSource> domainPage = dataSourceProvider.findPage(pageRequest, filter);
            final Page<DataSource> page = domainPage.map(modelTransform.convertDataSourceToRestModel(true, encryptCredentials));

            // Return results
            final SearchResult<DataSource> searchResult = new SearchResultImpl<>();
            searchResult.setData(page.getContent());
            searchResult.setRecordsTotal(page.getTotalElements());
            return Response.ok(searchResult).build();
        });
    }

    @GET
    @ApiOperation("Lists all datasources matching the plugin id")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the data sources", response = DataSource.class),
                      @ApiResponse(code = 400, message = "", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Internal server error", response = RestResponseStatus.class)
                  })
    @Path("/plugin-id")
    public Response getDataSources(@QueryParam("pluginIds") final String pluginIds) {
        log.entry(pluginIds);
        final boolean encryptCredentials = true;
        List<String> pluginList = Arrays.asList(pluginIds.split(",")).stream().map(id -> id.trim()).collect(Collectors.toList());
        final Set<DataSource> datasources = metadataService.read(() -> {
            // Require admin permission if the results should include unencrypted credentials.
            accessController.checkPermission(AccessController.SERVICES, encryptCredentials ? FeedServicesAccessControl.ACCESS_DATASOURCES : FeedServicesAccessControl.ADMIN_DATASOURCES);

            return dataSourceProvider.findAll().stream().filter(ds -> pluginList.contains(ds.getConnector().getPluginId()))
                .map(modelTransform.dataSourceToRestModel(true, encryptCredentials))
                .collect(Collectors.toSet());
        });
        return Response.ok(datasources).build();
    }

    @GET
    @Path("{id}/files")
    @ApiOperation("List files of a data source")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "List of files in path", response = DataSetFile.class, responseContainer = "List"),
                      @ApiResponse(code = 400, message = "A path is not valid", response = RestResponseStatus.class),
                      @ApiResponse(code = 403, message = "Access to the path is restricted", response = RestResponseStatus.class),
                      @ApiResponse(code = 404, message = "Data source does not exist", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Failed to list files", response = RestResponseStatus.class)
                  })
    public Response listFiles(@PathParam("id") final String dataSourceId, @QueryParam("path") final String path) {
        log.entry(dataSourceId, path);

        return metadataService.read(() -> {
            // List files at path
            final DataSource dataSource = findDataSource(dataSourceId, false);
            return Response.ok(log.exit(doListFiles(path, dataSource))).build();
        });
    }

    private List<DataSetFile> doListFiles(@QueryParam("path") String path, DataSource dataSource) {
        final List<DataSetFile> files;

        try {
            log.debug("Listing files at path: {}", path);
            files = fileManager.listFiles(path, dataSource);
        } catch (final AccessDeniedException e) {
            log.debug("Access denied accessing path: {}: {}", path, e, e);
            throw new ForbiddenException(getMessage("catalog.datasource.listFiles.forbidden", path));
        } catch (final CatalogException e) {
            log.debug("Catalog exception when accessing path: {}: {}", path, e, e);
            throw new BadRequestException(getMessage(e));
        } catch (final Exception e) {
            if (exceptionTransformer.causesInChain(e)) {
                throw new ThriftConnectionException(e);
            }

            if (log.isErrorEnabled()) {
                log.error("Failed to list data source files at path " + path + ": " + e, e);
            }
            final RestResponseStatus status = new RestResponseStatus.ResponseStatusBuilder()
                .message(getMessage("catalog.datasource.listFiles.error", path))
                .url(request.getRequestURI())
                .setDeveloperMessage(e)
                .buildError();
            throw new InternalServerErrorException(Response.serverError().entity(status).build());
        }
        return files;
    }

    @GET
    @Path("{id}/tables")
    @ApiOperation("List tables in a data source")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "List of tables", response = DataSetTable.class, responseContainer = "List"),
                      @ApiResponse(code = 404, message = "Data source does not exist", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Failed to list tables", response = RestResponseStatus.class)
                  })
    public Response listTables(@PathParam("id") final String dataSourceId,
                               @QueryParam("catalog") final String catalogName,
                               @QueryParam("schema") final String schemaName) {
        log.entry(dataSourceId, catalogName, schemaName);
        //read as the user to see if they can access the datasource
        boolean hasAccess = metadataService.read(() -> {
            return findDataSource(dataSourceId, true) != null;
        });

        if (hasAccess) {
            return metadataService.read(() -> {
                // List tables
                final DataSource dataSource = findDataSource(dataSourceId, false);
                final List<DataSetTable> tables = doListTables(catalogName, schemaName, dataSource);

                return Response.ok(log.exit(tables)).build();
            }, MetadataAccess.SERVICE);
        } else {
            log.debug("Access denied accessing datasource : {}", dataSourceId);
            throw new ForbiddenException(getMessage("catalog.datasource.forbidden"));
        }
    }

    private List<DataSetTable> doListTables(@QueryParam("catalog") String catalogName, @QueryParam("schema") String schemaName, DataSource dataSource) {
        final List<DataSetTable> tables;

        try {
            log.debug("List tables for catalog:{} schema:{}", catalogName, schemaName);
            tables = tableManager.listCatalogsOrTables(dataSource, catalogName, schemaName);
        } catch (final Exception e) {
            if (exceptionTransformer.causesInChain(e)) {
                throw new ThriftConnectionException(e);
            }

            if (log.isErrorEnabled()) {
                log.error("Failed to list tables for catalog [" + catalogName + "] schema [" + schemaName + "]: " + e, e);
            }
            final RestResponseStatus status = new RestResponseStatus.ResponseStatusBuilder()
                .message(getMessage("catalog.datasource.listTables.error", catalogName, schemaName))
                .url(request.getRequestURI())
                .setDeveloperMessage(e)
                .buildError();
            throw new InternalServerErrorException(Response.serverError().entity(status).build());
        }
        return tables;
    }

    @GET
    @Path("{id}/tables/filter")
    @ApiOperation("List all tables in a data source")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "List of tables", response = DataSetTable.class, responseContainer = "List"),
                      @ApiResponse(code = 404, message = "Data source does not exist", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Failed to list tables", response = RestResponseStatus.class)
                  })
    public Response listTables(@PathParam("id") final String dataSourceId, @QueryParam("filter") String catalogOrSchemaName) {

        List<DataSetTable> tableList = new ArrayList<>();
        //read as the user to see if they can access the datasource
        boolean hasAccess = metadataService.read(() -> {
            return findDataSource(dataSourceId, true) != null;
        });

        if (hasAccess) {

            tableList = metadataService.read(() -> {
                // List tables
                final DataSource dataSource = findDataSource(dataSourceId, false);

                final List<DataSetTable> tables;
                try {
                    log.debug("List tables for catalogOrSchema:{}", catalogOrSchemaName);
                    tables = tableManager.listTables(dataSource, catalogOrSchemaName);
                } catch (final Exception e) {
                    if (exceptionTransformer.causesInChain(e)) {
                        throw new ThriftConnectionException(e);
                    }

                    if (log.isErrorEnabled()) {
                        log.error("Failed to list tables for [" + catalogOrSchemaName + "] : " + e, e);
                    }
                    final RestResponseStatus status = new RestResponseStatus.ResponseStatusBuilder()
                        .message(getMessage("catalog.datasource.listTablesFilter.error", catalogOrSchemaName))
                        .url(request.getRequestURI())
                        .setDeveloperMessage(e)
                        .buildError();
                    throw new InternalServerErrorException(Response.serverError().entity(status).build());
                }
                return tables;

            }, MetadataAccess.SERVICE);
        }

        return Response.ok(log.exit(tableList)).build();
    }

    /**
     * Gets the schema of the specified table using the specified data source.
     *
     * @param dataSourceId the data source id
     * @param tableName    the table name
     * @param schema       the schema name, or {@code null} to search all schemas
     * @return the table and field details
     */
    @POST
    @Path("{id}/tables/{tableName}/dataset")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Gets the schema of the specified table.", notes = "Connects to the database specified by the data source.")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the table schema.", response = DataSetWithTableSchema.class),
                      @ApiResponse(code = 403, message = "Access denied.", response = RestResponseStatus.class),
                      @ApiResponse(code = 404, message = "A JDBC data source with that id does not exist.", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "NiFi or the database are unavailable.", response = RestResponseStatus.class)
                  })
    public Response createJdbcTableDataSet(@PathParam("id") final String dataSourceId,
                                           @PathParam("tableName") final String tableName,
                                           @QueryParam("schema") final String schema) {
        // TODO Verify user has access to data source
        // Require admin permission if the results should include unencrypted credentials.
        final boolean encryptCredentials = true;

        accessController.checkPermission(AccessController.SERVICES, encryptCredentials ? FeedServicesAccessControl.ACCESS_DATASOURCES : FeedServicesAccessControl.ADMIN_DATASOURCES);
        DataSetWithTableSchema dataSetWithTableSchema = null;
        boolean hasAccess = false;
        try {
            //ensure the user can read teh datasource

            DataSource encryptedSource = metadataService.read(() -> {
                return findDataSource(dataSourceId, true);
            });
            hasAccess = encryptedSource != null;

            if (hasAccess) {
                //fetch the datasource as a service account to get the creds.
                DataSource dataSource = metadataService.read(() -> {
                    return findDataSource(dataSourceId, false);
                }, MetadataAccess.SERVICE);

                //describe the datasource and table

                CatalogTableSchema tableSchema = tableManager.describeTable(dataSource, schema, tableName);

                //create the dataset as a service account.  if we get here the user has access to view the data source

                // dataSetWithTableSchema = metadataService.commit(() -> {
                // List tables

                if (tableSchema != null) {
                    DataSet dataSet = new DataSet();
                    //assign the datasource to this dataset with encrypted credentials
                    dataSet.setDataSource(encryptedSource);
                    String fullTableName = (tableSchema.getTable() != null)
                                           ? tableSchema.getTable().getQualifiedIdentifier()
                                           : HiveUtils.quoteIdentifier(tableSchema.getSchemaName(), tableSchema.getName());
                    dataSet.setTitle(tableSchema.getSchemaName() + "." + tableSchema.getName());

                    DefaultDataSetTemplate defaultDataSetTemplate = DataSetUtil.mergeTemplates(dataSet);
                    List<String> paths = defaultDataSetTemplate.getPaths();
                    String format = defaultDataSetTemplate.getFormat();

                    Map<String, String> options = defaultDataSetTemplate.getOptions();
                    if (options == null) {
                        options = new HashMap<>();
                    }
                    if ("hive".equalsIgnoreCase(format.toLowerCase())) {
                        if (paths == null) {
                            paths = new ArrayList<>();
                        }
                        paths.add(fullTableName);
                    }
                    options.put("dbtable", fullTableName);

                    dataSet.setFormat(format);
                    dataSet.setPaths(paths);
                    dataSet.setOptions(options);

                    DataSet dataSet1 = dataSetService.findOrCreateDataSet(dataSet, encryptCredentials);
                    dataSetWithTableSchema = new DataSetWithTableSchema(dataSet1, tableSchema);
                } else {
                    if (log.isErrorEnabled()) {
                        log.error("Failed to describe tables for schema [" + schema + "], table [" + tableName + "], dataSource [" + dataSourceId + "] ");
                    }
                    final RestResponseStatus status = new RestResponseStatus.ResponseStatusBuilder()
                        .message(getMessage("catalog.datasource.describeTable.error", tableName, schema))
                        .url(request.getRequestURI())
                        .buildError();
                    throw new InternalServerErrorException(Response.serverError().entity(status).build());
                }

                // });
            } else {
                //no acceess
                final RestResponseStatus status = new RestResponseStatus.ResponseStatusBuilder()
                    .message(getMessage("catalog.datasource.forbidden"))
                    .url(request.getRequestURI())
                    .buildError();
                throw new InternalServerErrorException(Response.serverError().entity(status).build());
            }
        } catch (Exception e) {
            if (exceptionTransformer.causesInChain(e)) {
                throw new ThriftConnectionException(e);
            }

            final RestResponseStatus status = new RestResponseStatus.ResponseStatusBuilder()
                .message(getMessage("catalog.datasource.describeTable.error", tableName, schema))
                .url(request.getRequestURI())
                .buildError();
            throw new InternalServerErrorException(Response.serverError().entity(status).build());
        }

        return Response.ok(dataSetWithTableSchema).build();
    }

    @GET
    @Path("{id}/credentials")
    @ApiOperation("List credentials for a data source")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "List of credentials", response = DataSetTable.class, responseContainer = "List"),
                      @ApiResponse(code = 404, message = "Data source does not exist", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Failed to list credentials", response = RestResponseStatus.class)
                  })
    public Response listCredentials(@PathParam("id") final String dataSourceId) {
        final boolean encrypted = true;

        log.entry(dataSourceId, encrypted);
        log.debug("List tables for catalog:{} encrypted:{}", encrypted);

        try {
            final DataSource dataSource = findDataSource(dataSourceId, true);
            final Set<Principal> principals = SecurityContextUtil.getCurrentPrincipals();
            final Map<String, String> credProps = this.credentialManager.getCredentials(dataSource, encrypted, principals);
            DataSourceCredentials credentials = new DataSourceCredentials(credProps, encrypted);

            return Response.ok(log.exit(credentials)).build();
        } catch (final Exception e) {
            if (exceptionTransformer.causesInChain(e)) {
                throw new ThriftConnectionException(e);
            }

            log.error("Failed to retrieve credentials for datasource [{}] encrypted={}: " + e, dataSourceId, encrypted, e);

            final RestResponseStatus status = new RestResponseStatus.ResponseStatusBuilder()
                .message(getMessage("catalog.datasource.credentials.error", dataSourceId))
                .url(request.getRequestURI())
                .setDeveloperMessage(e)
                .buildError();
            throw new InternalServerErrorException(Response.serverError().entity(status).build());
        }
    }

    @GET
    @Path("{id}/plugin")
    @ApiOperation("Gets the plugin associates with the connector of the specified data source")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "The plugin associates with the data source's connector", response = DataSetTable.class, responseContainer = "List"),
                      @ApiResponse(code = 404, message = "Data source does not exist", response = RestResponseStatus.class),
                      @ApiResponse(code = 500, message = "Failed to obtain the plugin", response = RestResponseStatus.class)
                  })
    public Response getConnectorPlugin(@PathParam("id") final String dataSourceId) {
        log.entry(dataSourceId);

        final DataSource dataSource = findDataSource(dataSourceId, true);

        return log.exit(this.pluginController.getPlugin(dataSource.getConnector().getPluginId()));
    }

    @POST
    @Path("{id}/dataset")
    @ApiOperation("creates a new dataset for a datasource")
    public Response createDataSet(@PathParam("id") final String datasourceId) {
        log.entry(datasourceId);
        final DataSource dataSource = findDataSource(datasourceId, true);
        final DataSet dataSet = new DataSet();
        dataSet.setDataSource(dataSource);

        return dataSetController.createDataSet(dataSet);
    }

    /**
     * Gets the data source with the specified id.
     *
     * @throws NotFoundException if the data source does not exist
     */
    @Nonnull
    private DataSource findDataSource(@Nonnull final String id, boolean encryptCredentials) {
        return metadataService.read(() -> {
            // Require admin permission if the results should include unencrypted credentials.
            accessController.checkPermission(AccessController.SERVICES, encryptCredentials ? FeedServicesAccessControl.ACCESS_DATASOURCES : FeedServicesAccessControl.ADMIN_DATASOURCES);

            com.thinkbiganalytics.metadata.api.catalog.DataSource.ID dsId = dataSourceProvider.resolveId(id);
            return dataSourceProvider.find(dsId)
                .map(modelTransform.dataSourceToRestModel(true, encryptCredentials))
                .orElseThrow(() -> {
                    log.debug("Data source not found: {}", id);
                    return new NotFoundException(getMessage("catalog.datasource.notFound.id", id));
                });
        });
    }

    @GET
    @Path("{id}/actions/available")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Gets the list of available actions that may be permitted or revoked on a data source.")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the actions.", response = ActionGroup.class),
                      @ApiResponse(code = 404, message = "A data source with the given ID does not exist.", response = RestResponseStatus.class)
                  })
    public Response getAvailableActions(@PathParam("id") final String dataSourceIdStr) {
        log.debug("Get available actions for data source: {}", dataSourceIdStr);

        return this.securityService.getAvailableDataSourceActions(dataSourceIdStr)
            .map(g -> Response.ok(g).build())
            .orElseThrow(() -> new WebApplicationException("A data source with the given ID does not exist: " + dataSourceIdStr, Response.Status.NOT_FOUND));
    }

    @GET
    @Path("{id}/actions/allowed")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Gets the list of actions permitted for the given username and/or groups.")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the actions.", response = ActionGroup.class),
                      @ApiResponse(code = 404, message = "A data source with the given ID does not exist.", response = RestResponseStatus.class)
                  })
    public Response getAllowedActions(@PathParam("id") final String dataSourceIdStr, @QueryParam("user") final Set<String> userNames, @QueryParam("group") final Set<String> groupNames) {
        log.debug("Get allowed actions for data source: {}", dataSourceIdStr);

        Set<? extends Principal> users = Arrays.stream(this.securityTransform.asUserPrincipals(userNames)).collect(Collectors.toSet());
        Set<? extends Principal> groups = Arrays.stream(this.securityTransform.asGroupPrincipals(groupNames)).collect(Collectors.toSet());

        return this.securityService.getAllowedDataSourceActions(dataSourceIdStr, Stream.concat(users.stream(), groups.stream()).collect(Collectors.toSet()))
            .map(g -> Response.ok(g).build())
            .orElseThrow(() -> new WebApplicationException("A data source with the given ID does not exist: " + dataSourceIdStr, Response.Status.NOT_FOUND));
    }

    @GET
    @Path("{id}/roles")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Gets the list of assigned members the data source's roles")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "Returns the role memberships.", response = ActionGroup.class),
                      @ApiResponse(code = 404, message = "A data source with the given ID does not exist.", response = RestResponseStatus.class)
                  })
    public Response getRoleMemberships(@PathParam("id") final String dataSourceIdStr, @QueryParam("verbose") @DefaultValue("false") final boolean verbose) {
        return this.securityService.getDataSourceRoleMemberships(dataSourceIdStr)
            .map(m -> Response.ok(m).build())
            .orElseThrow(() -> new WebApplicationException("A data source with the given ID does not exist: " + dataSourceIdStr, Response.Status.NOT_FOUND));
    }

    @POST
    @Path("{id}/roles")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation("Updates the members of one of a data source's roles.")
    @ApiResponses({
                      @ApiResponse(code = 200, message = "The permissions were changed successfully.", response = ActionGroup.class),
                      @ApiResponse(code = 404, message = "No data source exists with the specified ID.", response = RestResponseStatus.class)
                  })
    public Response postPermissionsChange(@PathParam("id") final String dataSourceIdStr, final RoleMembershipChange changes) {
        return this.securityService.changeDataSourceRoleMemberships(dataSourceIdStr, changes)
            .map(m -> Response.ok(m).build())
            .orElseThrow(() -> new WebApplicationException("Either a data source with the ID \"" + dataSourceIdStr + "\" does not exist or it does not have a role named \""
                                                           + changes.getRoleName() + "\"", Response.Status.NOT_FOUND));
    }
}


text:
Browsing tag: Ninja Theory
Hellblade 2 Gets New Trailer for Next-Gen and It Looks Amazing News 0
News 0 At The Game Awards, Microsoft boss Phil Spencer went up on stage to introduce possibly the first game for its next-gen platform, and it's from newly acquired studio Ninja Theory, and it's none other than Hellblade 2! Note that sequel is targeted for Xbox Series X, or Microsoft's next-gen console. Given how we don't have […]
E3 2019: Bleediing Edge Announced, New 4v4 Multiplayer Game From Ninja Theory News 0
News 0 At today's Microsoft E3 press conference, Microsoft unveiled a new title from Ninja Theory (DmC, Enslaved, Hellblade), and it's oddly enough, a 4v4 multiplayer game titled "Bleeding Edge." The Bleeding Edge technical alpha starts later this month on June 27. No release date has been released so far.

redpajama_set_name: RedPajamaCommonCrawl


text:
NASA has pinpointed an area where astronauts could land on Mars. Ice is so accessible there that they could dig it up with a shovel.
Morgan McFall-Johnsen
A NASA image of Mars.
NASA via AP
NASA scientists discovered a vast region of Mars where water ice sits just an inch below the surface.
Because that area is not at the planet's poles or equator, it offers a promising place to land astronauts someday.
Landing near accessible stores of ice is crucial because astronauts on Mars would need a way to make drinking water and to create rocket fuel for the journey back to Earth.
NASA scientists just discovered a vast region of Mars where water ice sits just an inch below the surface.
It could be the perfect place for astronauts to land, since any crew that touches down on the red planet would have to mine resources there, and water is the most important one. Mars astronauts will need to dig up ice to make drinking water and to create rocket fuel for the journey back to Earth — when you break down water into oxygen and hydrogen, the latter can be used to make fuel.
"Bringing your own water from Earth would be incredibly expensive," Sylvain Piqueux, the NASA planetary scientist who led the research, told Business Insider. "Everything that you don't have to bring with you leaves more room for a science experiment or additional engineering capabilities."
'If you bring a rake or a shovel, you could access it'
The annotated area of Mars in this illustration holds near-surface water ice that would be easily accessible to astronauts.
NASA/JPL-Caltech
Piqueux and his team discovered the ice field when they examined data from NASA's Mars Reconnaissance Orbiter (MRO) and Mars Odyssey orbiter.
To their surprise, this ice appears to be as little as an inch below the planet's surface.
"If you bring a rake or a shovel, you could access it. You wouldn't need to bring heavy equipment to access it," Piqueux said. "In this case, the surprise was that ice was right there, so shallow."
Planning for a serious digging operation as part of any space mission requires heavy equipment, which means additional fuel would be needed for a launch. Plus, digging on Mars isn't easy, as the InSight lander's heat probe discovered when it started trying to do that in February. The so-called "mole" instrument is supposed to dig 16 feet down, but it only made a few inches of progress before getting stuck.
InSight's heat probe, or mole, backed about halfway out of the hole it had burrowed, October 26, 2019.
Then the mole mysteriously popped out of its hole in October. It's been unable to dig deeper ever since.
NASA found water ice in an unexpected place
Mars has lots of surface water ice at its poles. But those poles aren't a great place to send astronauts, since they're freezing cold and shrouded in darkness for half the year.
Mars' polar ice cap.
NASA/JPL-Caltech/MSSS
NASA doesn't want to land astronauts at a spot near the planet's equator, either, because it's too warm for ice to exist. Unlike Earth, Mars doesn't have a thick enough atmosphere to host liquid water. When ice gets too warm, the water skips the liquid stage altogether and simply evaporates.
But the subsurface ice that Piqueux discovered is in a temperate area that's just right for astronauts to land.
"Part of the excitement of this work is that we found ice in mid-latitudes," Piqueux said. "We knew that there was ice potentially at those latitudes from other instruments, but we also thought that it would be located much deeper."
Piqueux thinks this ice may have come from an ancient snowfall that was followed by a dust storm, which quickly blanketed the snow. That thin layer of dust could have preserved the frozen water for billions of years.
Now that NASA researchers have pinpointed this area with plentiful water resources, they can start investigating potential landing sites there. They'll search for a more specific place that's safe for humans to land and also offers new opportunities for studying Mars' surface and potential to host alien life.
"It's the beginning of putting together those parts of the puzzle for Mars," Piqueux said.
SEE ALSO: A stunning animation by a planetary scientist shows how huge our solar system is — and why that makes it so hard to depict
NOW WATCH: 6 scientists spent a year in a mock Mars habitat in Hawaii with no fresh water
More: Space NASA Mars Astronauts


 Creative Email Call-to-Action Ideas That Really Drive Action » Red Mango Marketing!
You've figured out what to say in your email, designed it to catch the eye of your reader, and sent it out.
You check your email marketing reports and… surprise! People are opening it!
But they aren't clicking. They aren't taking action.
You aren't driving them to where you want them to be.
It's time to think about changing your email's call-to-action.
There are many factors of a successful call-to-action. But if you already have the essentials down, sometimes all it takes is a simple tweak in wording to get your readers to click.
We've created this infographic to help you test out new ways to drive readers to your website, online survey, store, event and more.
Try testing one of these new calls-to-actions in your next email campaign. Then, pay attention to your reports and take note of any changes in your click through rate.
The more personable you can be in your emails, the more likely people are to click. So say goodbye to "click here" and hello to your shiny new click-through rate.
Ready to start driving action with your new CTA knowledge? Log in to your Constant Contact account now.
The post Creative Email Call-to-Action Ideas That Really Drive Action appeared first on Constant Contact Blogs.
This entry was posted on Wednesday, June 1st, 2016 at 11:01 am	and is filed under click-through rate, content, Email Marketing, News & Updates. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.


Late last night gov.uk launched – I urge you to have a look and give feedback to the team. It's a beta site but it is an indication of how much change is happening in central government. I started working on the way central govt uses websites in 2010 and with the help of Transform wrote a report calling for a revolution in how government thinks about the Internet. We tried to shift the emphasis onto us, the user, by asking for simpler, more open, cheaper and more beautiful website design.
After the establishment of the Government Digital Service and the installation of Mike Bracken as Executive Director of digital these ideas are now becoming a reality. There is a long way to go but it would be easy to underestimate the sea of change without knowing the amount of talent being built up and the plans ahead. Mike has recruited a band of warriors to help including Tom Loosemore, Mark o'Neil and Felicity Singleton and I am so proud of my small role in their work. Have a look at the cabinet office blog and please get involved – all of these services get better when we all help improve them.
The prize is big – massive savings, more people online and the UK in a world leading position.


text:
George Strait Announces A Show In Austin
Gwen Published: October 11, 2021
The King of Country Music just can't stay away from the stage and we aren't complaining. This morning George Strait just announced he would be playing a show in Austin on April 29th, at the Moody Center. This will be the grand opening of the venue. Special guests on the line will include Willie Nelson and Family along with Randy Rogers Band.
General Admission Tickets for this show will go on sale Friday October 22nd, at 10am. Tickets for George Strait Fan Club Members will go on sale Friday, October 15th at 10am. I don't know the prices of tickets yet. I will be able to tell you more in the morning. VIP Packages will also be available.
Here is what was what written on George Strait's social media to announce the show:
Let's take you back to Texas! George and his friends Willie Nelson & Family and Randy Rogers Band are playing a special grand opening show at the Moody Center ATX in Austin, Texas on Friday, April 29th. Tickets go on sale Oct. 22 at 10AM CT
George Strait just recently played the Austin City Limits music festival to a sold out crowd. He also announced earlier in the year that he would be making a historic return to the Houston Livestock Show and Rodeo on March 20th, by closing out the event. Joining him that night will be Ashley McBride. George Strait also has dates set in Las Vegas, at the T-Mobile Arena during the National Finals Rodeo December 3rd and 4th and February 11th and 12th.
George is no spring chicken so if you get the opportunity to see him, I wouldn't pass it up.
george-strait-god-and-country-music-new-song
."}" data-sheets-userformat="{"2":33555201,"3":{"1":0},"11":4,"12":0,"28":1}">
KEEP LOOKING: See Inside Jason Aldean's Spectacular Beach House Mansion:


The Gordon Davis Johnson and Shane P.C. (the Firm) Real Estate Section is diversified, and spans of a wide variety of transaction types, regions and countries.
The Real Estate Section has extensive expertise in advising clients through all stages of a real estate transaction, including the acquisition, financing, development, construction, leasing, management and sale of improved and unimproved real estate property. The Firm assists property owners in all aspects of the land development and land use entitlement process, including zoning, utility districts, tax abatement, and enterprise and foreign trade zones.
Additionally, the Section advises on the formation of joint ventures and partnerships in connection with the development of office, retail, industrial, hotel, resort and mixed-use projects.
The Firm's Tax Section's knowledge of tax-free exchanges and the acquisition and development of international properties complements the Real Estate Section's expertise.
The Firm represents both lenders and borrowers in the southwest region, including Texas, New Mexico and Mexico, in the structuring and documentation of complex real estate and asset-based financing transactions, including recapitalization financings, revolving credit loans, multiple option and competitive bid facilities, construction loans, shelter loans, and maquiladora loans.
The Firm strives to be an asset to each real estate transaction through the cultivation of an intimate understanding of the development process and real estate industry. The Firm is an active member of many trade organizations including CCIM, ICSC and IREM.
The Section handles contract negotiations for unimproved and improved, farm and ranch, and affordable housing properties. Throughout the purchase process, the Section conducts due diligence to provide accurate advice on potential issues affecting the client's property. The Section also handles closings and 1031 exchanges.
The Section advises clients on the legal aspects of leasing property including retail, office, industrial and hospitality structures. The Section has an expertise in negotiating national tenant leases and shopping center development and management, including the negotiation of reciprocal easement agreements, tenant exclusives and restrictions for shopping centers.
The Section provides legal assistance to developers and investors starting new construction projects. In addition to easement planning, acquisition and enforcement, the Section advises on maintaining compliance with municipal regulations, planning for subdivisions and shopping centers and the development of tax incentive agreements. The Section has advised clients regarding the planning and feasibility of shopping centers, hospitality projects, apartments, town homes, office spaces, retail spaces and more.
With an understanding of local and regional markets, the Section can provide legal advice regarding the financing of commercial real estate projects including loan opinion letters and seller financing options.
To protect clients and their interests, the Section advises on contract negotiations and performance benchmarks for construction projects.
As a vital resource to many industries, ownership, usage and control of water plays a crucial role in the growth and development of businesses and industries. When contemplating the purchase or sale of water rights, clients can obtain legal advice from the Section.


Buhaira Consulting is a real estate whith a tailor-made selection of the best properties for sale in Seville.
From penthouses with breathtaking views in the center of Seville to cozy manor houses in Carmona, we have a wide range of properties that fit your needs.
One of the most important luxury estates in Spain. In Sotogrande highlight its magnificent villas with sea or some of the best golf courses in the world. In the port are apartments and penthouses with stunning views of the marina.
Famous for its villages with wide white sand beaches surrounded by parks and marshes. On the coast of Cádiz is often sight the mountain from the beach, a union atypical feature that allows coastal properties enjoy unimaginable views.
Area of contrasts, with a variety of unique landscapes: rock and water, mountain and olive grove, livestock and wildlife. An ideal area to find recreational farms or hunting lodges or villas where privacy first.
Always looking for the best I found outstanding properties throughout Spain, from cosmopolitan and full of activity areas to charming places where lost and disconnected from the world.
Newly built villa with pool for sale in gated community with access control.
Luxury Duplex for rent in unique building of the Old Town.


text:
Digital Art Commissions
For further information please email, info@otdac.org.
Opening Times – Digital Art Commissions is a not-for-profit, digital art commissioning body and platform for artworks whose primary means of encounter is online.
Opening Times supports digital practices, experimental publishing and visual culture research through commissions and curated programmes.
Paul Flannery
Tim Steer
Founded in 2014 by:
Rhys Coren
Dave Hoyland
Rees & Company
We've not published anything here yet. Try searching for something else.
Emails do not match.
Patrick GoddardDirty Deeds Done Dirt CheapCommission2017
http://dirtydeedsdonecheap.otdac.org/
For his Opening Times commission, Patrick Goddard has produced Dirty Deeds Done Dirt Cheap, a short video including spoken and musical elements that looks at the abstracting tendencies of contemporary high finance; where money no longer bears any relation to the production of useful goods or services. Music for the video was commissioned from the London based band Henge, playing a Doom Metal cover version of ABBA's classic song, Money Money Money.
Featuring: Regina Lazarenko and Patrick Goddard with specially commissioned music from Henge.
For an interview with the artist about his commission, please click here.
Patrick Goddard
Doom Metal, Finance, Money, Video
Patrick Goddard is an artist and writer working in London. Completing an MFA at Goldsmiths University in 2011, he is currently studying for a doctorate at Oxford University, in Fine Art practice.
Recent shows include: 'Go Professional' at Seventeen Gallery, London April-June 2017; 'Looking for the Ocean Estate' Almanac Projects, London, Nov-Dec 2016, 'Gone To Croatan' at Outpost Gallery, Norwich, May-June 2015; 'Revolver II' at Matt's Gallery, London, November-December 2014.
Alice TheobaldTaking StockCommission2017
http://takingstock.otdac.org/
For her commission, Alice Theobald has produced Taking Stock, a music library made up of 21 specially composed instrumental scores.
Taking Stock with Alice Theobald is a ongoing project in which her own musical compositions draw upon long-term research into the commodification and economy of emotions through language, sound and intonation and how they shape human experience, interaction and perception. Whilst also analysing the affective power of film scores, music for television and stock music.
Alice Theobald develops multifaceted installations that incorporate sculpture, music, performance and video. Her work addresses accepted concepts of spectacle and emotion to explore the spectrum of acting and performance present in our everyday lives and the demand for "authenticity". Drawing upon a mixture of pop cultural references, Theobald questions the essence of drama, stage and life when representing complex human themes such as love, aspiration, freedom, alienation, memory and doubt.
For an interview with the artist about her commission, please click here.
Alice Theobald
Emotion, Library, Music, Stock
Alice Theobald (b. 1985, Leicester, UK) lives and works in Huntingdon. Selected recent performances, exhibitions, and film screenings include: Weddings and Babies, Pilar Corrias Gallery, London (2017); The Next Step, Two Queens, Leicester (2016); Alice Theobald and Atomik Architecture, BALTIC Ryder Commission, BALTIC Centre for Contemporary Art, Gateshead (2016); You've got my back and I'm on your side, FRAC Champagne-Ardenne, Reims (2016); The boys the girls and the political, Lisson Gallery, London (2015); The Fifth Artist, Wysing Arts Centre, Cambridge (2015); Scene Four: Home, Flat Time House, London (2015); Dear Luxembourg (yours, bucktoothed grl), Nosbaum Reding Projects, Luxembourg (2015); Marmalade Me, South London Gallery, London (2014); I've said yes now, that's it., Outpost, Norwich (2014) and Chisenhale Gallery, London (2014); AFTER/HOURS/DROP/BOX, Modern Art Oxford, Oxford (2013) and Spike Island, Bristol (2014); Young London, V22, London (2013); They Keep Putting Words In My Mouth! An Operetta of Sorts, Pilar Corrias Gallery, London (2013); Situation|Event, Gasworks, London (2013). She is currently working on an opera with her band Ravioli Me Away at Wysing Arts Centre and BALTIC Centre for Contemporary Art in 2018.
Zoe WilliamsSnail TracksCommission2017
http://snailtracks.otdac.org
For her commission Williams has produced a collation of nine short video clips arranged in a grid which allude to the language and structure of adult-content websites. These clips magnify and examine the often heavily fetishised surfaces of materials which include fur, leather, cream cakes, ceramics and bodily fluids. Each clip features a sound composition by London-based musician Patchfinder, simultaneously responding whilst heightening and creating ruptures with the overt sensuality of the imagery. All of the videos can be downloaded and watched for free. The changing images behind the clips also hyperlink to a seemingly tangential selection of website pages.
Zoe Williams
Fetish, Music, Technology, Video
Zoe Williams (b.1983) lives and works in London. Recent exhibitions include Spring/Summer 2015, DCA, Dundee; Pel, Galerie Antoine Levi; 'Chateaux Double Wide' Glasgow International Festival 2016; H Y P E R C O N N E C T E D, MMOMA, part of 5th Moscow International Biennale of Young Artists; Tender Touches, Austrian Cultural Forum, London; Skins, Ellis King, Dublin.
Material Art FairNew FeelingsScreening2016
http://material-fair.com/es/programa-p%C3%BAblico
Opening Times presents New Feelings, a screening curated for the public program of Material Art Fair, Mexico.
From the 4th to the 7th February the screening program will be showing simultaneously at Material Art Fair and on the Material Art Fair website. The program focuses on the subjects of Capital, Desire and Bodies and concludes with an epilogue that examines all three through music videos and found footage online.
New Feelings looks at affective economies in our networked society, bringing together a series of works from artists including Maja Čule, Heather Phillipson, Jala Wahid and Andrew Norman Wilson. These works bear witness to the evolving process of capital governing desire, desire governing the body and the body becoming capital.
4th-7th February, 2016
Goethe-Insitut LondonInstitutionalising the DigitalPanel Discussion, Take-over2015
https://www.goethe.de/ins/gb/en/index.html
In collaboration with the Goethe-Insitut London and Melanie Bühler, Opening Times organised Institutionalising the Digital, a panel discussion and website take-over.
Institutionalising the Digital aimed to highlight a number of recent initiatives to have surfaces that are dedicated to presenting the work of artists in an online environment. Branching off into 'the digital', institutions now incorporate digital infrastructures as part of their institutional properties, launching digital residencies and giving away grants to support digital works. The talk focussed on the questions and concerns involved in the presentation, facilitation and support of artistic practices online by institutions that are both 'digitally native' as well as organisations that preceded computer technology.
Opening Times was present during the event through a take-over of the Goethe-Institut's website, interacting live with the discussion.
The panellists include:
Lauren Boyle, DIS Magazine, curator of the 9th Berlin Biennale 2016
Cécile B. Evans, artist, London
Zachary Kaplan, Assistant Director at Rhizome, New York
Susanne Pfeffer, Director at Fridericianum, Kassel
The event was moderated by Filipa Ramos, Editor in Chief, art-agenda & Co-curator Vdrome.
Panel Discussion, Take-over
Constant DullaartEpizeuxis, Conduplicatio, MesodiplosisResidency2015
http://epizeuxis.otdac.org/http://conduplicatio.otdac.org/http://mesodiplosis.otdac.org/
For his residency, Constant Dullaart has made a series of three website pieces. Each site is a container for an external page (Google Images, Apple and Wikipedia) which is split into different shaped parts inside moving frames. These dynamic frames re-draw the content into relationships with other parts, producing a collaged cut-out of the browser content in different shapes and layers.
For an interview with the artist about his residency, please click here.
June-September, 2015
Art, Artwork, Browser, Collage, Environment, Frames, Space, Website, Window
Constant Dullaart (b 1979, NL) lives and works in Amsterdam and Berlin. Recent solo exhibitions include The Censored Internet, Aksioma, Ljubljana, 2015; Stringendo, Vanishing Mediators at Carroll / Fletcher, London, 2014 and Onomatopoeia, Utah Museum of Contemporary Art, Salt Lake City 2012. Group exhibitions include Casting a Wide net, Postmasters, NYC, 2014; Online Mythologies, Polytechnic Museum, Moscow, 2012; Genius without talent, de Appel, Amsterdam, 2012; A Painting Show, Autocenter, Berlin, 2011 and Off the Record, Stedelijk Museum, Amsterdam, 2009.
Heather PhillipsonSERIOUS TRACTIONCommission2015
http://serioustraction.otdac.org
Heather Phillipson's new online video, SERIOUS TRACTION, takes place inside a body inside a room inside a screen inside a browser window. Limited to text and sporadic two-tone, diagrammatic images, the video conflates systematised behaviours – of language, medicine and intimate/public experience – through which the body is laid out, penetrated, assessed.
Art, body, Language, Medicine, poetry
Heather Phillipson works across video, sculpture, music, drawing, text and live events. In 2015, Phillipson will present solo projects at Schirn Frankfurt, the Istanbul Biennial, Sheffield Doc Fest, and will be Writer in Residence at the Whitechapel Gallery, London and artist in residence at Wysing Arts Centre. Recent solo exhibitions include: Bunker259, New York; Dundee Contemporary Arts; Grundy Art Gallery, Blackpool; BALTIC Centre for Contemporary Art, Gateshead; and a video commission for Random Acts, Channel 4 television. Phillipson is an award-winning poet and was named a Next Generation Poet in 2014.
Orit GatHow Do We Write When We Write Online?Research Commission2014
http://hdwwwwwo.otdac.org/
For her research commission, Orit Gat has produced How We Write When We Write Online? – a series of questions posed to writers on how we write on the internet and an essay based on the responses. The list of contributors include: Ben Eastham, Brian Droitcour, Chris Fite-Wassilak, Dan Fox, Dushko Petrovich, Emily LaBarge, Jessica Loudis, Kyle Chayka, Merve Unsal, Michael Connor, Nicholas O'Brien, Oliver Basciano, Omar Kholeif, Ozge Ersoy, Stacey Allan, Stephanie Bailey, Tyler Coburn and Wendy Vogel. The project is also ongoing with an open submission process for further responses.
Image: Julia Weist, With Haim Steinbach, 2014 (Fujifilm instant film). For more information see here.
Online, Reading, Text, Writing
Orit Gat (b. 1984,Tel Aviv, Israel) is a writer based in New York. She writes about contemporary art, publishing, the internet, and all sorts of links between these. Her writing is published regularly on Rhizome, where she is a contributing editor, and has appeared in numerous magazines, including frieze, ArtReview, The White Review and The Brooklyn Rail. She is the managing editor at WdW Review and online art editor at BOMB magazine.
Richard HealyWHERE'S PETER?Commission2014
http://wherespeter.otdac.org/
For his Opening Times commission, Richard Healy has produced the video WHERE'S PETER?. The work incorporates recorded footage, found documents and research that trace the relationship between Benjamin Britten and Peter Pears. The video self-consciously choreographs and organises material about spaces and structures that potentially afford subversive or alternative social models.
Richard Healy
Britten, Pears, Research, Sexuality, Space
Richard Healy lives and works in London. Selected exhibitions include: Last Seen Entering the Biltmore, South London Gallery, 2014; I love you Me either, Project Native Informant, London, 2014; Prone Positions, Rowing, London, 2013; Vetiver, Marian Cramer Projects, Amsterdam, 2012; But Mr Architect!, Furnished Space, London, 2012; Dumb, The Brno House of Art, Brno, 2012; Strategies for Building, Outpost, Norwich, 2011; Young British Art, Limoncello, London, 2011; Hey Guys!, Fotograf, Prague, 2011.
Julie Born Schwartz再吻一次, ONE MORE KISSResidency2014
http://onemorekiss.otdac.org
Grey dust is covering the trees. It makes them sparkle like silver leaves in the wind. The sky is hanging on large billboards.
He hears the rain drumming on the window and looks at the shadow falling from the lit-up rose. She takes his hand. Place your fingers on the black mirror. Close your eyes. Try to relax. He paints a landscape. The thick ink touches the paper, and the water runs in circles through the mountains. Each night he paints the same motif again and again.
For an interview with the artist about her residency, please click here.
Julie Born Schwartz
September-December, 2014
Beijing, Kepler, Sci-fi, Screens, Video
Julie Born Schwartz (b. 1981, Chicago, USA, raised in Denmark) is an artist based between London and Copenhagen. Born Schwartz graduated from the Royal Academy Schools in London and she is represented by Union Pacific Gallery, London.
Selected recent exhibitions and screenings include: The Photographic Center, Copenhagen; Union Pacific Gallery, London; Royal Academy of Arts, London; The Sunday Painter, London; Kunstarkaden, Munich; Lauritz Kunsthal, Copenhagen and Kunstfilmtage, Düsseldorf.
Megan RooneyTilia americanaCommission2014
http://tiliaamericana.otdac.org
For her artwork commission, Megan Rooney has created Tilia americana, a set of five sequential, multi-framed videos with an accompanying narrated text. The videos incorporate elements of drawing, sculpture and performance, portraying women obscured by water-coloured pillow cases in different suburban situations. The audio track accompanying the videos is a voiceover of the artist reading a fractured and dreamlike prose poem.
For an interview with the artist about her commission, please click here. For a transcript of the narrative, please click here.
Drawing, Environment, Narrative, Suburbia, Video, Voiceover
Megan Rooney (b.1986, Canada) is an artist based in London. Recent exhibitions and performances include: An Evening of Performances, David Roberts Art Foundation, London; Till the stars turn cold, S1 Artspace, Sheffield; Affluent Insights, LimaZulu, London; Pleasure Principals, Fondation Galeries Lafayette, Paris; Der Leone have Sept Cabeça (with Quinn Latimer), CRAC, Alsace; Pleasure and Charity – sharing in the experience, Art Gallery of Ontario, and 0% Promise SPACE, London.
PhillipsTempo di MartelloTake-over2014
http://phillips.com/auctions/auction/PD010114
To coincide with the Paddles On auction at Phillips on the 3rd of July 2014, Opening Times took-over the Phillips website with Tempo di Martello, bringing the site to life with animations and website interventions inspired by the fancy footwork of MC Hammer.
3rd-5th July, 2015
Take-over
Oliver SutherlandArabidopsis Thaliana FlammeusCommission2014
http://atf14.otdac.org
For his artwork commission, Sutherland, in collaboration with a synthetic biologist, has created an altered genome file for the common weed Arabidopsis Thaliana. The new genome has an altered pigment in daylight and a fluorescing pigment under certain light conditions. The file has a limit to the number of downloads on Opening Times' website before it will be dependent on peer sharing and seeding through the BitTorrent sharing community. The artwork exists as an 'ApE' file, a format produced using a freeware genetic editor often used by biological engineers and genetic hackers. However, the consumer tools to actualise and produce genetic code as living matter does not yet exist and therefore the file sits in anticipation of a future technology to activate it.
(Note: BitTorrent is a protocol supporting the practice of peer-to-peer file sharing that is used to distribute large amounts of data over the Internet. To download the files a BitTorrent client such as uTorrent is required).
Oliver Sutherland
ApE, BitTorrent, Genetics, Genome, Technology, Torrent
Oliver Sutherland (b.1985, UK) graduated from the Royal College of Art, London, in 2012. Recent exhibitions include: After/Hours/Drop/Box, London; Home Theatre, Baró Galeria, Sao Paulo; Brand Innovations for Ubiquitous Authorship, Carroll/Fletcher, London; Chimera Q.T.E, Cell Projects, London; and Sound Spill, Seventeen, London
Nicolas SassoonPandoraResidency2014
http://pandora.otdac.org
Throughout his 3 month residency, Sassoon will create and upload new works to his Pandora project, centred around the building he has occupied on Pandora Street for the last five years, a building that is both his house and studio. Whilst playing on the mythological story of the same name, Sassoon uses the structure of his domestic space as container for multiple works.
Nicolas Sassoon
3D, Artwork, GIF, Space, Studio, Website
Nicolas Sassoon (b. Marseille, France, 1981) is an artist based between Vancouver BC, Canada and France. His work explores representation of everyday space through computer-based processes, often appearing in the form of animated GIFs. He is co-founder of WALLPAPERS, and a member of Computers Club.
Ruth ProctorAlwaysCommission2014
http://always.otdac.org
For her artwork commission, Proctor has created Always, a standalone website that displays a clock, continuously counting on from the launch of the Opening Times website. The appearance of the website changes on each visit. When a button is pressed, an automated screen shot is downloaded, recording the unique time of the visitor's interaction.
Ruth Proctor
Clock, Movement, Time, Website
Ruth Proctor (b. Scunthorpe, UK, 1980) is an artist based in London, UK. She is represented by Hollybush Gardens, London and Norma Mangione Gallery, Turin.


Last weekend me and my sister ran a stall at our village's playgroup family fun day and I sold lavender bags which I had made. We didn't make a huge amount of money as it wasn't very busy (as some sneaky people planned a dog show and carboot sale on the same day in our village). However I am not deterred and it has made me want to run more stalls - maybe at a carboot sale or a craft fair. Also I have recently been considering opening an Etsy shop as I have been making little cushions as well and I have really got into it. Watch this space.
The mess we made pricing everything up.
If you saw my summer bucket list post (link here), you may have noticed that I hoped to find a summer job this summer (and have a stall somewhere) and, lo and behold, I have done just that - it really is rare that I complete anything that I write on my bucket lost, to be honest.
Anyway, my summer job is cleaning holiday lodges and, so far, it has been relatively nice. I love being able to go and do something and actually make some money to save for uni. Everyone I have met are really lovely and it is very convenient as it is just in the neighbouring village. So, good times, happy days.
I also bought some fun new PJs from Primarni. 'Fries before guys' will deffo be my new mantra from now on.
As well as lots of lavender bags, I have been making cushions. The first one I made was the red spotty one which I gave to my sister and then I made the smaller one in front of it which I gave to my other sister. Like all of the things I make, they have exposed stitching and are a bit rough and ready but I find that endearing. I think it is quite a romantic notion being able to see where the person who made it might have gone a bit wobbly on the stitching as you don't get that personal touch in factory made things.
I also took place in a book swap organised by Hayley of What Hayley Did and Beth of Beth Tinkerbell and I received a lovely book from Megan from Hepburn's Pixie Crop.
I was so pleased with the book I received as I haven't read anything by Virginia Woolf before but I have always been interested in her work so I am looking forward to reading this - I am currently reading North and South by Elizabeth Gaskell, just btw.
I hope you enjoyed this post and I, once again, apologise for the bad quality pics - my blogging camera is out of action as it needs new batteries.
Love the cushions! Such talent!
Those lavender bags are adorable.
They are so adorable. I always wish I had a talent for craft, but sadly, I don't! I'd definitely open the Etsy shop!


text:
Q: nVidia GTX 1080 Ti support for 3 4k monitors Will it be possible to use the GTX 1080 Ti as a GPU for three 27 inch 4k monitors, assuming that the main purpose of the PC is programming?
Thank you.

A: Can't speak to gaming, but 3 x 4K@60hz works fine with my 1060 6GB for programming. No different than a single screen.

A: This is possible for this marvelous video card, and much more.
For a hardware test report see the article
NVIDIA's GeForce GTX 1080 GPU benchmarked at Triple 4K (11,520 x 2160).
In the Heaven is an intensive GPU benchmark,
it hit 10.7 FPS on average.
The conclusions says:

Does the performance live up to what we expected? Yes, it does. The GTX 1080 beats every other card we tested in every single test, which is a huge win for NVIDIA. We have a card that handles 11,520 x 2160 better than the $999 Titan X, and the HBM1-powered Radeon R9 Fury X.

A: It is not possible.
Bought three 27 inch 4k displays, plugged them into the 1080Ti, had a laggy experience which is not even suitable for programming.

redpajama_set_name: RedPajamaStackExchange


In this post, we have discussed Into The Dead 2 v0.8.1 Apk Mod+OBB Data (Unlimited all) Download. Into the Dead is AN iOS and Android game developed by PikPok. Players should run through a landscape swarming with zombies and move left or right to dodge them. The player has no selection, however, to maneuver forward and obtain as so much as potential before dying. Weapons are picked up from offer crates because the player runs past.In the previous post, we had discussed Sniper Ghost Warrior v1.1.2 Apk Mod+OBB Data (Unlimited all) Download.
Into the Dead throws you into the ugly world of the zombie apocalypse wherever there are not any second probabilities. Do what you have got to so as to remain alive, keep moving as quickly as you'll, into the dead 2 download and shield yourself by any means that necessary. once the Dead area unit rising, run.
Into the Dead could be a person action game wherever players can step into the shoes of a survivor of the zombie apocalypse World Health Organization is urgently fleeing the present space, that is totally overrun by zombies.The game is controlled exploitation your device's measuring device, therefore if you tilt your phone to 1 aspect, into the dead 2 mod apk your character can head in one direction and if you tilt it to the opposite aspect, he can go the opposite method. additionally, you'll bite the screen to use weapons, tho' that's not as necessary.
The most necessary issue in Into the Dead is avoiding the zombies that seem on the horizon and which is able to do away with you if they bit you. additionally, as you run, you may encounter additional obstacles, together with trees, fields of corn, abandoned tractors and far additional. something that slows you down may well be fatal.Into the Dead is an action game with an easy, into the dead 2 apk free download addictive visual vogue. The game's setting and effects area unit very spectacular and that they produce a continuing feeling of despair.
Evolving narrative and multiple endings – complete seven action-packed chapters, 60 stages, and many challenges.
Varied gameplay – fireplace from military gun emplacements, kill the hordes from atop vehicles or risk going when them on foot.
Multiple, immersive environments – discover totally different locations, from oil fields and military bases to campsites and rural farm communities.
Powerful weapons and implements of war perks – unlock and upgrade explosives, disturbance weapons, firearms, and more.
Ever-increasing zombie threats – adapt your ways to wipe out totally different hordes, together with armored and running zombies.
Daily and special event modes – prove your skills to win exclusive prizes.
Loyal canine companions – deflect zombies and keep protected within the field.
Bug fixes and performance enhancements for all devices.


text:
Q: How to make one ASP.NET MVC site "derive" from another My question is similar to "ASP.NET 2 projects to share same files", but with an ASP.NET MVC slant.
Basically, we have two sites, one being based mostly on the other (roughly 90% views, controllers, images, in the second are identical to the first). However, in some cases, the views may be different, or a controller in the second site may be different to the first.
Are there any simple ways of achieving this in ASP.NET MVC?
So far, we've looked at using linked files to have two totally seperate projects where the second project shares the files it needs from the first.
One problem with this approach is that most pages in the second project don't literally exist in the virtual directory, it makes debugging a pain - you have to publish in order to generate the files so you can debug.
Does anyone have any better approaches, or ways this approach can be simplified?

A: This article might help: http://dotnetslackers.com/articles/aspnet/storing-asp-net-mvc-controllers-views-in-separate-assemblies.aspx
Essentially, it involves creating your own WebFormViewEngine which tells MVC where to look for the Views.

A: If they are 90% the same and you want to keep them in sync as functionality evolves I would consider making them into a single web application and using the host header to differentiate and to change the images/links/text/functionality/etc. between the two sites.
You can do this in your BaseController: look at the host header and create a Brand object that every page and view has access to, just like it might have aUser object.  That Brand object can include the name of the site, the canonical Url for the site, the location of the image directory for that brand, boolean values to turn certain features on or off, ...
Now in a Controller you can just ask if (Brand.SupportsPageX) {...} to decide what to show next.  And if you make the brand object part of your base view model or put it in the view collection you can have views that use the brand's name, image directory, ... to customize how they look.

A: *

*Put as much as possible into a shared non-UI library project.  I do that on every project that I work on this, increased testability, shared code with Windows Services, and a host of other reasons.
I've found that if you're aggressive with this then quite often over two-thirds of the project would be in the non-UI shared library.
After doing this, you can take things a step further and create a second library with some shared MVC/UI elements.

*Depending on your opinion of it, RenderAction may help a bit here. As you know,  RenderAction will allow you to group up those controller/view components and pass in variable arguments at runtime.

A: A project I'm working on currently has a similar requirement and we have started looking at using portable areas. I personally haven't delved very deeply into them at the moment, but it may be worth you taking a look.

A: I've been working with areas to create multiple websites that I can host with one hosting account.  I would think you could use a similar approach.  By implementing the common functionality in the base project, it will be available to each area.  You can also override any of the base views or controllers by implementing them in the area.  It may require some tweaking of the ViewEngine and Routing but I think it could be done.


text:
#ifndef __itkTransformParametersAdaptor_h
#define __itkTransformParametersAdaptor_h

#include "itkTransformParametersAdaptorBase.h"

namespace itk
{
/** \class TransformParametersAdaptor
 * \brief Base helper class intended for multi-resolution image registration.
 *
 * During multi-resolution image registration, it is often useful to expand
 * the number of parameters describing the transform when going from one level
 * to the next.  For example, in B-spline registration, one often wants to
 * increase the mesh size (or, equivalently, the control point grid size) for
 * increased flexibility in optimizing the transform.  This requires the
 * propagation of the current transform solution to the next level where the
 * solution is identical but with an increase in the number of parameters.
 * This base class and those derived classes are meant to handle these types of
 * situations.
 *
 * Basic usage will involve the user specifying the required fixed parameters, i.e.
 *
 *   \code
 *   transformAdaptor->SetTransform( transform );
 *   transformAdaptor->SetRequiredFixedParameters( fixedParameters );
 *   transformAdaptor->AdaptTransformParameters();
 *   \endcode
 *
 * which will adjust the transform based on the new fixed parameters.
 *
 * \author Nick Tustison
 * \author Marius Staring
 *
 * \ingroup ITKRegistrationCommon
 */
template<class TTransform>
class TransformParametersAdaptor
: public TransformParametersAdaptorBase<TTransform>
{
public:

  /** Standard class typedefs. */
  typedef TransformParametersAdaptor                     Self;
  typedef TransformParametersAdaptorBase<TTransform>     Superclass;
  typedef SmartPointer<Self>                             Pointer;
  typedef SmartPointer<const Self>                       ConstPointer;

  /** Run-time type information (and related methods). */
  itkTypeMacro( TransformParametersAdaptor, TransformParametersAdaptorBase );

  /** Typedefs associated with the transform */
  typedef TTransform                                     TransformType;
  typedef typename Superclass::TransformPointer          TransformPointer;
  typedef typename Superclass::ParametersType            ParametersType;
  typedef typename Superclass::ParametersValueType       ParametersValueType;

  /** Set the transform to be adapted */
  itkSetObjectMacro( Transform, TransformType );

  /** New macro for creation of through the object factory. */
  itkNewMacro( Self );

  /** Set the fixed parameters */
  itkSetMacro( RequiredFixedParameters, ParametersType );

  /** Get the fixed parameters */
  itkGetConstReferenceMacro( RequiredFixedParameters, ParametersType );

  /** Initialize the transform using the specified fixed parameters */
  virtual void AdaptTransformParameters() {};

protected:
  TransformParametersAdaptor() {}
  ~TransformParametersAdaptor() {}

  void PrintSelf( std::ostream & os, Indent itkNotUsed( indent ) ) const
  {
    os << "Fixed parameters: " << this->m_RequiredFixedParameters << std::endl;
  }

  TransformPointer                           m_Transform;
  ParametersType                             m_RequiredFixedParameters;

private:
  TransformParametersAdaptor( const Self & ); //purposely not implemented
  void operator=( const Self & );             //purposely not implemented

}; //class TransformParametersAdaptor
}  // namespace itk

#endif /* __itkTransformParametersAdaptor_h */

redpajama_set_name: RedPajamaGithub


Unemployment fell to its lowest level in nearly half a century in September even as job growth slowed, allowing President Donald Trump to present a mostly-favorable economic picture in the critical home stretch before the midterm elections.
The Labor Department reported 134,000 new jobs in September, down from 270,000 in August. Wage growth dipped, with average hourly earnings up 2.8 percent over the previous year, down from 2.9 percent in August — the largest single-month jump in nearly a decade. But unemployment was 3.7 percent, down from 3.9 percent in August and its lowest level since December 1969.
If the September job-growth number signals a long-term slowdown, that would hardly be unexpected, given that the economy has for months been at or near full employment. But the September jobs number, combined with the slowdown in wage growth, complicates Republicans' efforts to claim success from their tax cut and deregulatory policies. Earlier week, Trump boasted on Twitter about "blowout numbers on New Jobs" that failed to materialize.
Long-term data show the pace of job growth under Trump has been about the same as during the last two years of the Obama administration.
At the same time, the historically low unemployment rate fits neatly into Trump's promise of restoring the American economy's bygone dominance.
Abnormally high wage growth in September 2017, at 2.8 percent, may account for wages falling in BLS' year-over-year comparison. Wages one year ago were up 12 cents an hour, the largest jump since 2007.
The Labor Department earlier reported that gains in full-time median weekly earnings lagged inflation during the first six months of 2018 compared to one year before, and also during the last three months of 2017. The average hourly wage increase, which is usually higher but less representative of the typical worker's wage than the median, remained about even with the Consumer Price Index.
The labor force participation rate — the share of people actively looking for a job — remained near 63 percent in September, close to its lowest level since the 1970s.
These findings have allowed Democrats to argue that whatever economic gains were generated by lower taxes did not translate into higher wages for ordinary workers. "Most Americans don't feel more economically secure," said Chris Lu, who was President Barack Obama's deputy labor secretary.
Analysts surveyed by Bloomberg had predicted the creation of 180,000 jobs in September, an increase in over-the-year hourly earnings of 2.9 percent, and an unemployment rate down at 3.8 percent.
Economists had speculated that the September numbers could be skewed by Hurricane Florence, but BLS analysts said that response rates for the surveys were within normal ranges.