ListItem.java
/*
* onedrive-java-sdk - A Java SDK to access OneDrive drives and files.
* Copyright © 2023-2025 Andy Miles (andy.miles@amilesend.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.amilesend.onedrive.resource.site;
import com.amilesend.client.parse.strategy.GsonExclude;
import com.amilesend.onedrive.connection.OneDriveConnection;
import com.amilesend.onedrive.resource.item.BaseItem;
import com.amilesend.onedrive.resource.item.DriveItem;
import com.amilesend.onedrive.resource.item.type.SharePointIds;
import com.amilesend.onedrive.resource.site.response.GetColumnValuesResponse;
import com.amilesend.onedrive.resource.site.type.ContentType;
import com.google.common.annotations.VisibleForTesting;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import okhttp3.RequestBody;
import org.apache.commons.lang3.Validate;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Map;
import static com.amilesend.onedrive.connection.OneDriveConnection.JSON_MEDIA_TYPE;
import static com.amilesend.onedrive.parse.resource.parser.Parsers.FIELD_VALUE_SET_PARSER;
import static com.amilesend.onedrive.parse.resource.parser.Parsers.GET_COLUMN_VALUES_RESPONSE_PARSER;
import static com.amilesend.onedrive.parse.resource.parser.Parsers.newListItemParser;
import static com.amilesend.onedrive.parse.resource.parser.Parsers.newListItemVersionListParser;
import static com.amilesend.onedrive.parse.resource.parser.Parsers.newListItemVersionParser;
import static com.amilesend.onedrive.resource.site.List.LIST_BASE_URL_PATH;
import static com.amilesend.onedrive.resource.site.ListItemVersion.LIST_ITEM_VERSIONS_URL_SUFFIX;
import static com.amilesend.onedrive.resource.site.ListItemVersion.LIST_ITEM_VERSION_BASE_URL_PATH;
import static com.amilesend.onedrive.resource.site.Site.SITE_BASE_URL_PATH;
/**
* Represents an item in a list.
* <p>
* <a href="https://learn.microsoft.com/en-us/onedrive/developer/rest-api/resources/listitem">API Documentation.</a>
* @see List
*/
@EqualsAndHashCode(callSuper=true)
@Getter
@SuperBuilder
@ToString(callSuper = true)
public class ListItem extends BaseItem {
public static final String LIST_ITEM_BASE_URL_PATH = "/items/";
public static final String LIST_ITEM_URL_SUFFIX = "/items";
private static final int MAX_COLUMN_LENGTH = 128;
private static final int MAX_SELECTED_COLUMNS = 12;
/** The key is the name of the column, the value is the associated value of the columns set on this list item. */
private final Map<String, Object> fields;
/** The Sharepoint identifiers. */
private final SharePointIds sharepointIds;
/** For document libraries, the list item as a drive item. */
private final DriveItem driveItem;
/** The associated site identifier. */
@GsonExclude
private final String siteId;
/** The associated list identifier. */
@GsonExclude
private final String listId;
/** The OneDrive API connection. */
@EqualsAndHashCode.Exclude
@GsonExclude
private final OneDriveConnection connection;
/** The content type of this list item. */
@Setter
private ContentType contentType;
/** Indicates if this item is deleted or not (read-only). */
@GsonExclude
private boolean deleted;
//////////////////
// Column values
//////////////////
/**
* Gets the associated columns and its values.
* <p>
* <a href="https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/listitem_get">
* API Documentation</a>.
*
* @return the response
* @see GetColumnValuesResponse
*/
public GetColumnValuesResponse getColumnValues() {
return connection.execute(
connection.newRequestBuilder()
.url(newStringBuilderForListItemUrl()
.append("?expand=fields")
.toString())
.build(),
GET_COLUMN_VALUES_RESPONSE_PARSER);
}
/**
* Gets the selected columns and its values for the given list of column names.
* <p>
* <a href="https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/listitem_get">
* API Documentation</a>.
*
* @param columnsToSelect the list of column names (max 12)
* @return the response
* @see GetColumnValuesResponse
*/
public GetColumnValuesResponse getColumnValues(final java.util.List<String> columnsToSelect) {
final String selectedColumns = validateAndJoinColumns(columnsToSelect);
return connection.execute(
connection.newRequestBuilder()
.url(newStringBuilderForListItemUrl()
.append("?expand=fields(select=")
.append(selectedColumns)
.append(")")
.toString())
.build(),
GET_COLUMN_VALUES_RESPONSE_PARSER);
}
/**
* Updates the defined columns and associated values for this list item. All other values on the list item
* are left alone.
* <p>
* <a href="https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/listitem_update">
* API Documentation</a>.
*
* @param fields the fields to update
* @return the field value set containing all the columns and values
*/
public Map<String, Object> updateColumnValues(final Map<String, Object> fields) {
Validate.notEmpty(fields, "fields must not be blank");
return connection.execute(
connection.newWithBodyRequestBuilder()
.url(newStringBuilderForListItemUrl()
.append("/fields")
.toString())
.patch(RequestBody.create(
connection.getGsonFactory().getInstance(connection).toJson(fields),
JSON_MEDIA_TYPE))
.build(),
FIELD_VALUE_SET_PARSER);
}
////////////////////
// ListItemVersion
////////////////////
/**
* Gets the list of versions for this list item.
* <p>
* <a href="https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/listitem_list_versions">
* API Documentation</a>.
*
* @return the list item versions
*/
public java.util.List<ListItemVersion> getListItemVersions() {
final String siteId = getSiteId();
final String listId = getListId();
final String listItemId = getId();
return connection.execute(
connection.newRequestBuilder()
.url(newStringBuilderForListItemUrl()
.append(LIST_ITEM_VERSIONS_URL_SUFFIX)
.toString())
.build(),
newListItemVersionListParser(siteId, listId, listItemId));
}
/**
* Gets the {@link ListItemVersion} for the given {@code versionId}.
* <p>
* <a href="https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/listitemversion_get">
* API Documentation</a>.
*
* @param versionId the list item version identifier
* @return the list item version
*/
public ListItemVersion getVersion(final String versionId) {
Validate.notBlank(versionId, "versionId must not be blank");
final String siteId = getSiteId();
final String listId = getListId();
final String listItemId = getId();
return connection.execute(
connection.newRequestBuilder()
.url(newStringBuilderForListItemUrl()
.append(LIST_ITEM_VERSION_BASE_URL_PATH)
.append(versionId)
.toString())
.build(),
newListItemVersionParser(siteId, listId, listItemId));
}
//////////
// CRUD
//////////
/**
* Updates this list item (e.g., name, description, parentReference, content type).
* <p>
* <a href="https://learn.microsoft.com/en-us/onedrive/developer/rest-api/resources/listitem">
* API Documentation</a>.
*
* @return the updated list item
*/
public ListItem update() {
final String siteId = getSiteId();
final String listId = getListId();
return connection.execute(
connection.newWithBodyRequestBuilder()
.url(newStringBuilderForListItemUrl().toString())
.patch(RequestBody.create(toJson(), JSON_MEDIA_TYPE))
.build(),
newListItemParser(siteId, listId));
}
/**
* Deletes this item from the list.
* <p>
* <a href="https://learn.microsoft.com/en-us/onedrive/developer/rest-api/resources/listitem">
* API Documentation</a>.
*/
public void delete() {
connection.execute(
connection.newRequestBuilder()
.url(newStringBuilderForListItemUrl().toString())
.delete()
.build());
// Set the deleted state for this list item for consumers that still have reference to the object.
deleted = true;
}
/**
* Gets the JSON representation of this list item.
*
* @return the list item JSON value
*/
@VisibleForTesting
String toJson() {
return connection.getGsonFactory().getInstance(connection).toJson(this);
}
private String validateAndJoinColumns(final java.util.List<String> columns) {
Validate.notEmpty(columns, "columns list must not be empty");
Validate.isTrue(columns.size() <= MAX_SELECTED_COLUMNS,
"cannot select more than " + MAX_SELECTED_COLUMNS + "columns");
final java.util.List<String> encodedColumns = new ArrayList<>(columns.size());
for (final String column : columns) {
Validate.notNull(column, "column name must not be null");
Validate.isTrue(column.length() < MAX_COLUMN_LENGTH);
encodedColumns.add(URLEncoder.encode(column, StandardCharsets.UTF_8));
}
return String.join(",", encodedColumns);
}
private StringBuilder newStringBuilderForListItemUrl() {
return new StringBuilder(connection.getBaseUrl())
.append(SITE_BASE_URL_PATH)
.append(getSiteId())
.append(LIST_BASE_URL_PATH)
.append(getListId())
.append(LIST_ITEM_BASE_URL_PATH)
.append(getId());
}
}