/*
* 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.client.mvp;
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.Dependent;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ClosingEvent;
import com.google.gwt.user.client.Window.ClosingHandler;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import org.jboss.errai.security.shared.api.identity.User;
import org.uberfire.backend.vfs.impl.LockInfo;
import org.uberfire.backend.vfs.impl.LockResult;
import org.uberfire.client.resources.i18n.WorkbenchConstants;
import org.uberfire.client.workbench.VFSLockServiceProxy;
import org.uberfire.client.workbench.events.ChangeTitleWidgetEvent;
import org.uberfire.mvp.ParameterizedCommand;
import org.uberfire.workbench.events.NotificationEvent;
import org.uberfire.workbench.events.ResourceAddedEvent;
import org.uberfire.workbench.events.ResourceUpdatedEvent;
/**
* Default implementation of {@link LockManager} using the
* {@link VFSLockServiceProxy} for lock management.
*/
@Dependent
public class LockManagerImpl implements LockManager {
@Inject
private VFSLockServiceProxy lockService;
@Inject
private javax.enterprise.event.Event<ChangeTitleWidgetEvent> changeTitleEvent;
@Inject
private javax.enterprise.event.Event<UpdatedLockStatusEvent> updatedLockStatusEvent;
@Inject
private javax.enterprise.event.Event<NotificationEvent> lockNotification;
@Inject
private LockDemandDetector lockDemandDetector;
@Inject
private User user;
private LockTarget lockTarget;
private LockInfo lockInfo = LockInfo.unlocked();
private HandlerRegistration closeHandler;
private boolean lockRequestPending;
private boolean unlockRequestPending;
private boolean lockSyncComplete;
private List<Runnable> syncCompleteRunnables = new ArrayList<Runnable>();
private Timer reloadTimer;
@Override
public void init(final LockTarget lockTarget) {
this.lockTarget = lockTarget;
final ParameterizedCommand<LockInfo> command = new ParameterizedCommand<LockInfo>() {
@Override
public void execute(final LockInfo lockInfo) {
if (!lockRequestPending && !unlockRequestPending) {
updateLockInfo(lockInfo);
}
}
};
lockService.retrieveLockInfo(lockTarget.getPath(),
command);
}
@Override
public void onFocus() {
publishJsApi();
fireChangeTitleEvent();
fireUpdatedLockStatusEvent();
}
@Override
public void acquireLockOnDemand() {
if (lockTarget == null) {
return;
}
final Widget widget = getLockTargetWidget();
final Element element = widget.getElement();
acquireLockOnDemand(element);
widget.addAttachHandler(new AttachEvent.Handler() {
@Override
public void onAttachOrDetach(AttachEvent event) {
// Handle widget reattachment/reparenting
if (event.isAttached()) {
acquireLockOnDemand(element);
}
}
});
}
public EventListener acquireLockOnDemand(final Element element) {
Event.sinkEvents(element,
lockDemandDetector.getLockDemandEventTypes());
EventListener lockDemandListener = new EventListener() {
@Override
public void onBrowserEvent(Event event) {
if (isLockedByCurrentUser()) {
return;
}
if (lockDemandDetector.isLockRequired(event)) {
acquireLock();
}
}
};
Event.setEventListener(element,
lockDemandListener);
return lockDemandListener;
}
@Override
public void acquireLock() {
if (lockTarget == null) {
return;
}
if (isLockedByCurrentUser()) {
fireChangeTitleEvent();
return;
}
if (lockInfo.isLocked()) {
handleLockFailure(lockInfo);
} else if (!lockRequestPending) {
lockRequestPending = true;
final ParameterizedCommand<LockResult> command = new ParameterizedCommand<LockResult>() {
@Override
public void execute(final LockResult result) {
if (result.isSuccess()) {
updateLockInfo(result.getLockInfo());
releaseLockOnClose();
} else {
handleLockFailure(result.getLockInfo());
}
lockRequestPending = false;
}
};
lockService.acquireLock(lockTarget.getPath(),
command);
}
}
@Override
public void releaseLock() {
final Runnable releaseLock = new Runnable() {
@Override
public void run() {
releaseLockInternal();
}
};
if (lockSyncComplete) {
releaseLock.run();
} else {
syncCompleteRunnables.add(releaseLock);
}
}
private void releaseLockInternal() {
if (isLockedByCurrentUser() && !unlockRequestPending) {
unlockRequestPending = true;
ParameterizedCommand<LockResult> command = new ParameterizedCommand<LockResult>() {
@Override
public void execute(final LockResult result) {
updateLockInfo(result.getLockInfo());
if (result.isSuccess()) {
if (closeHandler != null) {
closeHandler.removeHandler();
}
}
unlockRequestPending = false;
}
};
lockService.releaseLock(lockTarget.getPath(),
command);
}
}
private void releaseLockOnClose() {
closeHandler = Window.addWindowClosingHandler(new ClosingHandler() {
@Override
public void onWindowClosing(ClosingEvent event) {
releaseLock();
}
});
}
private void handleLockFailure(final LockInfo lockInfo) {
if (lockInfo != null) {
updateLockInfo(lockInfo);
lockNotification.fire(new NotificationEvent(WorkbenchConstants.INSTANCE.lockedMessage(lockInfo.lockedBy()),
NotificationEvent.NotificationType.INFO,
true,
lockTarget.getPlace(),
20));
} else {
lockNotification.fire(new NotificationEvent(WorkbenchConstants.INSTANCE.lockError(),
NotificationEvent.NotificationType.ERROR,
true,
lockTarget.getPlace(),
20));
}
// Delay reloading slightly in case we're dealing with a flood of events
if (reloadTimer == null) {
reloadTimer = new Timer() {
public void run() {
reload();
}
};
}
if (!reloadTimer.isRunning()) {
reloadTimer.schedule(250);
}
}
private void reload() {
lockTarget.getReloadRunnable().run();
}
private boolean isLockedByCurrentUser() {
return lockInfo.isLocked() && lockInfo.lockedBy().equals(user.getIdentifier());
}
private void updateLockInfo(@Observes LockInfo lockInfo) {
if (lockTarget != null && lockInfo.getFile().equals(lockTarget.getPath())) {
this.lockInfo = lockInfo;
this.lockSyncComplete = true;
fireChangeTitleEvent();
fireUpdatedLockStatusEvent();
for (Runnable runnable : syncCompleteRunnables) {
runnable.run();
}
syncCompleteRunnables.clear();
}
}
void onResourceAdded(@Observes ResourceAddedEvent res) {
if (lockTarget != null && res.getPath().equals(lockTarget.getPath())) {
releaseLock();
}
}
void onResourceUpdated(@Observes ResourceUpdatedEvent res) {
if (lockTarget != null && res.getPath().equals(lockTarget.getPath())) {
if (!res.getSessionInfo().getIdentity().equals(user)) {
reload();
}
releaseLock();
}
}
void onSaveInProgress(@Observes SaveInProgressEvent evt) {
if (lockTarget != null && evt.getPath().equals(lockTarget.getPath())) {
releaseLock();
}
}
void onLockRequired(@Observes LockRequiredEvent event) {
if (lockTarget != null && isVisible() && !isLockedByCurrentUser()) {
acquireLock();
}
}
private native void publishJsApi()/*-{
var lockManager = this;
$wnd.isLocked = function () {
return lockManager.@org.uberfire.client.mvp.LockManagerImpl::isLocked()();
}
$wnd.isLockedByCurrentUser = function () {
return lockManager.@org.uberfire.client.mvp.LockManagerImpl::isLockedByCurrentUser()();
}
$wnd.acquireLock = function () {
lockManager.@org.uberfire.client.mvp.LockManagerImpl::acquireLock()();
}
$wnd.releaseLock = function () {
lockManager.@org.uberfire.client.mvp.LockManagerImpl::releaseLock()();
}
$wnd.reload = function () {
return lockManager.@org.uberfire.client.mvp.LockManagerImpl::reload()();
}
}-*/;
private Widget getLockTargetWidget() {
final IsWidget isWidget = lockTarget.getWidget();
if (isWidget instanceof Widget) {
return ((Widget) isWidget);
}
return isWidget.asWidget();
}
private boolean isLocked() {
return lockInfo.isLocked();
}
protected LockInfo getLockInfo() {
return lockInfo;
}
protected void fireChangeTitleEvent() {
changeTitleEvent.fire(LockTitleWidgetEvent.create(lockTarget,
lockInfo,
user));
}
protected void fireUpdatedLockStatusEvent() {
if (isVisible()) {
updatedLockStatusEvent.fire(new UpdatedLockStatusEvent(lockInfo.getFile(),
lockInfo.isLocked(),
isLockedByCurrentUser()));
}
}
private boolean isVisible() {
final Widget widget = getLockTargetWidget();
final Element element = widget.getElement();
boolean visible = UIObject.isVisible(element) &&
(element.getAbsoluteLeft() != 0) && (element.getAbsoluteTop() != 0);
return visible;
}
}