The traccar-api-client is a Java Client Software to provide full (remote) control over your Traccar Server.

It can be used to integrate your Tracking with your company systems, like Human Resources-, Sales- and Fleet Management Software. Synchronize your employees with their cars and driver information without utilizing the Traccar UI. Pull driving reports for your sales team …​ and much more ;)

Architectural Characteristics

  • Separation of Concerns
    The architecture separates generated API code in traccar-openapitools-client
    from business logic via Api interface in traccar-api-client.

  • Generated API Layer
    All REST API models and client classes are generated from the Traccar OpenAPI spec (yaml)
    and maintained in traccar-openapitools-client.

  • Extensible Client
    traccar-api-client wraps and extends the generated API,
    providing a stable interface for application developers.

Component Interaction

This sequence diagram illustrates how components interact at runtime for REST API calls:

Diagram

Traccar API @Service

The Traccar OpenAPI Client wraps the Traccar API in a Spring @Service — simple as that.
You can always counter check against the latest Official Traccar API Reference.

usage

After adding the jar to your software or build system the API can easily be added to any Springboot Application as a @Service in your code

@Autowired private ApiService api;

authentication

Then you can set your credentials via bearer token or basic authentication

// use your token generated from your Traccar server
api.setBearerToken(YOUR_TRACCAR_TOKEN);
// switch auth method
api.setBasicAuth(mail, password);
// switch for every REST call as needed

or switch users with different access to different user, devices etc. directly in your code.
see package bm.traccar.invoke.auth

usage example

Here’s a simple example code to demo how to add the Traccar API Service to your company software.
First you need these prerequesites:

  • Add the traccar-api-client-x.y.z.jar to your application.
    Or add it to your .m2 repo or nexus server, then to your pom.

  • Add your Traccar Server URL and credentials to your application.properties file.
    i.e. host, user.name, user.password, user.email (, accountToken).
    Or provide these values in a way that meets your security strategy (secrets etc.)

and then the coding is straight forward:

package your.company.app...;

import bm.traccar.api.ApiService            // (1)

public class TraccarUsers {

    @Autowired
    private ApiService api;                 // (1)

    public static void main(String[] args) {

        api.setBasicAuth(mail, password);   // (2)
//      api.setBearerToken(token);

        User user = new User();             // (3)
        user.setName("user-1");
        user.setEmail("email-1");
        user.setPassword("pw-1");

        User userOnServer = api.users.createUser(user);     // (4)

    }
}
  1. add the ApiService, being a Spring @Service class

  2. choose and set your authentication
    and can change it any time in the program flow.

  3. create a new User (DTO) in your software for your employees

  4. create a new User (Entity) in your Traccar Server
    The new User (DTO) is returned with a unique ID from the server.

mimic URL syntax

Note how the Java invokation mimics a REST call

// <server>/api/users?user_Id=11
User user = api.users.getUsers(userId);

And that is all you need to create users, devices etc. and manage them in your code!
For more examples check the integration tests, i.e. UsersIT and others.

As you can see the above expression is similar the URL

http://{host}:{port}/api/users/{id}
User userOUT =       api.users.createUser(user);
https://localhost/api/devices?id=3
Device[] devices =        api.devices.getById(3);

configuration

For convenience you can use the application.properties file and grab the @Value in your source code, depending on your security concepts. Check the *.*IT integration test files for demo code.

application.properties

traccar.host=http://localhost
traccar.user.name=admin
traccar.user.password=admin
traccar.user.email=admin@domain.com

your implementation (compare ITests)

@Value("${traccar.user.name}")     private String name;
@Value("${traccar.user.password}") private String password;
@Value("${traccar.user.email}")    private String mail;

fixed docker authentication

For integration testing with docker we apply a fixed serviceAccountToken to gain (virtual) administration over the complete server.

traccar.web.serviceAccountToken=VIRTUAL_ADMIN_ACCESS
@Value("${traccar.web.serviceAccountToken}") private String virtualAdmin;
Important

You have to prepare your docker version of traccar via traccar.xml file
by adding the service account token (with its risks):

<entry key='web.serviceAccountToken'>VIRTUAL_ADMIN_ACCESS</entry>

Traccar API Interface

If you don’t want to use the Traccar @Service and rather implement your own, you should start with the Traccar `API interface. The coding is similar (and using the same implementations)

package your.company.app...;

import bm.traccar.api.Api                   // (1)

public class TraccarUsers {

    @Autowired
    private Api api;                        // (1)

    public static void main(String[] args) {

        api.setBasicAuth(mail, password);   // (2)
//      api.setBearerToken(token);

        User user = new User();             // (3)
        user.setName("user-1");
        user.setEmail("email-1");
        user.setPassword("pw-1");

        // ApiService syntax
        User userOnServer = api.users.createUser(user);
        // Api interface syntax             // (4)
        User userOnServer = api.getUsersApi().createUser(user);

    }
}
  1. this time we are auto wiring the Api interface

  2. choose and set your authentication

  3. create a new User (DTO) in your software for your employees

  4. create a new User (Entity) on your Traccar Server

By using the Api interface you can also get individual sub API implementations:

Api.Users users = api.getUsersApi()
User newUser = users.createUser(user);

This can be convenient, if you don’t want to mess with many APIs and focus the implementation on User Management, for example.

Traccar API Implementation

This software is based on a generated Java Spring REST Client from the Traccar REST specification. The single package (bm.traccar.api) implementation wraps the Java client in a Spring @Service to be applied wherever feasible. The API client completely hides the REST intricities inside regular Java methods.

The traccar-api-client is basically a single package application or rather service.
In addition there is a websocket package to receive live information from the Tracking System.

openapi-generator

As Traccar provides a REST specification in form of a yaml file we faciliate the OpenAPI Generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec v3.

— see github.com/OpenAPITools/openapi-generator

Note that this Java code generation takes place in the isolated traccar-openapitools-client project. It has to run before this project which is build around the generated artifact.

Tip

To get more details on the generation you can read the project notes: openapitools-client

You can check Generate the client using Maven for a starting point on how to apply Maven properties etc.

Traccar REST

Traccar v6.7 provides 17 APIs (see package bm.traccar.generated.api) and as the GTS progresses Traccar v6.12.2 provides 19 APIs. These APIs support many aspects of GPS Tracking and beyond, like health monitoring.

The traccar-api-client defines one single (nested) API interface: bm.traccar.api.Api, which allows us to switch the implementation. This was required, for example, when changing from swagger to OpenAPI (3 to 3.1).

Tip

Therefor you should stick to the bm.traccar.api.Api interface to connect your program logic!

Data Transfer Objects - DTOs …​

In addition the traccar-api-client-6.7 provides 21 Data Transfer Objects and v6.12.2 already has 26 (package bm.traccar.generated.model.dto).

These DTO represent the different System Entities and are used to exchange information with the server.
For example the User API call to Create a User is provided as Java method by the Api interface:

    public interface Api {
           interface Users {
                User createUser(User user) throws ApiException;

In your code you can easily create a new User() in your software

    User userIN = new User();
    userIN.setName(usr);
    userIN.setEmail(mail);
    userIN.setPassword(pwd);
    userIN. ...

and then create it on server side and receive a copy:

    User userOUT =  api.users.createUser(userIN);

Note that userIN and userOUT are different instances. The latter provides the userId provided by the Traccar Model and is vital to use in the client server communication. The ID is usually hidden for external users (i.e. in the UI), but required for unambiguously user identification!

…​ are not System Entities

Nevertheless you should always distinguish Data Transfer Objects from actual Entities in the Systems Entity Relations Model - ERM. The Traccar System is build around the Traccar Data Model, which is represented in the model package, while the DTOs are created in the resource package. The Entities are only accessed by Traccar itself, while DTOs are snapshots of them. The Traccar Database is the best place to monitor what’s going on.

This is similar to DNA and RNA:
The DNA (ERM) is part of the living organic System,
while the RNA (DTOs) is simply a copy of the DNA from a certain point in time.

Traccar sub Api implementations

This project is part of a larger software and therefor the Traccar Api Endpoints (operationId) are implemented in the order required by the main application. During prototyping you should avoid any bulk implementations. Protoyping is modelling and every new cycle, i.e. x.y.z release can introduce new sub APIs and endpoints - as the main application progresses. This way the implementation for all sub APIs turns into a mature pattern. And interdependencies can be implemented with convenient methods combining common actions.

The most important aspect of this project is its layer functionality. As the Traccar GTS progresses the REST specification is modified and we want to avoid code changes all over our application. In the best case the code changes can be restricted to this project. The project can fix and hide specification inconsistencies and should provide consistent Java methods.

SessionApi

We can find all generated sub APIs in the bm.traccar.generated.api package. For example we use the UsersApi and DevicesApi by setting the authentication, sending a request and receive entities. The ApiClient class holds the username and password. When you call an API method, it will automatically construct and add the Authorization header. This works perfectly for APIs that are stateless and check the basic auth header on every request.

The SessionApi is a special case as REST and HTTP are defined as a stateless protocols. A session makes sense, if we log into the browser UI and keep it open. For the traccar-api-client we don’t need to establish a session for REST interactions. The sole purpose is to use the SessionApi to create a session and retrieve a JSESSIONID (Java Session Id) to be used with a websocket connection.

You can make a POST request to the /api/session endpoint with the user’s email and password. This will create a session and return a Session Cookie managed by Jetty. This process is implemented in the ApiService and can be called with

    String jSessionId = api.session.createSessionGetJsessionId(userMail, userPassword);

That’s all we need from the SessionApi and we will move on to the websocket implementation with the returned JSESSIONID for the provided User. When you connect to websocket endpoint /api/socket (Not REST endpoint /api/session!), the server checks for the presence of a valid session cookie to authorize the real-time data stream.

Traccar ApiAspect and ApiException

This API client implementation applies Aspect-Oriented Programming (AOP) as the technique for handling exceptions in Spring Boot applications. This way all exception handling happens in one central ApiAspect class and avoids code duplication.

We apply AOP, since the Traccar REST API is a moving target. SubAPIs are added, methods and types are changed etc. With AOP we can grasp a general pattern that applies to all sub APIs, even the ones we haven’t implemented or Traccar will offer in future.

Generally any problem in the client software should through an ApiException to take over responsibility and to provide useful messages for developers and wrap the cause for deeper analysis.

As you can see in the bm.traccar.api.ApiAspect class the @Pointcut includes all *Api classes and methods in :

@Pointcut("execution(public * bm.traccar.generated.api.*Api.*(..))")

and the Joinpoint is defined to only catch RestClientExceptions, wrap them in an ApiException and throw it for the method execution:

public void afterThrowingApiMethod(JoinPoint joinPoint, RestClientException rcEx)
    throws ApiException {

Note that the ApiException is a RuntimeException and the caller is not forced by the compiler to catch it. However you should handle it as good practice and to ensure that you have a binding communication.

    Spring AOP
    invocation order
        invoke **UsersApi.usersPost(..) ** <-------------+
          invoke ApiClient.selectHeaderAccept(..)        |
          invoke ApiClient.selectHeaderContentType(..)   |
          invoke ApiClient.invokeAPI(..)                 |
        Exception in ApiMethod: **UsersApi.usersPost(..) **

Traccar Scenario

For Real Time Development it makes sense to precisely define the RT environment to be observed and controlled. In the context of a Tracking System we will simply define the environment as a (fixed) set of devices and call it a Scenario. These devices will represent RealObjects (that we will develop eventually) in the Real World. They can be many vehicles, some pedestrians, bicycles - if we think of a Traffic Application.

Scenario Entities

In conjunction with Traccar a scenario is composed of different sets of Traccar Entities.

The RealTimeModel (in the traccar-realtime-client project) will allow access to Traccar Users, Devices, Drivers etc. to compose any scenario that fits your business. This RealTimeModel or RealTimeManager or RealTimeStateManager reflects all objects of one authenticated Traccar User and can only see what he is allowed to see. This can be experienced and explored in the Traccar UI - as we proceed.

Scenario Properties

For a concrete development approach we will define a Scenario simply as a (fixed) number of devices. All other Traccar Entities are related to these devices somehow. At development time we will create a complete Scenario from a scenario.properties file, which defines all actors of the - for the time being: closed system.

Note

For convenience the scenario is placed in the actual project packages and not in the test packages. This makes it easier to be used to test higher level projects.

On the long run there are endless ways to persist and load a scenario. With json you can define a complete relational model to load and bigger models could be retrieved from the database itself. For compressed ER models Googles Protocol Buffers could do the job, as we have already practiced in the JeeTS project.

At dev time we will use the scenario.properties file to explore Traccar’s Permission Management.

We’ll start with some simple initial rules to setup a Scenario:

  • scenario.properties do not define any technical details on the Traccar Server.
    These should remain in the application.properties and the server should then be prepared to load a Scenario. Less technical and more domain-driven.

  • Each scenario must have (at least) one User (account!) as the Scenario Manager.
    This authenticated User must be authorized to see the complete Scenario - vice versa his view defines the Scenario. The smallest scenario would be one User with access to his own Device.

So let’s begin to fill the file with Users and Devices:

    # basic scenario
    ## users
    scenario.user[0].name=admin
    scenario.user[0].password=admin
    scenario.user[0].email=admin@scenario.com
    scenario.user[0].administrator=true        (1)
    ## devices
    scenario.device[0].name=runner
    scenario.device[0].uniqueId=10
    scenario.device[0].model=ro
  1. First user[0] is the scenario manager, i.e. administrator

This scenario defines one administrator, one manager, two users and four devices for a start.

ScenarioProperties

Now we can apply Spring Technology to load these properties with the ScenarioProperties class

    @Component
    @ConfigurationProperties(prefix = "scenario", ignoreUnknownFields = false)
    @PropertySource("classpath:scenario.properties")
    public class ScenarioProperties { ...

We are using @ConfigurationProperties for Type-Safe Configuration for managing multiple related properties. The class groups all the related properties together, is type-safe, less error-prone, and keeps your configuration clean. You can inject the entire ScenarioProperties object into any other bean (like a service or controller) using standard dependency injection.

The configuration is set in the ScenarioConfig class, which can be empty:

    @Configuration
    @EnableConfigurationProperties(ScenarioProperties.class)
    @PropertySource("classpath:scenario.properties")
    public class ScenarioConfig { /* empty */ }

And finally we unit test the auto wiring

    public class ScenarioPropertiesTest {
      @Autowired private ScenarioProperties scenarioProperties;

Now we can access the ScenarioProperties class to pick up the objects.

ScenarioLoader

Note that the property objects differ from the Traccar Entities.

    bm.traccar.rt.scenario.ScenarioProperties.Device
    bm.traccar.generated.model.dto.Device

So let’s code a little ETL loader to Extract the property objects, Transform them into Traccar Entities and Load (CRUD) these to the Traccar Server.

The ScenarioLoader reads the objects from the properties file

  @Autowired private ScenarioProperties props;

and creates the Traccar Entities on the server via api.

  @Autowired protected Api api;

Functionality and structure are straight forward.

Scenario Tests

Run the tests in test package bm.traccar.rt.scenario to go into details and debugging.

In test package bm.traccar.rt you will find a BaseReaTimeScenarioTest which sets up the scenario.properties @BeforeAll testing. It tears down the scenario and clears the server @AfterAll testing. This is the base for the *IT tests in the package.

The ReaTimeScenarioIT sets up the scenario and displays all Beans in the ApplicationContext. Here we will find all classes of the bm-traccar branch as you can see by the package names, i.e. application layers:

    bm.traccar.invoke.ApiClient             // traccar-openapitools-client
    bm.traccar.generated.api.DevicesApi
    ..                   and other *Apis
    bm.traccar.api.ApiService               // traccar-api-client
    ..
    bm.traccar.rt.RealTimeController        // traccar-rt-client
    ..
    bm.traccar.ws.TraccarSessionManager     // traccar-rt-client websocket
    bm.traccar.ws.TraccarWebSocketRoute

Note that the bm.traccar.rt.RealTimeManager is not a Spring Bean and is managed by the RealTimeContoller!

Traccar User Management

This is an ITest guide through Traccar’s User Management

Server Roles

Let’s have another look at the ScenarioLoaderIT.

@Before the tests are started the Traccar Docker Container is started. At this point the server is launched from its original distribution. This implies that there is no registered User at this point to take control. A hen and egg problem.

virtualAdmin

Therefor we have setup a virtualAdmin, which is predefined in the traccar.xml file with

<entry key='web.serviceAccountToken'>VIRTUAL_ADMIN_ACCESS</entry>

then this value is matched in the application.properties

traccar.web.serviceAccountToken=VIRTUAL_ADMIN_ACCESS

and we can login to the server with

api.setBearerToken(virtualAdmin);

to create an Admin User defined in the prop file.

browser validation

You can debug the ITests and set a breakpoint before the scenario is torn down.
Then you can login to the Traccar frontend via browser URL

http://localhost/?token=VIRTUAL_ADMIN_ACCESS

and check the status of users and devices.
Don’t forget to resume teardown!

User Account

The ROAF is about Tracking Devices being tracked by Traccar GTS.
Traccar is a multi client user system with a detailed permission structure that we want to use.
We have setup the serviceAccount password to access the fresh server.
The first step is to create a Traccar Account represented by a User - the admin.

admin

Admin - superuser with full unlimited access to the whole Traccar server.

— see https://www.traccar.org/user-management/

The admin User is created on the server straight forward with minimal attributes

admin = api.getUsersApi().createUserWithCredentials(name, password, email, administrator);

This is the technical service administrator of the Traccar GTS, is not part of the scenario and has no restrictions.
So we can leave the virtualAdmin behind and log in as admin

api.setBasicAuth(admin.getEmail(), props.getUser().get(0).password);

Note that we fetch the password from the user in the prop file, since passwords are not transfered from server.

Scenario Users

After we have setup the admin we will use this account to setup the scenario

    // continue as admin who is not part of the scenario!
    api.setBasicAuth(admin.getEmail(), admin.getPassword());
    createScenarioUsers();
    createScenarioDevices();

Traccar’s User Management distiguishes three user roles

Traccar user management model supports use cases from simple individual user accounts
to large organizations with multiple managers, administrators and regular users.

— see https://www.traccar.org/user-management/

manager

Manager - user with extended capabilities allowing them
to manage a subset of users and register new ones.
The difference between manager and regular user is in their user limit value.
A manager has the user limit not equals to 0.

— see https://www.traccar.org/user-management/

We want one manager for each scenario. Or a game master for each game.
He can observe the scenario and interfere like a referee, even disqualify and take devices out of the scenario.

Again we can apply createUserWithCredentials to create a User without admin rights and then we can update/upgrade the User to become a manager:

    // provide permissions
    //  0: Cannot create any users/devices.              regular user
    // -1: Can create an unlimited number of users/devices.   manager
    //  N: Can create up to N users/devices.                  manager
    manager.setUserLimit(2);
    manager.setDeviceLimit(3);
    api.getUsersApi().updateUser(manager.getId(), manager);

Now the manager can log into the frontend and create two Users and two Devices.
If he tries to add another one the frontend will report Manager user limit reached.

If you’ll have a look at the @Test managerUserLimit() in UsersApiIT you can see how a - technical - admin creates a manager via REST. Then this manager is authenticated and is used to create three Users with manager.setUserLimit(2)!

Further analysis showed that all created Users belong to the users marked as admin.

To setup a more complex scenario with individual rights we need to work with the PermissionsApi.

Traccar Permissions

Permissions

Permissions = Link an Object to another Object

=> User = user account
http://localhost/api/users > all users as json

Administrators and managers can control permissions for their users from the Users menu in the settings. It is accessible from the Connections menu option in the users list.

Almost all entities in Traccar have associated permissions and can be linked to user accounts. It applies not only to devices, but also things like geofences, notifications etc.

Link an Object to another Object

Authorizations:
BasicAuthApiKey
Request Body schema: application/json
required
userId
integer <int64>
User id, can be only first parameter
deviceId
integer <int64>
Device id, can be first parameter or second only in combination with userId
groupId
integer <int64>
Group id, can be first parameter or second only in combination with userId
geofenceId
integer <int64>
Geofence id, can be second parameter only
notificationId
integer <int64>
Notification id, can be second parameter only
calendarId
integer <int64>
Calendar id, can be second parameter only and only in combination with userId
attributeId
integer <int64>
Computed attribute id, can be second parameter only
driverId
integer <int64>
Driver id, can be second parameter only
managedUserId
integer <int64>
User id, can be second parameter only and only in combination with userId
commandId
integer <int64>
Saved command id, can be second parameter only

Link an Object to another Object

userId       deviceId
userId       groupId
userId      managedUserId
userId      calendarId
userId      geofenceId
deviceId    geofenceId
groupId     geofenceId
userId      notificationId
deviceId    notificationId
groupId     notificationId
userId      attributeId
deviceId    attributeId
groupId     attributeId
userId      driverId
deviceId    driverId
groupId     driverId
userId      commandId
deviceId    commandId
groupId     commandId

Administrators and managers can control permissions for their users from the Users menu in the settings. It is accessible from the Connections menu option in the users list.

When an object is shared between several users, they have full access to it, including the ability to delete it. If you want to avoid it, you can duplicate instances so that each user has their own object. Duplicating a device with the same unique id is not possible, so Traccar provides a special device readonly user setting to restrict users for modifying devices.

Devices

Some entities can be linked to devices. For example, a geofence or a notification can be linked to a device. When linked, it means that it’s associated with the device, but it does not affect user permissions. To provide user access, it also needs to be linked to the user account.

For example, if a geofence is linked to a device, it means that the geofence events will be generated for this device.

Linking can be done from the settings. It is accessible from the Connections menu option in the devices list.

Groups

A group represent a group of devices. It is not possible to group any other entities, but you can link some entities to groups the same way you link them to devices. Same as with devices, linking to a group does not affect user permissions.

For example, if a geofence is linked to a group, it means that the geofence events will be generated for all devices in the group and subgroups.

Linking can be done from the settings. It is accessible from the Connections menu option in the groups list.

To add a device to a group, edit the device and select the Group under the Extra section. Groups can be nested. To add a group to another group, edit the group and select the parent Group under the Extra section.

setup scenario (later in BaseIT)
- create admin > over all users (players) and (their) devices
- create user/s > over his own device/s
- create device/s for admin/user
- login as admin > load all devices
-> one MrX and two/three Detectives

upgrading traccar-api-client

The generation of the Java API Client takes place in the traccar-openapitools-client as a completly independant process and therefor versioned one to one against Traccar. The jar can be used in any project and does not require customization for this repo.
The versioned input openapi.yaml file is defined in the pom file.
The project documentation also describes different formats to be aware of.

v1.1.17 - Traccar v6.10.0 to v6.12.2

After upgrading the Traccar version

<traccar.version>6.12.2</traccar.version>

in the parent pom, the traccar-openapitools-client should compile without further ado.
So you can actually compile the complete repo and wait for compilation problems in the traccar-api-client.

    [ERROR] traccar-api-client/src/main/java/bm/traccar/api/impl/DevicesImpl.java:[40,22]
      method devicesGet in class bm.traccar.generated.api.DevicesApi cannot be applied to given types;
      required: Boolean,    Integer,    Integer,    String, Boolean
         found: <nulltype>, <nulltype>, <nulltype>, String
        reason: actual and formal argument lists differ in length
    [INFO] 1 error

When you open DevicesImpl.java the IDE provides another error message:

The method devicesGet(Boolean, Integer, Integer, String, Boolean) in the type DevicesApi is not applicable
   for the arguments (null,    null,    null,    String         )

You should counter check the differences directly in the Traccar repo: v6.10.0 to v6.12.2
The new! javadoc tells us

Fetch a list of Devices Without any params, returns a list of the user's devices
    List<Device> bm.traccar.generated.api.DevicesApi.devicesGet(
        @Nullable Boolean all,
        @Nullable Integer userId,
        @Nullable Integer id,
        @Nullable String uniqueId,
        @Nullable Boolean excludeAttributes ) throws RestClientResponseException

Luckily we are still prototyping and avoiding bulk implementations and are not getting tempted to direct this to your CoPilot or other AI agent!

In order to test our methods implemented so far we have our Integration Test in place: DevicesApiIT …​

Long story short: During prototyping we want to keep it as simple as possible. For the time being we can simply ignore the new parameters (all being @Nullable) and call the method with null values without any issues.
Therefor we simplify the main API interface method to

List<Device> getDevices(); // GET

and the implementation:

@Override
 public List<Device> getDevices() {
   return devicesApi.devicesGet(null, null, null, null, null);
 }

The ITests will be used without parameters as a generic method.
We can still filter on client side and we can add getDevices methods with descriptive names.

  • ✓ done

If we’re lucky every new version is only a version bump with small changes.
It this case we can wittness a significant change in the API with the addition of new parameters to existing endpoints and the addition of new endpoints.

No need to go into details here, since we implement new API methods on demand.