| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.netbeans.modules.extbrowser.plugins; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.json.simple.JSONObject; |
| import org.json.simple.JSONValue; |
| import org.json.simple.parser.ParseException; |
| import org.netbeans.modules.extbrowser.chrome.ChromeBrowserImpl; |
| import org.netbeans.modules.web.browser.api.PageInspector; |
| import org.netbeans.modules.web.browser.spi.MessageDispatcher; |
| import org.netbeans.modules.web.browser.spi.MessageDispatcher.MessageListener; |
| import org.netbeans.modules.web.browser.spi.ScriptExecutor; |
| import org.openide.util.Lookup; |
| |
| /** |
| * Script executor for an external browser. |
| * |
| * @author Jan Stola |
| */ |
| public class RemoteScriptExecutor implements ScriptExecutor { |
| /** Logger for this class. */ |
| private static final Logger LOG = Logger.getLogger(RemoteScriptExecutor.class.getName()); |
| // Message attributes |
| private static final String MESSAGE_TYPE = "message"; // NOI18N |
| private static final String MESSAGE_EVAL = "eval"; // NOI18N |
| private static final String MESSAGE_ID = "id"; // NOI18N |
| private static final String MESSAGE_SCRIPT = "script"; // NOI18N |
| private static final String MESSAGE_STATUS = "status"; // NOI18N |
| private static final String MESSAGE_STATUS_OK = "ok"; // NOI18N |
| private static final String MESSAGE_RESULT = "result"; // NOI18N |
| /** ID of the last message sent to the browser plugin. */ |
| private int lastIDSent = 0; |
| /** ID of the last message received from the browser plugin. */ |
| private int lastIDReceived = 0; |
| /** Lock guarding access to the modifiable state of the executor. */ |
| private final Object LOCK = new Object(); |
| /** Results of the executed scripts. It maps message ID to the result. */ |
| private Map<Integer,Object> results = new HashMap<Integer,Object>(); |
| /** Web-browser pane of this executor. */ |
| private ChromeBrowserImpl browserImpl; |
| /** Determines whether the executor was initialized. */ |
| private boolean initialized; |
| /** Determines whether the executor is active (i.e. ready to use). */ |
| private boolean active; |
| |
| /** |
| * Creates a new {@code RemoteScriptExecutor}. |
| * |
| * @param browserImpl web-browser pane of the executor. |
| */ |
| public RemoteScriptExecutor(ChromeBrowserImpl browserImpl) { |
| this.browserImpl = browserImpl; |
| } |
| |
| @Override |
| public Object execute(String script) { |
| synchronized (LOCK) { |
| if (!active) { |
| return ERROR_RESULT; |
| } |
| if (!initialized) { |
| initialize(); |
| } |
| int id = ++lastIDSent; |
| JSONObject message = new JSONObject(); |
| message.put(MESSAGE_TYPE, MESSAGE_EVAL); |
| message.put(MESSAGE_ID, id); |
| message.put(MESSAGE_SCRIPT, script); |
| ExternalBrowserPlugin.getInstance().sendMessage( |
| message.toJSONString(), |
| browserImpl, |
| PageInspector.MESSAGE_DISPATCHER_FEATURE_ID); |
| try { |
| do { |
| LOCK.wait(); |
| } while (!results.containsKey(id)); |
| } catch (InterruptedException iex) { |
| LOG.log(Level.INFO, null, iex); |
| } |
| return results.remove(id); |
| } |
| } |
| |
| /** |
| * Initializes the executor. |
| */ |
| private void initialize() { |
| Lookup lookup = browserImpl.getLookup(); |
| MessageDispatcher dispatcher = lookup.lookup(MessageDispatcher.class); |
| if (dispatcher != null) { |
| dispatcher.addMessageListener(new MessageListener() { |
| @Override |
| public void messageReceived(String featureId, String message) { |
| if (PageInspector.MESSAGE_DISPATCHER_FEATURE_ID.equals(featureId)) { |
| if (message == null) { |
| deactivate(); |
| } else { |
| RemoteScriptExecutor.this.messageReceived(message); |
| } |
| } |
| } |
| }); |
| } else { |
| LOG.log(Level.INFO, "No MessageDispatcher found in ExtBrowserImpl.getLookup()!"); // NOI18N |
| } |
| initialized = true; |
| } |
| |
| /** |
| * Deactivates this executor. All pending results are set to {@code ERROR_RESULT}. |
| */ |
| private void deactivate() { |
| synchronized (LOCK) { |
| if (lastIDReceived < lastIDSent) { |
| int fromID = lastIDReceived+1; |
| LOG.log(Level.INFO, "Executor disposed before responses with IDs {0} to {1} were received!", // NOI18N |
| new Object[]{fromID, lastIDSent}); |
| for (int i=fromID; i<=lastIDSent; i++) { |
| results.put(i, ERROR_RESULT); |
| } |
| lastIDReceived = lastIDSent; |
| } |
| active = false; |
| LOCK.notifyAll(); |
| } |
| } |
| |
| /** |
| * Activates the executor. |
| */ |
| void activate() { |
| synchronized (LOCK) { |
| active = true; |
| } |
| } |
| |
| /** |
| * Called when a message for this executor is received. |
| * |
| * @param messageTxt message for this executor. |
| */ |
| void messageReceived(String messageTxt) { |
| try { |
| JSONObject message = (JSONObject)JSONValue.parseWithException(messageTxt); |
| Object type = message.get(MESSAGE_TYPE); |
| if (MESSAGE_EVAL.equals(type)) { |
| int id = ((Number)message.get(MESSAGE_ID)).intValue(); |
| synchronized (LOCK) { |
| for (int i=lastIDReceived+1; i<id; i++) { |
| LOG.log(Level.INFO, "Haven''t received result of execution of script with ID {0}.", i); // NOI18N |
| results.put(i, ERROR_RESULT); |
| } |
| Object status = message.get(MESSAGE_STATUS); |
| Object result = message.get(MESSAGE_RESULT); |
| if (MESSAGE_STATUS_OK.equals(status)) { |
| results.put(id, result); |
| } else { |
| LOG.log(Level.INFO, "Message with id {0} wasn''t executed successfuly: {1}", // NOI18N |
| new Object[]{id, result}); |
| results.put(id, ERROR_RESULT); |
| } |
| lastIDReceived = id; |
| LOCK.notifyAll(); |
| } |
| } |
| } catch (ParseException ex) { |
| LOG.log(Level.INFO, "Ignoring message that is not in JSON format: {0}", messageTxt); // NOI18N |
| } |
| } |
| |
| } |