Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions server/libs/modules/components/liferay/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
version="1.0"

dependencies {
implementation("com.github.ben-manes.caffeine:caffeine")
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,138 @@
package com.bytechef.component.liferay.action;

import static com.bytechef.component.definition.ComponentDsl.action;
import static com.bytechef.component.definition.ComponentDsl.dynamicProperties;
import static com.bytechef.component.definition.ComponentDsl.string;
import static com.bytechef.component.definition.ConnectionDefinition.BASE_URI;
import static com.bytechef.component.definition.Context.Http.responseType;
import static com.bytechef.component.liferay.constant.LiferayConstants.APPLICATION;
import static com.bytechef.component.liferay.constant.LiferayConstants.ENDPOINT;
import static com.bytechef.component.liferay.constant.LiferayConstants.PROPERTIES;

import com.bytechef.component.definition.ActionContext;
import com.bytechef.component.definition.ComponentDsl.ModifiableActionDefinition;
import com.bytechef.component.definition.Context;
import com.bytechef.component.definition.Context.Http;
import com.bytechef.component.definition.Context.Http.Body;
import com.bytechef.component.definition.Context.Http.Executor;
import com.bytechef.component.definition.Context.Http.Response;
import com.bytechef.component.definition.Context.Http.ResponseType;
import com.bytechef.component.definition.OptionsDataSource.ActionOptionsFunction;
import com.bytechef.component.definition.Parameters;
import com.bytechef.component.definition.PropertiesDataSource;
import com.bytechef.component.liferay.util.LiferayOptionUtils;
import com.bytechef.component.liferay.util.LiferayPropertiesUtils;
import com.bytechef.component.liferay.util.PropertiesContainer;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* @author Igor Beslic
*/
public class LiferayHeadlessAction {

public static final String ENDPOINT = "endpoint";

public static final ModifiableActionDefinition ACTION_DEFINITION = action("headless")
.title("Headless Api")
public static final ModifiableActionDefinition ACTION_DEFINITION = action("headlessRequest")
.title("Headless Request")
.description("The Headless endpoint to use.")
.properties(
string(APPLICATION)
.label("Application")
.options((ActionOptionsFunction<String>) LiferayOptionUtils::getApplicationsOptions)
.required(true),
string(ENDPOINT)
.label("Endpoint")
.required(true)
.placeholder("headless-portal/v1.0)"))
.options((ActionOptionsFunction<String>) LiferayOptionUtils::getEndpointsOptions)
.optionsLookupDependsOn(APPLICATION)
.required(true),
dynamicProperties(PROPERTIES)
.properties(
(PropertiesDataSource.ActionPropertiesFunction) (
inputParameters, connectionParameters, lookupDependsOnPaths, context) -> {

PropertiesContainer propertiesContainer = LiferayPropertiesUtils.createPropertiesForParameters(
inputParameters.getRequiredString(APPLICATION), inputParameters.getRequiredString(ENDPOINT),
context);

return propertiesContainer.properties();
})
.propertiesLookupDependsOn(APPLICATION, ENDPOINT)
.required(false))
.output()
.perform(LiferayHeadlessAction::perform);

public static Object perform(Parameters inputParameters, Parameters connectionParameters, Context context) {
public static Object perform(Parameters inputParameters, Parameters connectionParameters, ActionContext context) {
String baseUri = connectionParameters.getRequiredString(BASE_URI);
String endpoint = inputParameters.getRequiredString(ENDPOINT);

String endpointUri = baseUri + "/" + endpoint;
String[] endpointParts = endpoint.split(" ");
String method = endpointParts[0];
String endpointUrl = endpointParts[1];

Http.Response response = context.http(http -> http.get(endpointUri))
.configuration(Http.timeout(Duration.ofMillis(inputParameters.getInteger("timeout", 10000))))
.execute();
String endpointUri = baseUri + "/o/" + inputParameters.getRequiredString(APPLICATION) + endpointUrl;

Map<String, ?> properties = inputParameters.getMap(PROPERTIES);

Map<String, ?> body = Map.of();
Map<String, List<String>> headers = Map.of();
Map<String, List<String>> queryParameters = Map.of();
Map<String, ?> pathParameters = Map.of();

PropertiesContainer propertiesContainer = LiferayPropertiesUtils.createPropertiesForParameters(
inputParameters.getRequiredString(APPLICATION), inputParameters.getRequiredString(ENDPOINT),
context);

if (properties != null) {
body = propertiesContainer.bodyParameters()
.stream()
.filter(properties::containsKey)
.collect(Collectors.toMap(p -> p, p -> String.valueOf(properties.get(p))));

int statusCode = response.getStatusCode();
headers = propertiesContainer.headerParameters()
.stream()
.filter(properties::containsKey)
.collect(Collectors.toMap(p -> p, p -> List.of(String.valueOf(properties.get(p)))));

if ((statusCode >= 200) && (statusCode < 300)) {
return response.getBody();
queryParameters = propertiesContainer.queryParameters()
.stream()
.filter(properties::containsKey)
.collect(Collectors.toMap(p -> p, p -> List.of(String.valueOf(properties.get(p)))));

pathParameters = propertiesContainer.headerParameters()
.stream()
.filter(properties::containsKey)
.collect(Collectors.toMap(p -> p, p -> String.valueOf(properties.get(p))));
}

context.log(log -> log.warn("Received response code {}, from endpoint {}", statusCode, endpointUri));
for (Map.Entry<String, ?> entry : pathParameters.entrySet()) {
String key = entry.getKey();
String value = String.valueOf(entry.getValue());

endpointUri = endpointUri.replace("{" + key + "}", value);
}

Executor executor = getExecutor(context, method, endpointUri);

Response response = executor.headers(headers)
.queryParameters(queryParameters)
.configuration(Http.timeout(Duration.ofMillis(inputParameters.getInteger("timeout", 10000))))
.configuration(responseType(ResponseType.JSON))
.body(Body.of(body))
.execute();

return response.getBody();
}

return endpoint;
private static Executor getExecutor(Context context, String method, String finalEndpointUri) {
return context.http(http -> switch (method) {
case "GET" -> http.get(finalEndpointUri);
case "POST" -> http.post(finalEndpointUri);
case "PUT" -> http.put(finalEndpointUri);
case "PATCH" -> http.patch(finalEndpointUri);
case "DELETE" -> http.delete(finalEndpointUri);
case "HEAD" -> http.head(finalEndpointUri);
default -> throw new IllegalArgumentException("Unknown HTTP method: " + method);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@
*/
public class LiferayConstants {

public static final String APPLICATION = "application";
public static final String CONTEXT_NAME = "contextName";
public static final String DISCOVER = "discover";
public static final String ENDPOINT = "endpoint";
public static final String GET = "GET";
public static final String NAME = "name";
public static final String METHOD = "method";
public static final String NAME = "name";
public static final String PARAMETERS = "parameters";
public static final String POST = "POST";
public static final String PROPERTIES = "properties";
public static final String SERVICE = "service";
public static final String SERVICES = "services";
public static final String TYPE = "type";
Expand All @@ -43,4 +45,5 @@ public class LiferayConstants {
.propertiesLookupDependsOn(SERVICE)
.properties((ActionPropertiesFunction) LiferayUtils::createParameters)
.required(true);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2025 ByteChef
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bytechef.component.liferay.util;

import static com.bytechef.component.definition.ComponentDsl.option;
import static com.bytechef.component.definition.Context.Http.responseType;
import static com.bytechef.component.liferay.constant.LiferayConstants.APPLICATION;

import com.bytechef.component.definition.Context;
import com.bytechef.component.definition.Context.Http.ResponseType;
import com.bytechef.component.definition.Option;
import com.bytechef.component.definition.Parameters;
import com.bytechef.component.definition.TypeReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* @author Marija Horvat
*/
public class LiferayOptionUtils {

private LiferayOptionUtils() {
}

public static List<Option<String>> getApplicationsOptions(
Parameters inputParameters, Parameters connectionParameters, Map<String, String> lookupDependsOnPaths,
String searchText, Context context) {

List<Option<String>> options = new ArrayList<>();

Map<String, ?> body = context
.http(http -> http.get("/o/openapi/openapi.json"))
.configuration(responseType(ResponseType.JSON))
.execute()
.getBody(new TypeReference<>() {});

if (!(body.get("paths") instanceof Map<?, ?> resources)) {
return List.of();
}

List<String> uniqueApps = resources.keySet()
.stream()
.map(Object::toString)
.filter(path -> path.contains("openapi.{type"))
.map(path -> path.replaceFirst("/", ""))
.map(path -> path.replaceFirst("/openapi\\.\\{type.*}$", ""))
.distinct()
.toList();

for (String app : uniqueApps) {
options.add(option(app, app));
}

return options;
}

public static List<Option<String>> getEndpointsOptions(
Parameters inputParameters, Parameters connectionParameters, Map<String, String> lookupDependsOnPaths,
String searchText, Context context) {

List<Option<String>> options = new ArrayList<>();

Map<String, ?> body = context
.http(http -> http.get("/o/" + inputParameters.getRequiredString(APPLICATION) + "/openapi.json"))
.configuration(responseType(ResponseType.JSON))
.execute()
.getBody(new TypeReference<>() {});

String version = "";

if (body.get("info") instanceof Map<?, ?> info) {
version = String.valueOf(info.get("version"));
}

if (body.get("paths") instanceof Map<?, ?> paths) {
for (Map.Entry<?, ?> pathEntry : paths.entrySet()) {
String endpoint = String.valueOf(pathEntry.getKey());
Object methodsObj = pathEntry.getValue();

if (methodsObj instanceof Map<?, ?> methods) {
for (Map.Entry<?, ?> methodEntry : methods.entrySet()) {
String key = String.valueOf(methodEntry.getKey());

String method = key.toUpperCase();

String label = method + " " + endpoint;

String value = method + " " + endpoint.replaceFirst("/" + version, "");

options.add(option(label, value));
}
}
}
}

return options;
}
}
Loading
Loading