/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* 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
*
* http://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 org.kie.workbench.common.screens.datamodeller.client.widgets.editor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Dependent;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
import org.gwtbootstrap3.client.ui.constants.ButtonType;
import org.jboss.errai.common.client.api.Caller;
import org.jboss.errai.common.client.api.RemoteCallback;
import org.kie.workbench.common.screens.datamodeller.client.DataModelerContext;
import org.kie.workbench.common.screens.datamodeller.client.DataModelerErrorCallback;
import org.kie.workbench.common.screens.datamodeller.client.command.AddPropertyCommand;
import org.kie.workbench.common.screens.datamodeller.client.command.DataModelCommand;
import org.kie.workbench.common.screens.datamodeller.client.command.DataModelCommandBuilder;
import org.kie.workbench.common.screens.datamodeller.client.context.DataModelerWorkbenchContextChangeEvent;
import org.kie.workbench.common.screens.datamodeller.client.handlers.DomainHandler;
import org.kie.workbench.common.screens.datamodeller.client.handlers.DomainHandlerRegistry;
import org.kie.workbench.common.screens.datamodeller.client.resources.i18n.Constants;
import org.kie.workbench.common.screens.datamodeller.client.util.DataModelerUtils;
import org.kie.workbench.common.screens.datamodeller.client.util.ObjectPropertyComparator;
import org.kie.workbench.common.screens.datamodeller.client.util.UIUtil;
import org.kie.workbench.common.screens.datamodeller.client.validation.ValidatorService;
import org.kie.workbench.common.screens.datamodeller.events.ChangeType;
import org.kie.workbench.common.screens.datamodeller.events.DataModelerEvent;
import org.kie.workbench.common.screens.datamodeller.events.DataObjectChangeEvent;
import org.kie.workbench.common.screens.datamodeller.events.DataObjectFieldChangeEvent;
import org.kie.workbench.common.screens.datamodeller.events.DataObjectFieldCreatedEvent;
import org.kie.workbench.common.screens.datamodeller.events.DataObjectFieldDeletedEvent;
import org.kie.workbench.common.screens.datamodeller.events.DataObjectFieldSelectedEvent;
import org.kie.workbench.common.screens.datamodeller.events.DataObjectSelectedEvent;
import org.kie.workbench.common.screens.datamodeller.model.maindomain.MainDomainAnnotations;
import org.kie.workbench.common.screens.datamodeller.service.DataModelerService;
import org.kie.workbench.common.services.datamodeller.core.DataModel;
import org.kie.workbench.common.services.datamodeller.core.DataObject;
import org.kie.workbench.common.services.datamodeller.core.ObjectProperty;
import org.uberfire.backend.vfs.Path;
import org.uberfire.client.mvp.LockRequiredEvent;
import org.uberfire.client.mvp.PlaceManager;
import org.uberfire.ext.editor.commons.client.validation.ValidatorCallback;
import org.uberfire.ext.editor.commons.client.validation.ValidatorWithReasonCallback;
import org.uberfire.ext.widgets.common.client.resources.i18n.CommonConstants;
import org.uberfire.mvp.Command;
import org.uberfire.mvp.impl.PathPlaceRequest;
@Dependent
public class DataObjectBrowser
implements IsWidget,
DataObjectBrowserView.Presenter {
protected DataModelCommandBuilder commandBuilder;
protected DataObject dataObject;
protected DataModelerContext context;
protected ListDataProvider<ObjectProperty> dataProvider = new ListDataProvider<ObjectProperty>( new ArrayList<ObjectProperty>( ) );
protected ValidatorService validatorService;
protected DomainHandlerRegistry handlerRegistry;
protected Caller<DataModelerService> modelerService;
protected Event<DataModelerEvent> dataModelerEvent;
protected Event<DataModelerWorkbenchContextChangeEvent> dataModelerWBContextEvent;
protected Event<LockRequiredEvent> lockRequiredEvent;
protected PlaceManager placeManager;
protected NewFieldPopup newFieldPopup;
protected DataObjectBrowserView view;
protected boolean readonly = true;
@Inject
public DataObjectBrowser( DomainHandlerRegistry handlerRegistry,
DataModelCommandBuilder commandBuilder,
Caller<DataModelerService> modelerService,
ValidatorService validatorService,
Event<DataModelerEvent> dataModelerEvent,
Event<DataModelerWorkbenchContextChangeEvent> dataModelerWBContextEvent,
Event<LockRequiredEvent> lockRequiredEvent,
PlaceManager placeManager,
NewFieldPopup newFieldPopup,
DataObjectBrowserView view ) {
this.handlerRegistry = handlerRegistry;
this.commandBuilder = commandBuilder;
this.modelerService = modelerService;
this.validatorService = validatorService;
this.dataModelerEvent = dataModelerEvent;
this.dataModelerWBContextEvent = dataModelerWBContextEvent;
this.lockRequiredEvent = lockRequiredEvent;
this.placeManager = placeManager;
this.newFieldPopup = newFieldPopup;
this.view = view;
view.init( this );
view.setTableHeight( DataObjectBrowserHelper.calculateTableHeight( 0 ) );
view.setDataProvider( dataProvider );
}
@PostConstruct
protected void init() {
setReadonly( true );
newFieldPopup.addPopupHandler( new NewFieldPopupView.NewFieldPopupHandler() {
@Override
public void onCreate( String fieldName,
String fieldLabel,
String type,
boolean multiple ) {
onCreateNewProperty( dataObject,
DataModelerUtils.unCapitalize( fieldName ),
fieldLabel,
type,
multiple,
true );
}
@Override
public void onCreateAndContinue( String fieldName,
String fieldLabel,
String type,
boolean multiple ) {
onCreateNewProperty( dataObject,
DataModelerUtils.unCapitalize( fieldName ),
fieldLabel,
type,
multiple,
false );
}
@Override
public void onCancel() {
newFieldPopup.hide();
}
} );
}
@Override
public Widget asWidget() {
return view.asWidget();
}
public DataModelerContext getContext() {
return context;
}
public void setContext( DataModelerContext context ) {
this.context = context;
if ( context != null ) {
setDataObject( context.getDataObject() );
setReadonly( context.isReadonly() );
} else {
setReadonly( true );
}
}
private void onCreateNewProperty( final DataObject dataObject,
final String propertyName,
final String propertyLabel,
final String propertyType,
final Boolean isMultiple,
final boolean closePopup ) {
if ( dataObject != null ) {
validatorService.isValidIdentifier( propertyName, new ValidatorCallback() {
@Override
public void onFailure() {
newFieldPopup.setErrorMessage( Constants.INSTANCE.validation_error_invalid_object_attribute_identifier( propertyName ) );
}
@Override
public void onSuccess() {
validatorService.isUniqueAttributeName( propertyName, dataObject, new ValidatorWithReasonCallback() {
@Override
public void onFailure() {
showFailure( ValidatorService.MANAGED_PROPERTY_EXISTS );
}
@Override
public void onFailure( String reason ) {
showFailure( reason );
}
private void showFailure( String reason ) {
if ( ValidatorService.UN_MANAGED_PROPERTY_EXISTS.equals( reason ) ) {
ObjectProperty unmanagedProperty = getDataObject().getUnManagedProperty( propertyName );
newFieldPopup.setErrorMessage( Constants.INSTANCE.validation_error_object_un_managed_attribute_already_exists( unmanagedProperty.getName(), unmanagedProperty.getClassName() ) );
} else {
newFieldPopup.setErrorMessage( Constants.INSTANCE.validation_error_object_attribute_already_exists( propertyName ) );
}
}
@Override
public void onSuccess() {
if ( propertyType != null && !"".equals( propertyType ) && !UIUtil.NOT_SELECTED.equals( propertyType ) ) {
boolean multiple = isMultiple && !getContext().getHelper().isPrimitiveType( propertyType ); //extra check
addNewProperty( getDataObject(), propertyName, propertyLabel, propertyType, multiple );
if ( closePopup ) {
newFieldPopup.hide();
} else {
newFieldPopup.resetInput();
}
} else {
newFieldPopup.setErrorMessage( Constants.INSTANCE.validation_error_missing_object_attribute_type() );
}
}
} );
}
} );
}
}
private void setDataObject( DataObject dataObject ) {
this.dataObject = dataObject;
setObjectSelectorLabel( dataObject );
List<ObjectProperty> dataObjectProperties = ( dataObject != null ) ?
DataModelerUtils.getManagedProperties( dataObject ) : Collections.<ObjectProperty>emptyList();
ArrayList<ObjectProperty> sortBuffer = new ArrayList<ObjectProperty>();
if ( dataObject != null ) {
sortBuffer.addAll( dataObjectProperties );
}
Collections.sort( sortBuffer, new ObjectPropertyComparator( "name", true ) );
adjustTableSize( sortBuffer.size() );
dataProvider.getList().clear();
dataProvider.getList().addAll( sortBuffer );
view.redrawTable();
}
private void adjustTableSize( int rows ) {
int height = DataObjectBrowserHelper.calculateTableHeight( rows );
int currentHeight = view.getTableHeight();
if ( height != currentHeight ) {
view.setTableHeight( height );
}
}
private void addNewProperty( DataObject dataObject,
final String propertyName,
final String propertyLabel,
final String propertyType,
final Boolean isMultiple ) {
AddPropertyCommand command = commandBuilder.buildAddPropertyCommand(
getContext(),
DataModelerEvent.DATA_OBJECT_BROWSER,
dataObject, propertyName, propertyLabel,
propertyType, isMultiple );
command.execute();
ObjectProperty property = command.getProperty();
adjustTableSize( dataProvider.getList().size() + 1 );
dataProvider.getList().add( property );
view.setSelectedRow( property, true );
executePostCommandProcessing( command );
}
public void onDeleteProperty( final ObjectProperty objectProperty,
final int index ) {
checkUsagesAndDeleteDataObjectProperty( objectProperty, index );
}
private void deleteProperty( final ObjectProperty objectProperty,
final int index ) {
if ( dataObject != null ) {
adjustTableSize( dataProvider.getList().size() -1 );
dataObject.getProperties().remove( objectProperty );
dataProvider.getList().remove( index );
dataProvider.flush();
dataProvider.refresh();
getContext().getHelper().dataObjectUnReferenced( objectProperty.getClassName(), dataObject.getClassName() );
notifyFieldDeleted( objectProperty );
if ( dataProvider.getList().size() == 0 ) {
context.setObjectProperty( null );
notifyObjectSelected();
} else {
int nextSelectedRow = index > 0 ? ( index - 1 ) : 0;
view.setSelectedRow( dataProvider.getList().get( nextSelectedRow ), true );
}
}
}
private void checkUsagesAndDeleteDataObjectProperty( final ObjectProperty objectProperty,
final int index ) {
final String className = dataObject.getClassName();
final String fieldName = objectProperty.getName();
if ( getContext() != null ) {
final Path currentPath = getContext().getEditorModelContent() != null ? getContext().getEditorModelContent().getPath() : null;
modelerService.call( new RemoteCallback<List<Path>>() {
@Override
public void callback( List<Path> paths ) {
if ( paths != null && paths.size() > 0 ) {
//If usages for this field were detected in project assets
//show the confirmation message to the user.
view.showUsagesPopupForDeletion(
Constants.INSTANCE.modelEditor_confirm_deletion_of_used_field( objectProperty.getName() ),
paths,
new Command() {
@Override
public void execute() {
deleteProperty( objectProperty, index );
}
},
new Command() {
@Override
public void execute() {
//do nothing.
}
}
);
} else {
//no usages, just proceed with the deletion.
deleteProperty( objectProperty, index );
}
}
} ).findFieldUsages( currentPath, className, fieldName );
}
}
private void setObjectSelectorLabel( DataObject dataObject ) {
String label = dataObject != null ? DataModelerUtils.getDataObjectFullLabel( dataObject, false ) : "";
String title = dataObject != null ? dataObject.getClassName() : "";
view.setObjectSelectorLabel( label, title );
}
public DataModel getDataModel() {
return getContext() != null ? getContext().getDataModel() : null;
}
public DataObject getDataObject() {
return dataObject;
}
public void onSelectPropertyType( ObjectProperty property ) {
DataObject dataObject = getDataModel().getDataObject( property.getClassName() );
if ( dataObject != null ) {
openDataObject( dataObject );
}
}
private void setReadonly( boolean readonly ) {
this.readonly = readonly;
view.setReadonly( readonly );
}
public boolean isReadonly() {
return readonly;
}
private void executePostCommandProcessing( DataModelCommand command ) {
List<DomainHandler> handlers = handlerRegistry.getDomainHandlers();
for ( DomainHandler handler : handlers ) {
handler.postCommandProcessing( command );
}
}
public void redrawFields() {
view.redrawTable();
}
@Override
public void onSelectCurrentDataObject() {
ObjectProperty currentSelection = view.getSelectedRow();
if ( currentSelection != null ) {
//If we are about to go to the data object editor
//and a row was selected we must un-select it.
view.setSelectedRow( currentSelection, false );
}
context.setObjectProperty( null );
notifyObjectSelected();
}
@Override
public void onNewProperty() {
lockRequiredEvent.fire( new LockRequiredEvent() );
if ( getContext() != null ) {
newFieldPopup.init( getContext() );
newFieldPopup.show();
}
}
@Override
public void onSelectProperty( ObjectProperty selectedProperty ) {
if ( selectedProperty != null ) {
context.setObjectProperty( selectedProperty );
notifyFieldSelected();
}
}
@Override
public void onSortByName( boolean ascending ) {
sortTable( new ObjectPropertyComparator( "name", ascending ) );
}
@Override
public void onSortByLabel( boolean ascending ) {
sortTable( new ObjectPropertyComparator( "label", ascending ) );
}
@Override
public void onSortByType( boolean ascending ) {
sortTable( new ObjectPropertyComparator( "className", ascending ) );
}
private void sortTable( Comparator<ObjectProperty> comparator ) {
Collections.sort( dataProvider.getList(), comparator );
}
@Override
public String getPropertyTypeDisplayValue( ObjectProperty property ) {
String displayName = property.getClassName();
if ( property.isBaseType() ) {
displayName = DataModelerUtils.extractClassName( displayName );
} else {
String label = getContext().getHelper().getObjectLabelByClassName( displayName );
if ( label != null && !"".equals( label ) ) {
displayName = label;
}
}
if ( property.isMultiple() ) {
displayName += " [" + Constants.INSTANCE.objectBrowser_typeLabelMultiple() + "]";
}
return displayName;
}
@Override
public boolean isSelectablePropertyType( ObjectProperty property ) {
return !property.isBaseType() &&
!getDataObject().getClassName().equals( property.getClassName() ) &&
!getDataModel().isExternal( property.getClassName() );
}
private void onDataObjectChange( @Observes DataObjectChangeEvent event ) {
if ( event.isFromContext( context != null ? context.getContextId() : null ) ) {
if ( event.getChangeType() == ChangeType.CLASS_NAME_CHANGE ||
event.getChangeType() == ChangeType.PACKAGE_NAME_CHANGE ||
event.getChangeType() == ChangeType.OBJECT_NAME_CHANGE ||
MainDomainAnnotations.LABEL_ANNOTATION.equals( event.getAnnotationClassName() )
) {
setObjectSelectorLabel( dataObject );
// For self references: in case name or package changes redraw properties table
dataProvider.refresh();
dataProvider.flush();
}
}
}
private void onDataObjectPropertyChange( @Observes DataObjectFieldChangeEvent event ) {
if ( event.isFromContext( context != null ? context.getContextId() : null ) ) {
if ( event.getChangeType() == ChangeType.FIELD_NAME_CHANGE ||
event.getChangeType() == ChangeType.FIELD_TYPE_CHANGE ||
event.getChangeType() == ChangeType.FIELD_ANNOTATION_VALUE_CHANGE ||
event.getChangeType() == ChangeType.FIELD_ANNOTATION_ADD_CHANGE ||
event.getChangeType() == ChangeType.FIELD_ANNOTATION_REMOVE_CHANGE ) {
List<ObjectProperty> props = dataProvider.getList();
for ( int i = 0; i < props.size(); i++ ) {
if ( event.getCurrentField() == props.get( i ) ) {
view.redrawRow( i );
break;
}
}
}
}
}
private void onDataObjectFieldCreated( @Observes DataObjectFieldCreatedEvent event ) {
if ( event.isFromContext( context != null ? context.getContextId() : null ) &&
!DataModelerEvent.DATA_OBJECT_BROWSER.equals( event.getSource() ) ) {
setDataObject( dataObject );
}
}
private void notifyFieldDeleted( ObjectProperty deletedProperty ) {
DataObjectFieldDeletedEvent dataObjectFieldDeletedEvent = new DataObjectFieldDeletedEvent( getContext().getContextId(), DataModelerEvent.DATA_OBJECT_BROWSER, getDataObject(), deletedProperty );
dataModelerEvent.fire( dataObjectFieldDeletedEvent );
}
private void notifyObjectSelected() {
dataModelerWBContextEvent.fire( new DataModelerWorkbenchContextChangeEvent() );
dataModelerEvent.fire( new DataObjectSelectedEvent( getContext().getContextId(), DataModelerEvent.DATA_MODEL_BROWSER, getDataObject() ) );
}
private void notifyFieldSelected() {
dataModelerWBContextEvent.fire( new DataModelerWorkbenchContextChangeEvent() );
dataModelerEvent.fire( new DataObjectFieldSelectedEvent( getContext().getContextId(), DataModelerEvent.DATA_MODEL_BROWSER, getDataObject(), context.getObjectProperty() ) );
}
private void openDataObject( final DataObject dataObject ) {
final Path objectPath = getContext().getDataObjectPath( dataObject.getClassName() );
if ( objectPath != null ) {
view.showBusyIndicator( org.kie.workbench.common.widgets.client.resources.i18n.CommonConstants.INSTANCE.Loading() );
modelerService.call( new RemoteCallback<Boolean>() {
@Override
public void callback( Boolean exists ) {
view.hideBusyIndicator();
if ( Boolean.TRUE.equals( exists ) ) {
placeManager.goTo( new PathPlaceRequest( objectPath ) );
} else {
view.showYesNoCancelPopup( CommonConstants.INSTANCE.Warning(),
Constants.INSTANCE.objectBrowser_message_file_not_exists_or_renamed( objectPath.toURI() ),
new Command() {
@Override
public void execute() {
//do nothing.
}
},
CommonConstants.INSTANCE.Close(),
ButtonType.WARNING,
null,
null,
null,
null,
null,
null
);
}
}
}, new DataModelerErrorCallback( CommonConstants.INSTANCE.ExceptionNoSuchFile0( objectPath.toURI() ) ) ).exists( objectPath );
}
}
public static class DataObjectBrowserHelper {
private static final int ROW_HEIGHT = 30;
/**
* If there are more than 15 rows, let the table be scrolled.
*/
public static final int MAX_ROWS = 15;
public static final int MAX_TABLE_HEIGHT = ( MAX_ROWS + 1 ) * ROW_HEIGHT;
public static final int MIN_TABLE_HEIGHT = 3 * ROW_HEIGHT;
public static int calculateTableHeight( int rows ) {
int height;
if ( rows >= MAX_ROWS ) {
height = MAX_TABLE_HEIGHT;
} else if ( rows == 0 ) {
height = MIN_TABLE_HEIGHT;
} else {
height = (rows + 1) * ROW_HEIGHT;
height = height < MIN_TABLE_HEIGHT ? MIN_TABLE_HEIGHT : height;
}
return height;
}
}
}