/* * Copyright 2015 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.uberfire.backend.vfs.impl; import java.util.ArrayList; import java.util.List; import javax.enterprise.context.Dependent; import javax.enterprise.event.Observes; import javax.inject.Inject; import org.jboss.errai.common.client.api.annotations.Portable; import org.jboss.errai.ioc.client.container.IOC; import org.jboss.errai.security.shared.api.identity.User; import org.uberfire.backend.vfs.IsVersioned; import org.uberfire.backend.vfs.ObservablePath; import org.uberfire.backend.vfs.Path; import org.uberfire.mvp.Command; import org.uberfire.mvp.ParameterizedCommand; import org.uberfire.rpc.SessionInfo; import org.uberfire.workbench.events.ResourceBatchChangesEvent; import org.uberfire.workbench.events.ResourceChange; import org.uberfire.workbench.events.ResourceCopied; import org.uberfire.workbench.events.ResourceCopiedEvent; import org.uberfire.workbench.events.ResourceDeletedEvent; import org.uberfire.workbench.events.ResourceRenamed; import org.uberfire.workbench.events.ResourceRenamedEvent; import org.uberfire.workbench.events.ResourceUpdatedEvent; @Portable @Dependent public class ObservablePathImpl implements ObservablePath, IsVersioned { private Path path; private transient Path original; @Inject private transient SessionInfo sessionInfo; private transient List<Command> onRenameCommand = new ArrayList<Command>(); private transient List<Command> onDeleteCommand = new ArrayList<Command>(); private transient List<Command> onUpdateCommand = new ArrayList<Command>(); private transient List<Command> onCopyCommand = new ArrayList<Command>(); private transient List<ParameterizedCommand<OnConcurrentRenameEvent>> onConcurrentRenameCommand = new ArrayList<ParameterizedCommand<OnConcurrentRenameEvent>>(); private transient List<ParameterizedCommand<OnConcurrentDelete>> onConcurrentDeleteCommand = new ArrayList<ParameterizedCommand<OnConcurrentDelete>>(); private transient List<ParameterizedCommand<OnConcurrentUpdateEvent>> onConcurrentUpdateCommand = new ArrayList<ParameterizedCommand<OnConcurrentUpdateEvent>>(); private transient List<ParameterizedCommand<OnConcurrentCopyEvent>> onConcurrentCopyCommand = new ArrayList<ParameterizedCommand<OnConcurrentCopyEvent>>(); public ObservablePathImpl() { } public static String removeExtension(final String filename) { if (filename == null) { return null; } final int index = indexOfExtension(filename); if (index == -1) { return filename; } else { return filename.substring(0, index); } } public static int indexOfExtension(final String filename) { if (filename == null) { return -1; } final int extensionPos = filename.lastIndexOf("."); return extensionPos; } @Override public ObservablePath wrap(final Path path) { if (path instanceof ObservablePathImpl) { this.original = ((ObservablePathImpl) path).path; } else { this.original = path; } this.path = this.original; return this; } // Lazy-population of "original" for ObservablePathImpl de-serialized from a serialized PerspectiveDefinition that circumvent the "wrap" feature. // Renamed resources hold a reference to the old "original" Path which is needed to maintain an immutable hashCode used as part of the compound // Key for Activity and Place Management). However re-hydration stores the PartDefinition in a HashSet using the incorrect hashCode. By not // storing the "original" in the serialized form we can guarantee hashCodes in de-serialized PerspectiveDefinitions remain immutable. // See https://bugzilla.redhat.com/show_bug.cgi?id=1200472 for the re-producer. public Path getOriginal() { if (this.original == null) { wrap(this.path); } return this.original; } @Override public String getFileName() { return path.getFileName(); } @Override public String toURI() { return path.toURI(); } @Override public boolean hasVersionSupport() { return path instanceof IsVersioned && ((IsVersioned) path).hasVersionSupport(); } @Override public int compareTo(final Path o) { return path.compareTo(o); } @Override public void onRename(final Command command) { this.onRenameCommand.add(command); } @Override public void onDelete(final Command command) { this.onDeleteCommand.add(command); } @Override public void onUpdate(final Command command) { this.onUpdateCommand.add(command); } @Override public void onCopy(final Command command) { this.onCopyCommand.add(command); } @Override public void onConcurrentRename(final ParameterizedCommand<OnConcurrentRenameEvent> command) { this.onConcurrentRenameCommand.add(command); } @Override public void onConcurrentDelete(final ParameterizedCommand<OnConcurrentDelete> command) { this.onConcurrentDeleteCommand.add(command); } @Override public void onConcurrentUpdate(final ParameterizedCommand<OnConcurrentUpdateEvent> command) { this.onConcurrentUpdateCommand.add(command); } @Override public void onConcurrentCopy(final ParameterizedCommand<OnConcurrentCopyEvent> command) { this.onConcurrentCopyCommand.add(command); } @Override public void dispose() { onRenameCommand.clear(); onDeleteCommand.clear(); onUpdateCommand.clear(); onCopyCommand.clear(); onConcurrentRenameCommand.clear(); onConcurrentDeleteCommand.clear(); onConcurrentUpdateCommand.clear(); onConcurrentCopyCommand.clear(); if (IOC.getBeanManager() != null) { IOC.getBeanManager().destroyBean(this); } } void onResourceRenamed(@Observes final ResourceRenamedEvent renamedEvent) { if (path != null && path.equals(renamedEvent.getPath())) { path = renamedEvent.getDestinationPath(); if (sessionInfo.getId().equals(renamedEvent.getSessionInfo().getId())) { executeRenameCommands(); } else { executeConcurrentRenameCommand(renamedEvent.getPath(), renamedEvent.getDestinationPath(), renamedEvent.getSessionInfo().getId(), renamedEvent.getSessionInfo().getIdentity()); } } } void onResourceDeleted(@Observes final ResourceDeletedEvent deletedEvent) { if (path != null && path.equals(deletedEvent.getPath())) { if (sessionInfo.getId().equals(deletedEvent.getSessionInfo().getId())) { executeDeleteCommands(); } else { executeConcurrentDeleteCommand(deletedEvent.getPath(), deletedEvent.getSessionInfo().getId(), deletedEvent.getSessionInfo().getIdentity()); } } } void onResourceUpdated(@Observes final ResourceUpdatedEvent updatedEvent) { if (path != null && path.equals(updatedEvent.getPath())) { if (sessionInfo.getId().equals(updatedEvent.getSessionInfo().getId())) { executeUpdateCommands(); } else { executeConcurrentUpdateCommand(updatedEvent.getPath(), updatedEvent.getSessionInfo().getId(), updatedEvent.getSessionInfo().getIdentity()); } } } void onResourceCopied(@Observes final ResourceCopiedEvent copiedEvent) { if (path != null && path.equals(copiedEvent.getPath())) { if (sessionInfo.getId().equals(copiedEvent.getSessionInfo().getId())) { executeCopyCommands(); } else { executeConcurrentCopyCommand(copiedEvent.getPath(), copiedEvent.getDestinationPath(), copiedEvent.getSessionInfo().getId(), copiedEvent.getSessionInfo().getIdentity()); } } } void onResourceBatchEvent(@Observes final ResourceBatchChangesEvent batchEvent) { if (path != null && batchEvent.containPath(path)) { if (sessionInfo.getId().equals(batchEvent.getSessionInfo().getId())) { for (final ResourceChange change : batchEvent.getChanges(path)) { switch (change.getType()) { case COPY: executeCopyCommands(); break; case DELETE: executeDeleteCommands(); break; case RENAME: path = ((ResourceRenamed) change).getDestinationPath(); executeRenameCommands(); break; case UPDATE: executeUpdateCommands(); break; } } } else { for (final ResourceChange change : batchEvent.getChanges(path)) { switch (change.getType()) { case COPY: executeConcurrentCopyCommand(path, ((ResourceCopied) change).getDestinationPath(), batchEvent.getSessionInfo().getId(), batchEvent.getSessionInfo().getIdentity()); break; case DELETE: executeConcurrentDeleteCommand(path, batchEvent.getSessionInfo().getId(), batchEvent.getSessionInfo().getIdentity()); break; case RENAME: path = ((ResourceRenamed) change).getDestinationPath(); executeConcurrentRenameCommand(path, ((ResourceRenamed) change).getDestinationPath(), batchEvent.getSessionInfo().getId(), batchEvent.getSessionInfo().getIdentity()); break; case UPDATE: executeConcurrentUpdateCommand(path, batchEvent.getSessionInfo().getId(), batchEvent.getSessionInfo().getIdentity()); break; } } } } } private void executeRenameCommands() { if (!onRenameCommand.isEmpty()) { for (final Command command : onRenameCommand) { command.execute(); } } } private void executeConcurrentRenameCommand(final Path path, final Path destinationPath, final String sessionId, final User identity) { if (!onConcurrentRenameCommand.isEmpty()) { for (final ParameterizedCommand<OnConcurrentRenameEvent> command : onConcurrentRenameCommand) { final OnConcurrentRenameEvent event = new OnConcurrentRenameEvent() { @Override public Path getSource() { return path; } @Override public Path getTarget() { return destinationPath; } @Override public String getId() { return sessionId; } @Override public User getIdentity() { return identity; } }; command.execute(event); } } } private void executeCopyCommands() { if (!onCopyCommand.isEmpty()) { for (final Command command : onCopyCommand) { command.execute(); } } } private void executeConcurrentCopyCommand(final Path path, final Path destinationPath, final String sessionId, final User identity) { if (!onConcurrentCopyCommand.isEmpty()) { final OnConcurrentCopyEvent copyEvent = new OnConcurrentCopyEvent() { @Override public Path getSource() { return path; } @Override public Path getTarget() { return destinationPath; } @Override public String getId() { return sessionId; } @Override public User getIdentity() { return identity; } }; for (final ParameterizedCommand<OnConcurrentCopyEvent> command : onConcurrentCopyCommand) { command.execute(copyEvent); } } } private void executeUpdateCommands() { if (!onUpdateCommand.isEmpty()) { for (final Command command : onUpdateCommand) { command.execute(); } } } private void executeConcurrentUpdateCommand(final Path path, final String sessionId, final User identity) { if (!onConcurrentUpdateCommand.isEmpty()) { final OnConcurrentUpdateEvent event = new OnConcurrentUpdateEvent() { @Override public Path getPath() { return path; } @Override public String getId() { return sessionId; } @Override public User getIdentity() { return identity; } }; for (final ParameterizedCommand<OnConcurrentUpdateEvent> command : onConcurrentUpdateCommand) { command.execute(event); } } } private void executeDeleteCommands() { if (!onDeleteCommand.isEmpty()) { for (final Command command : onDeleteCommand) { command.execute(); } } } private void executeConcurrentDeleteCommand(final Path path, final String sessionId, final User identity) { if (!onConcurrentDeleteCommand.isEmpty()) { final OnConcurrentDelete event = new OnConcurrentDelete() { @Override public Path getPath() { return path; } @Override public String getId() { return sessionId; } @Override public User getIdentity() { return identity; } }; for (final ParameterizedCommand<OnConcurrentDelete> command : onConcurrentDeleteCommand) { command.execute(event); } } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Path)) { return false; } if (o instanceof ObservablePathImpl) { return this.getOriginal().equals(((ObservablePathImpl) o).getOriginal()); } return this.getOriginal().equals(o); } @Override public int hashCode() { return this.getOriginal().toURI().hashCode(); } @Override public String toString() { return toURI(); } }