/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * 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.jkiss.dbeaver.model.impl.sql.edit; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.edit.*; import org.jkiss.dbeaver.model.edit.prop.*; import org.jkiss.dbeaver.model.impl.DBSObjectCache; import org.jkiss.dbeaver.model.impl.ProxyPropertyDescriptor; import org.jkiss.dbeaver.model.impl.edit.AbstractObjectManager; import org.jkiss.dbeaver.model.impl.edit.DBECommandAbstract; import org.jkiss.dbeaver.model.messages.ModelMessages; import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.struct.DBSObject; import org.jkiss.utils.CommonUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * JDBC object editor */ public abstract class SQLObjectEditor<OBJECT_TYPE extends DBSObject, CONTAINER_TYPE extends DBSObject> extends AbstractObjectManager<OBJECT_TYPE> implements DBEObjectEditor<OBJECT_TYPE>, DBEObjectMaker<OBJECT_TYPE, CONTAINER_TYPE> { public static final String PATTERN_ITEM_INDEX = "%INDEX%"; //$NON-NLS-1$ public static final String PATTERN_ITEM_TABLE = "%TABLE%"; //$NON-NLS-1$ public static final String PATTERN_ITEM_INDEX_SHORT = "%INDEX_SHORT%"; //$NON-NLS-1$ public static final String PATTERN_ITEM_CONSTRAINT = "%CONSTRAINT%"; //$NON-NLS-1$ @Override public boolean canEditObject(OBJECT_TYPE object) { return true; } @Override public final DBEPropertyHandler<OBJECT_TYPE> makePropertyHandler(OBJECT_TYPE object, DBPPropertyDescriptor property) { return new PropertyHandler(property); } @Override public boolean canCreateObject(CONTAINER_TYPE parent) { return true; } @Override public boolean canDeleteObject(OBJECT_TYPE object) { return true; } ////////////////////////////////////////////////// // Commands @Override public final OBJECT_TYPE createNewObject(DBRProgressMonitor monitor, DBECommandContext commandContext, CONTAINER_TYPE parent, Object copyFrom) throws DBException { OBJECT_TYPE newObject; try { newObject = createDatabaseObject(monitor, commandContext, parent, copyFrom); } catch (ClassCastException e) { throw new IllegalArgumentException("Can't create object here.\nWrong container type: " + parent.getClass().getSimpleName()); } if (newObject == null) { return null; } final ObjectCreateCommand createCommand = makeCreateCommand(newObject); commandContext.getUserParams().put(newObject, createCommand); commandContext.addCommand(createCommand, new CreateObjectReflector<>(this), true); createObjectReferences(monitor, commandContext, createCommand); return newObject; } protected void createObjectReferences(DBRProgressMonitor monitor, DBECommandContext commandContext, ObjectCreateCommand createCommand) throws DBException { // Do nothing. Derived implementations may add extra handling } @Override public final void deleteObject(DBECommandContext commandContext, OBJECT_TYPE object, Map<String, Object> options) { commandContext.addCommand( new ObjectDeleteCommand(object, ModelMessages.model_jdbc_delete_object), new DeleteObjectReflector<>(this), true); } public ObjectCreateCommand makeCreateCommand(OBJECT_TYPE object) { return new ObjectCreateCommand(object, ModelMessages.model_jdbc_create_new_object); } protected abstract OBJECT_TYPE createDatabaseObject( DBRProgressMonitor monitor, DBECommandContext context, CONTAINER_TYPE parent, Object copyFrom) throws DBException; ////////////////////////////////////////////////// // Actions protected abstract void addObjectCreateActions(List<DBEPersistAction> actions, ObjectCreateCommand command); protected void addObjectModifyActions(List<DBEPersistAction> actionList, ObjectChangeCommand command) { } protected void addObjectExtraActions(List<DBEPersistAction> actions, NestedObjectCommand<OBJECT_TYPE, PropertyHandler> command) { } protected void addObjectRenameActions(List<DBEPersistAction> actions, ObjectRenameCommand command) { // Base SQL syntax do not support object properties change throw new IllegalStateException("Object rename is not supported in " + getClass().getSimpleName()); //$NON-NLS-1$ } protected void addObjectReorderActions(List<DBEPersistAction> actions, ObjectReorderCommand command) { if (command.getObject().isPersisted()) { // Not supported by implementation throw new IllegalStateException("Object reorder is not supported in " + getClass().getSimpleName()); //$NON-NLS-1$ } } protected abstract void addObjectDeleteActions(List<DBEPersistAction> actions, ObjectDeleteCommand command); ////////////////////////////////////////////////// // Properties protected StringBuilder getNestedDeclaration(CONTAINER_TYPE owner, DBECommandAbstract<OBJECT_TYPE> command) { return null; } protected void validateObjectProperty(OBJECT_TYPE object, DBPPropertyDescriptor property, Object value) throws DBException { } protected void validateObjectProperties(ObjectChangeCommand command) throws DBException { } protected void processObjectRename(DBECommandContext commandContext, OBJECT_TYPE object, String newName) throws DBException { ObjectRenameCommand command = new ObjectRenameCommand(object, ModelMessages.model_jdbc_rename_object, newName); commandContext.addCommand(command, new RenameObjectReflector(), true); } protected void processObjectReorder(DBECommandContext commandContext, OBJECT_TYPE object, List<? extends DBPOrderedObject> siblings, int newPosition) throws DBException { ObjectReorderCommand command = new ObjectReorderCommand(object, siblings, ModelMessages.model_jdbc_reorder_object, newPosition); commandContext.addCommand(command, new ReorderObjectReflector(), true); } protected class PropertyHandler extends ProxyPropertyDescriptor implements DBEPropertyHandler<OBJECT_TYPE>, DBEPropertyReflector<OBJECT_TYPE>, DBEPropertyValidator<OBJECT_TYPE> { private PropertyHandler(DBPPropertyDescriptor property) { super(property); } @Override public DBECommandComposite<OBJECT_TYPE, ? extends DBEPropertyHandler<OBJECT_TYPE>> createCompositeCommand(OBJECT_TYPE object) { return new ObjectChangeCommand(object); } @Override public void reflectValueChange(OBJECT_TYPE object, Object oldValue, Object newValue) { } @Override public String toString() { return original.getDisplayName(); } @Override public int hashCode() { return original.getId().hashCode(); } @Override public boolean equals(Object obj) { return obj != null && obj.getClass() == PropertyHandler.class && //editor == ((PropertyHandler)obj).editor && getId().equals(((PropertyHandler) obj).getId()); } @Override public void validate(OBJECT_TYPE object, Object value) throws DBException { validateObjectProperty(object, original, value); } } ////////////////////////////////////////////////// // Command objects protected static abstract class NestedObjectCommand<OBJECT_TYPE extends DBSObject, HANDLER_TYPE extends DBEPropertyHandler<OBJECT_TYPE>> extends DBECommandComposite<OBJECT_TYPE, HANDLER_TYPE> { protected NestedObjectCommand(OBJECT_TYPE object, String title) { super(object, title); } public abstract String getNestedDeclaration(DBSObject owner); } protected static class EmptyCommand extends DBECommandAbstract<DBPObject> { public EmptyCommand(DBPObject object) { super(object, "Empty"); //$NON-NLS-1$ } } protected class ObjectChangeCommand extends NestedObjectCommand<OBJECT_TYPE, PropertyHandler> { public ObjectChangeCommand(OBJECT_TYPE object) { super(object, "JDBC Composite"); //$NON-NLS-1$ } @Override public DBEPersistAction[] getPersistActions() { List<DBEPersistAction> actions = new ArrayList<>(); addObjectModifyActions(actions, this); addObjectExtraActions(actions, this); return actions.toArray(new DBEPersistAction[actions.size()]); } @Override public void validateCommand() throws DBException { validateObjectProperties(this); } @Override public String getNestedDeclaration(DBSObject owner) { // It is a trick // This method may be invoked from another Editor with different OBJECT_TYPE and CONTAINER_TYPE // TODO: May be we should make ObjectChangeCommand static final StringBuilder decl = SQLObjectEditor.this.getNestedDeclaration((CONTAINER_TYPE) owner, this); return CommonUtils.isEmpty(decl) ? null : decl.toString(); } } protected class ObjectCreateCommand extends NestedObjectCommand<OBJECT_TYPE, PropertyHandler> { protected ObjectCreateCommand(OBJECT_TYPE object, String title) { super(object, title); } @Override public DBEPersistAction[] getPersistActions() { List<DBEPersistAction> actions = new ArrayList<>(); addObjectCreateActions(actions, this); addObjectExtraActions(actions, this); return actions.toArray(new DBEPersistAction[actions.size()]); } @Override public void updateModel() { super.updateModel(); OBJECT_TYPE object = getObject(); if (!object.isPersisted()) { if (object instanceof DBPSaveableObject) { ((DBPSaveableObject)object).setPersisted(true); } DBUtils.fireObjectUpdate(object); } } @Override public String getNestedDeclaration(DBSObject owner) { // It is a trick // This method may be invoked from another Editor with different OBJECT_TYPE and CONTAINER_TYPE // TODO: May be we should make ObjectChangeCommand static final StringBuilder decl = SQLObjectEditor.this.getNestedDeclaration((CONTAINER_TYPE) owner, this); return CommonUtils.isEmpty(decl) ? null : decl.toString(); } } protected class ObjectDeleteCommand extends DBECommandDeleteObject<OBJECT_TYPE> { protected ObjectDeleteCommand(OBJECT_TYPE table, String title) { super(table, title); } @Override public DBEPersistAction[] getPersistActions() { List<DBEPersistAction> actions = new ArrayList<>(); addObjectDeleteActions(actions, this); return actions.toArray(new DBEPersistAction[actions.size()]); } @Override public void updateModel() { OBJECT_TYPE object = getObject(); DBSObjectCache<? extends DBSObject, OBJECT_TYPE> cache = getObjectsCache(object); if (cache != null) { cache.removeObject(object, false); } } } protected class ObjectRenameCommand extends DBECommandAbstract<OBJECT_TYPE> { private String oldName; private String newName; public ObjectRenameCommand(OBJECT_TYPE object, String title, String newName) { super(object, title); this.oldName = object.getName(); this.newName = newName; } public String getOldName() { return oldName; } public String getNewName() { return newName; } @Override public DBEPersistAction[] getPersistActions() { List<DBEPersistAction> actions = new ArrayList<>(); addObjectRenameActions(actions, this); return actions.toArray(new DBEPersistAction[actions.size()]); } @Override public DBECommand<?> merge(DBECommand<?> prevCommand, Map<Object, Object> userParams) { // We need very first and very last rename commands. They produce final rename final String mergeId = "rename" + getObject().hashCode(); ObjectRenameCommand renameCmd = (ObjectRenameCommand) userParams.get(mergeId); if (renameCmd == null) { renameCmd = new ObjectRenameCommand(getObject(), getTitle(), newName); userParams.put(mergeId, renameCmd); } else { renameCmd.newName = newName; return renameCmd; } return super.merge(prevCommand, userParams); } } public class RenameObjectReflector implements DBECommandReflector<OBJECT_TYPE, ObjectRenameCommand> { @Override public void redoCommand(ObjectRenameCommand command) { if (command.getObject() instanceof DBPNamedObject2) { ((DBPNamedObject2)command.getObject()).setName(command.newName); DBUtils.fireObjectUpdate(command.getObject()); } } @Override public void undoCommand(ObjectRenameCommand command) { if (command.getObject() instanceof DBPNamedObject2) { ((DBPNamedObject2)command.getObject()).setName(command.oldName); DBUtils.fireObjectUpdate(command.getObject()); } } } protected class ObjectReorderCommand extends DBECommandAbstract<OBJECT_TYPE> { private List<? extends DBPOrderedObject> siblings; private int oldPosition; private int newPosition; public ObjectReorderCommand(OBJECT_TYPE object, List<? extends DBPOrderedObject> siblings, String title, int newPosition) { super(object, title); this.siblings = siblings; this.oldPosition = ((DBPOrderedObject)object).getOrdinalPosition(); this.newPosition = newPosition; } public List<? extends DBPOrderedObject> getSiblings() { return siblings; } public int getOldPosition() { return oldPosition; } public int getNewPosition() { return newPosition; } @Override public DBEPersistAction[] getPersistActions() { List<DBEPersistAction> actions = new ArrayList<>(); addObjectReorderActions(actions, this); return actions.toArray(new DBEPersistAction[actions.size()]); } @Override public DBECommand<?> merge(DBECommand<?> prevCommand, Map<Object, Object> userParams) { // We need very first and very last reorder commands. They produce final rename final String mergeId = "reorder" + getObject().hashCode(); ObjectReorderCommand reorderCmd = (ObjectReorderCommand) userParams.get(mergeId); if (reorderCmd == null) { reorderCmd = new ObjectReorderCommand(getObject(), siblings, getTitle(), newPosition); userParams.put(mergeId, reorderCmd); } else { reorderCmd.newPosition = newPosition; return reorderCmd; } return super.merge(prevCommand, userParams); } } public class ReorderObjectReflector implements DBECommandReflector<OBJECT_TYPE, ObjectReorderCommand> { @Override public void redoCommand(ObjectReorderCommand command) { OBJECT_TYPE object = command.getObject(); // Update positions in sibling objects for (DBPOrderedObject sibling : command.getSiblings()) { if (sibling != object) { int siblingPosition = sibling.getOrdinalPosition(); if (command.newPosition < command.oldPosition) { if (siblingPosition >= command.newPosition && siblingPosition < command.oldPosition) { sibling.setOrdinalPosition(siblingPosition + 1); } } else { if (siblingPosition <= command.newPosition && siblingPosition > command.oldPosition) { sibling.setOrdinalPosition(siblingPosition - 1); } } } } // Update target object position ((DBPOrderedObject) object).setOrdinalPosition(command.newPosition); // Refresh object AND parent final DBSObject parentObject = object.getParentObject(); if (parentObject != null) { // We need to update order in navigator model DBUtils.fireObjectUpdate(parentObject, DBPEvent.REORDER); } } @Override public void undoCommand(ObjectReorderCommand command) { ((DBPOrderedObject)command.getObject()).setOrdinalPosition(command.oldPosition); final DBSObject parentObject = command.getObject().getParentObject(); if (parentObject != null) { // We need to update order in navigator model DBUtils.fireObjectUpdate(parentObject, DBPEvent.REORDER); } } } public static class RefreshObjectReflector<OBJECT_TYPE extends DBSObject> implements DBECommandReflector<OBJECT_TYPE, DBECommandAbstract<OBJECT_TYPE>> { @Override public void redoCommand(DBECommandAbstract<OBJECT_TYPE> command) { DBUtils.fireObjectRefresh(command.getObject()); } @Override public void undoCommand(DBECommandAbstract<OBJECT_TYPE> command) { DBUtils.fireObjectUpdate(command.getObject(), true); } } }