Upgrade to version 7.27.0.Final of drools

export/import of Guided rule template via excell now works
Export of Decision table via excell now works
This commit is contained in:
Nicolas Héron 2019-10-07 16:35:07 +02:00
commit 0c510abd66
11 changed files with 336 additions and 75 deletions

View file

@ -57,7 +57,7 @@ services:
drools-network-dev:
ipv4_address: 172.27.1.15
ports:
- 8080:12099
- 12099:12099
links:
- runtime-reverse-proxy:reverse-proxy
depends_on:

View file

@ -9,4 +9,6 @@ kie-wb.baseurl=http://kie-wb:8080/kie-wb/rest
kie-wb.username=admin
kie-wb.password=admin
adminConsole.tmpdir=/tmp
spring.servlet.multipart.enabled=false

View file

@ -0,0 +1,169 @@
package org.chtijbug.drools.console.service;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.chtijbug.drools.console.service.model.kie.KieConfigurationData;
import org.chtijbug.drools.console.service.util.AppContext;
import org.chtijbug.drools.console.vaadinComponent.componentView.service.dtmodel.ColumnDefinition;
import org.chtijbug.drools.console.vaadinComponent.componentView.service.dtmodel.DecisionTable;
import org.chtijbug.drools.console.vaadinComponent.componentView.service.dtmodel.GuidedException;
import org.drools.workbench.models.guided.dtable.backend.GuidedDTXMLPersistence;
import org.drools.workbench.models.guided.dtable.shared.model.GuidedDecisionTable52;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@Service
public class DecisionTableExcelService {
@Value("${adminConsole.tmpdir}")
public String tmpDir;
private KieRepositoryService kieRepositoryService;
private KieConfigurationData config;
private UserConnectedService userConnectedService;
private String assetContent;
public DecisionTableExcelService(){
this.kieRepositoryService = AppContext.getApplicationContext().getBean(KieRepositoryService.class);
this.config = AppContext.getApplicationContext().getBean(KieConfigurationData.class);
this.userConnectedService = AppContext.getApplicationContext().getBean(UserConnectedService.class);
}
public String getAssetContent() {
assetContent = kieRepositoryService.getAssetSource(config.getKiewbUrl(),
userConnectedService.getUserConnected().getUserName(),
userConnectedService.getUserConnected().getUserPassword(),
userConnectedService.getSpace(),
userConnectedService.getProject(),
userConnectedService.getAsset());
return assetContent;
}
public List<HashMap<String,Object>> importExcel(InputStream inputStream) throws IOException {
Workbook workbook= WorkbookFactory.create(inputStream);
DataFormatter dataFormatter = new DataFormatter();
List<HashMap<String,Object>> result=new ArrayList<>();
for(Sheet sheet: workbook) {
for (Row row: sheet) {
if(row.getRowNum()!=0) {
HashMap<String, Object> tmp = new HashMap<>();
result.add(tmp);
for (Cell cell : row) {
String cellValue = dataFormatter.formatCellValue(cell);
Integer numColumn = cell.getColumnIndex();
Cell title = sheet.getRow(0).getCell(numColumn);
tmp.put(dataFormatter.formatCellValue(title), cellValue);
}
}
}
}
return result;
}
public ByteArrayInputStream exportExcel(String nameTemplate) {
//Récupération des objets JAVA
assetContent = kieRepositoryService.getAssetSource(config.getKiewbUrl(),
userConnectedService.getUserConnected().getUserName(),
userConnectedService.getUserConnected().getUserPassword(),
userConnectedService.getSpace(),
userConnectedService.getProject(),
userConnectedService.getAsset());
GuidedDecisionTable52 model = GuidedDTXMLPersistence.getInstance().unmarshal(assetContent);
try {
DecisionTable decisionTable = new DecisionTable(model);
Workbook workbook = new XSSFWorkbook();
CreationHelper createHelper = workbook.getCreationHelper();
Sheet sheet = workbook.createSheet(nameTemplate);
Font headerFont = workbook.createFont();
headerFont.setFontHeightInPoints((short) 12);
headerFont.setColor(IndexedColors.BLACK.getIndex());
CellStyle headerCellStyle = workbook.createCellStyle();
headerCellStyle.setFont(headerFont);
Row headerRow = sheet.createRow(0);
if (decisionTable.getRows() != null && decisionTable.getRows().size() != 0) {
int columnIndex = 0;
int j=0;
for (ColumnDefinition columnDefinition : decisionTable.getColumnDefinitionList()) {
if (columnDefinition.isHideColumn()==false){
Cell cell = headerRow.createCell(j);
cell.setCellValue(columnDefinition.getHeader());
cell.setCellStyle(headerCellStyle);
j++;
}
columnIndex++;
}
int rowIndex = 1;
for (int i = 0; i < decisionTable.getRows().size(); i++) {
org.chtijbug.drools.console.vaadinComponent.componentView.service.dtmodel.Row row = decisionTable.getRows().get(i);
int k = 0;
int jj=0;
Row r = sheet.createRow(rowIndex);
for (ColumnDefinition columnDefinition : decisionTable.getColumnDefinitionList()) {
if (columnDefinition.isHideColumn()==false) {
Cell cell = r.createCell(k);
cell.setCellValue(row.getRowElements().get(jj).getValue());
cell.setCellStyle(headerCellStyle);
k++;
}
jj++;
}
rowIndex++;
}
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
FileOutputStream fileOutputStream=new FileOutputStream(tmpDir+"/"+nameTemplate+".xlsx");
workbook.write(fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
workbook.write(bos);
} catch (IOException e) {
e.printStackTrace();
}
byte[] barray = bos.toByteArray();
ByteArrayInputStream is = new ByteArrayInputStream(barray);
try {
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
return is;
} catch (GuidedException e) {
e.printStackTrace();
}
return null;
}
}

View file

@ -1,6 +1,5 @@
package org.chtijbug.drools.console.service;
import javassist.bytecode.ByteArray;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.chtijbug.drools.console.service.model.kie.KieConfigurationData;
@ -8,27 +7,44 @@ import org.chtijbug.drools.console.service.util.AppContext;
import org.drools.workbench.models.datamodel.rule.InterpolationVariable;
import org.drools.workbench.models.guided.template.backend.RuleTemplateModelXMLPersistenceImpl;
import org.drools.workbench.models.guided.template.shared.TemplateModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.*;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ExcelService {
public class GuidedRuleTemplateExcelService {
@Value("${adminConsole.tmpdir}")
public String tmpDir;
private KieRepositoryService kieRepositoryService;
private KieConfigurationData config;
private UserConnectedService userConnectedService;
private String assetContent;
public ExcelService(){
public GuidedRuleTemplateExcelService(){
this.kieRepositoryService = AppContext.getApplicationContext().getBean(KieRepositoryService.class);
this.config = AppContext.getApplicationContext().getBean(KieConfigurationData.class);
this.userConnectedService = AppContext.getApplicationContext().getBean(UserConnectedService.class);
}
public String getAssetContent() {
assetContent = kieRepositoryService.getAssetSource(config.getKiewbUrl(),
userConnectedService.getUserConnected().getUserName(),
userConnectedService.getUserConnected().getUserPassword(),
userConnectedService.getSpace(),
userConnectedService.getProject(),
userConnectedService.getAsset());
return assetContent;
}
public List<HashMap<String,Object>> importExcel(InputStream inputStream) throws IOException {
@ -126,7 +142,7 @@ public class ExcelService {
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
FileOutputStream fileOutputStream=new FileOutputStream("/home/guillaume/test.xlsx");
FileOutputStream fileOutputStream=new FileOutputStream(tmpDir+"/"+nameTemplate+".xlsx");
workbook.write(fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();

View file

@ -51,7 +51,7 @@ public class KieRepositoryService {
return RuleTemplateModelXMLPersistenceImpl.getInstance().marshal(model);
}
public void updateAssetSource(String url, String username, String password, String spaceName, String projectName, String assetName,List<HashMap<String,Object>> objects) {
public void updateAssetSource(String url, String username, String password, String spaceName, String projectName, String assetName,String assetSource) {
String assetContent = getAssetSource(url,
username,
@ -61,13 +61,11 @@ public class KieRepositoryService {
assetName);
String completeurl = url + "/chtijbug/" + spaceName + "/" + projectName + "/assets/" + assetName + "/source";
String content=pojoToStringMethod(assetContent,objects);
String completeurl = url + "/chtijbug/" + spaceName + "/" + projectName + "/asset/" + assetName + "/source";
logger.info("url moteur reco : " + completeurl);
ResponseEntity response = restTemplateKiewb
.execute(completeurl, HttpMethod.POST, requestCallback(content, username, password), clientHttpResponse -> {
.execute(completeurl, HttpMethod.POST, requestCallback(assetSource, username, password), clientHttpResponse -> {
String extractedResponse = null;
if (clientHttpResponse.getBody() != null) {
Scanner s = new Scanner(clientHttpResponse.getBody()).useDelimiter("\\A");
@ -236,7 +234,13 @@ public class KieRepositoryService {
private RequestCallback requestCallback(final Object content, String username, String password) {
return clientHttpRequest -> {
if (content != null) {
mapper.writeValue(clientHttpRequest.getBody(), content);
if (content instanceof String){
String stringContent = (String)content;
stringContent=stringContent.replace("\"","");
mapper.writeValue(clientHttpRequest.getBody(), stringContent);
}else {
mapper.writeValue(clientHttpRequest.getBody(), content);
}
}
clientHttpRequest.getHeaders().add(
HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

View file

@ -33,7 +33,9 @@ public class GuidedDecisionTableModelTransformer {
try {
DecisionTable decisionTable = new DecisionTable(model);
for (ColumnDefinition columnDefinition : decisionTable.getColumnDefinitionList()) {
assetEdit.addColumn(hashmap -> hashmap.get(columnDefinition.getHeader())).setHeader(columnDefinition.getHeader());
if (columnDefinition.isHideColumn()==false) {
assetEdit.addColumn(hashmap -> hashmap.get(columnDefinition.getHeader())).setHeader(columnDefinition.getHeader());
}
}
fillTable(decisionTable);
} catch (GuidedException e) {

View file

@ -122,6 +122,13 @@ public class ColumnDefinition {
case BOOLEAN:
value = Boolean.toString(cell.getBooleanValue());
break;
case NUMERIC_BIGDECIMAL:
if (cell.getNumericValue()!= null) {
value = cell.getNumericValue().toString();
}else{
value="";
}
break;
case NUMERIC:
if (cell.getNumericValue()!= null) {
value = cell.getNumericValue().toString();

View file

@ -1,34 +1,39 @@
package org.chtijbug.drools.console.view;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.upload.Upload;
import com.vaadin.flow.component.upload.receivers.FileBuffer;
import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
import com.vaadin.flow.component.upload.receivers.MultiFileBuffer;
import com.vaadin.flow.server.StreamResource;
import org.chtijbug.drools.console.service.ExcelService;
import org.chtijbug.drools.console.service.DecisionTableExcelService;
import org.chtijbug.drools.console.service.GuidedRuleTemplateExcelService;
import org.chtijbug.drools.console.service.KieRepositoryService;
import org.chtijbug.drools.console.service.UserConnectedService;
import org.chtijbug.drools.console.service.model.kie.KieConfigurationData;
import org.chtijbug.drools.console.service.util.AppContext;
import org.chtijbug.drools.console.vaadinComponent.ComponentPerso.DialogPerso;
import com.vaadin.flow.component.html.Label;
import org.chtijbug.drools.console.vaadinComponent.componentView.AssetEdit;
import org.drools.workbench.models.datamodel.rule.InterpolationVariable;
import org.drools.workbench.models.guided.template.backend.RuleTemplateModelXMLPersistenceImpl;
import org.drools.workbench.models.guided.template.shared.TemplateModel;
import org.vaadin.olli.FileDownloadWrapper;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EditTemplateView extends VerticalLayout {
private static int GuidedRuleTemplate = 1;
private static int DecisionTable = 2;
private int tableType;
private Button exportExcel;
@ -36,9 +41,14 @@ public class EditTemplateView extends VerticalLayout {
private Label title;
private Button saveButton;
private AssetEdit assetEdit;
private ExcelService excelService;
private GuidedRuleTemplateExcelService guidedRuleTemplateExcelService;
private DecisionTableExcelService decisionTableExcelService;
private KieRepositoryService kieRepositoryService;
@ -46,10 +56,26 @@ public class EditTemplateView extends VerticalLayout {
private UserConnectedService userConnectedService;
public EditTemplateView(DialogPerso dialogPerso,String nameTemplate){
private String assetName;
excelService= AppContext.getApplicationContext().getBean(ExcelService.class);
kieRepositoryService=AppContext.getApplicationContext().getBean(KieRepositoryService.class);
public String getAssetContent(){
if (tableType==GuidedRuleTemplate){
return guidedRuleTemplateExcelService.getAssetContent();
}else{
return decisionTableExcelService.getAssetContent();
}
}
public EditTemplateView(DialogPerso dialogPerso, String nameTemplate) {
this.assetName=nameTemplate;
if (nameTemplate.contains(".gdst") == false) {
guidedRuleTemplateExcelService = AppContext.getApplicationContext().getBean(GuidedRuleTemplateExcelService.class);
tableType = GuidedRuleTemplate;
} else {
decisionTableExcelService = AppContext.getApplicationContext().getBean(DecisionTableExcelService.class);
tableType = DecisionTable;
}
kieRepositoryService = AppContext.getApplicationContext().getBean(KieRepositoryService.class);
this.config = AppContext.getApplicationContext().getBean(KieConfigurationData.class);
this.userConnectedService = AppContext.getApplicationContext().getBean(UserConnectedService.class);
@ -59,7 +85,7 @@ public class EditTemplateView extends VerticalLayout {
MemoryBuffer fileBuffer = new MemoryBuffer();
importExcel=new Upload(fileBuffer);
importExcel = new Upload(fileBuffer);
importExcel.setDropLabel(new Span("drag and Drop Excel file here"));
importExcel.setClassName("menu-upload");
importExcel.setId("exampleupload");
@ -68,31 +94,55 @@ public class EditTemplateView extends VerticalLayout {
importExcel.addSucceededListener(succeededEvent -> {
if(!succeededEvent.getFileName().contains("xlsx")){
if (!succeededEvent.getFileName().contains("xlsx")) {
Notification.show("The file is incompatible, it must be in xlsx format");
}else {
} else {
try {
List<HashMap<String, Object>> objects= excelService.importExcel(fileBuffer.getInputStream());
if(objects!=null&&objects.size()>0){
if(objects.get(0).values().size()!=assetEdit.getColumns().size()){
Notification.show("Unable to add columns with the excel import for the moment");
}else {
remove(assetEdit);
assetEdit = new AssetEdit(objects);
add(assetEdit);
if (tableType == GuidedRuleTemplate) {
List<HashMap<String, Object>> objects = guidedRuleTemplateExcelService.importExcel(fileBuffer.getInputStream());
if (objects != null && objects.size() > 0) {
if (objects.get(0).values().size() != assetEdit.getColumns().size()) {
Notification.show("Unable to add columns with the excel import for the moment");
} else {
remove(assetEdit);
assetEdit = new AssetEdit(objects);
add(assetEdit);
String assetSource=this.getAssetContent();
TemplateModel model = RuleTemplateModelXMLPersistenceImpl.getInstance().unmarshal(assetSource);
int id=model.getColsCount();
model.clearRows();
Map<String, List<String>> existingData = model.getTable();
for (Map<String,Object> line : objects){
String[] cols =new String[model.getColsCount()];
int k=0;
for (InterpolationVariable interpolationVariable : model.getInterpolationVariablesList()){
kieRepositoryService.updateAssetSource(config.getKiewbUrl(),
userConnectedService.getUserConnected().getUserName(),
userConnectedService.getUserConnected().getUserPassword(),
userConnectedService.getSpace(),
userConnectedService.getProject(),
userConnectedService.getAsset(),objects);
Object o = line.get(interpolationVariable.getVarName());
cols[k]=o.toString();
k++;
}
model.addRow(cols);
}
model.setIdCol(id);
assetSource = RuleTemplateModelXMLPersistenceImpl.getInstance().marshal(model);
kieRepositoryService.updateAssetSource(config.getKiewbUrl(),
userConnectedService.getUserConnected().getUserName(),
userConnectedService.getUserConnected().getUserPassword(),
userConnectedService.getSpace(),
userConnectedService.getProject(),
userConnectedService.getAsset(), assetSource);
}
} else {
Notification.show("illegible or empty document");
}
} else {
List<HashMap<String, Object>> objects = decisionTableExcelService.importExcel(fileBuffer.getInputStream());
if (objects != null && objects.size() > 0) {
}
}else {
Notification.show("illegible or empty document");
}
} catch (IOException e) {
e.printStackTrace();
@ -103,27 +153,40 @@ public class EditTemplateView extends VerticalLayout {
importExcel.addFailedListener(failedEvent -> {
Notification.show("Error in the upload, please start again with another file");
});
FileDownloadWrapper fileDownloadWrapper=new FileDownloadWrapper(
new StreamResource("fsdfs.xlsx",
()->excelService.exportExcel(nameTemplate)));
exportExcel=new Button("Export excel");
FileDownloadWrapper fileDownloadWrapper = null;
if (tableType == GuidedRuleTemplate) {
fileDownloadWrapper = new FileDownloadWrapper(
new StreamResource(nameTemplate + ".xlsx",
() -> guidedRuleTemplateExcelService.exportExcel(nameTemplate)));
} else {
fileDownloadWrapper = new FileDownloadWrapper(
new StreamResource(nameTemplate + ".xlsx",
() -> decisionTableExcelService.exportExcel(nameTemplate)));
}
exportExcel = new Button("Export excel");
fileDownloadWrapper.wrapComponent(exportExcel);
exportExcel.setClassName("menu-button-asset-edit");
dialogPerso.getBar().add(fileDownloadWrapper);
title=new Label(nameTemplate);
title = new Label(nameTemplate);
title.setClassName("creation-runtime-title");
add(title);
assetEdit=new AssetEdit();
assetEdit = new AssetEdit();
add(assetEdit);
saveButton = new Button("Save");
add(saveButton);
saveButton.addClickListener(new ComponentEventListener<ClickEvent<Button>>() {
@Override
public void onComponentEvent(ClickEvent<Button> buttonClickEvent) {
System.out.println("coucou");
}
});
}
}

View file

@ -3,7 +3,7 @@ kie-wb.baseurl=http://localhost:8080/kie-wb/rest
kie-wb.username=admin
kie-wb.password=admin
adminConsole.tmpdir=/tmp
spring.data.elasticsearch.cluster-nodes=localhost:9300

View file

@ -34,9 +34,6 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.util.*;
@ -126,7 +123,8 @@ public class PackageResource {
return repoNames;
}
private void toto(){
private void toto() {
}
@ -139,14 +137,14 @@ public class PackageResource {
if (workspaceProject.getMainModule() != null) {
Module kmodule = workspaceProject.getMainModule();
EditorModelContent econtent = dataModelerService.loadContent(((KieModule) kmodule).getImportsPath());
DataModelImpl econtentDataModel = (DataModelImpl)econtent.getDataModel();
DataModelImpl econtentDataModel = (DataModelImpl) econtent.getDataModel();
List<DataObject> dataObjects = econtentDataModel.getExternalClasses();
for (DataObject dataObject : dataObjects){
for (DataObject dataObject : dataObjects) {
System.out.println(dataObject.toString());
String className="class="+dataObject.getPackageName()+"."+dataObject.getName();
String className = "class=" + dataObject.getPackageName() + "." + dataObject.getName();
projectResponse.getJavaClasses().add(className);
}
}
projectResponse.setArtifactId(workspaceProject.getMainModule().getPom().getGav().getArtifactId());
projectResponse.setGroupId(workspaceProject.getMainModule().getPom().getGav().getGroupId());
projectResponse.setVersion(workspaceProject.getMainModule().getPom().getGav().getVersion());
@ -293,7 +291,9 @@ public class PackageResource {
return foundElementPath;
}
} else {
if (dotFileFilter.accept(elementPath.getFileName().toString())) {
if (dotFileFilter.accept(elementPath.getFileName().toString())
&& elementPath.getFileName().toString().contains(assetName)) {
System.out.println("coucou");
return elementPath;
}
}
@ -401,7 +401,7 @@ public class PackageResource {
}
@POST
@Path("{organizationalUnitName}/{projectName}/asset/{assetName:.+}}/source")
@Path("{organizationalUnitName}/{projectName}/asset/{assetName}/source")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
public void updateAssetSource(
@PathParam("organizationalUnitName") String organizationalUnitName, @PathParam("projectName") String projectName, @PathParam("assetName") String assetName, String content) {
@ -419,18 +419,16 @@ public class PackageResource {
org.uberfire.java.nio.file.Path elementToUpdate = getFileElementPath(directoryStream, assetName);
File fileToUpdate = elementToUpdate.toFile();
if (fileToUpdate.isFile()) {
FileOutputStream fileOutputStream = new FileOutputStream(fileToUpdate);
fileOutputStream.write(content.getBytes());
fileOutputStream.close();
content = content.replace("\"", "");
ioService.write(elementToUpdate, content);
// FileOutputStream fileOutputStream = new FileOutputStream(fileToUpdate);
// fileOutputStream.write(content.getBytes());
// fileOutputStream.close();
}
}
} catch (RuntimeException e) {
throw new WebApplicationException(e);
} catch (FileNotFoundException e) {
throw new WebApplicationException(e);
} catch (IOException e) {
throw new WebApplicationException(e);
}
}

View file

@ -20,7 +20,7 @@
<packaging>pom</packaging>
<properties>
<jbpm.version>7.26.0.Final</jbpm.version>
<jbpm.version>7.27.0.Final</jbpm.version>
<node.version>v10.16.3</node.version>
<npm.version>6.11.3</npm.version>
<frontend-maven-plugin.version>1.8.0</frontend-maven-plugin.version>
@ -132,9 +132,9 @@
</developers>
<scm>
<connection>scm:git:git@gitlab.pymma-software.com:drools-platform/pymma-jbpm-platform-parent.git</connection>
<url>scm:git:git@gitlab.pymma-software.com:drools-platform/pymma-jbpm-platform-parent.git</url>
<developerConnection>scm:git:git@gitlab.pymma-software.com:drools-platform/pymma-jbpm-platform-parent.git</developerConnection>
<connection>scm:git:git@github.com:pymma/pymma-kie-platform.git</connection>
<url>scm:git:git@github.com:pymma/pymma-kie-platform.git</url>
<developerConnection>scm:git:git@github.com:pymma/pymma-kie-platform.git</developerConnection>
<tag>HEAD</tag>
</scm>