diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java new file mode 100644 index 0000000000..a338e4571c --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -0,0 +1,671 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.jscript; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.StringUtils; + +/** + * Node class specific for use by Template pages that support Bean objects as part of the model. + * The default template engine FreeMarker can use these objects and they are provided to support it. + * A single method is completely freemarker specific - getXmlNodeModel() + *

+ * The class exposes Node properties, children as dynamically populated maps and lists. + *

+ * Various helper methods are provided to access common and useful node variables such + * as the content url and type information. + * + * @author Kevin Roast + */ +public final class Node implements Serializable +{ + private static Log logger = LogFactory.getLog(Node.class); + + private final static String NAMESPACE_BEGIN = "" + QName.NAMESPACE_BEGIN; + private final static String CONTENT_DEFAULT_URL = "/download/direct/{0}/{1}/{2}/{3}"; + private final static String CONTENT_PROP_URL = "/download/direct/{0}/{1}/{2}/{3}?property={4}"; + private final static String FOLDER_BROWSE_URL = "/navigate/browse/{0}/{1}/{2}"; + + /** The children of this node */ + private Node[] children = null; + + /** The associations from this node */ + private ScriptableQNameMap assocs = null; + + /** Cached values */ + private NodeRef nodeRef; + private String name; + private QName type; + private String path; + private String id; + private Set aspects = null; + private ScriptableQNameMap properties; + private boolean propsRetrieved = false; + private ServiceRegistry services = null; + private Boolean isDocument = null; + private Boolean isContainer = null; + private String displayPath = null; + private String mimetype = null; + private Long size = null; + private TemplateImageResolver imageResolver = null; + private Node parent = null; + private ChildAssociationRef primaryParentAssoc = null; + + + /** + * Constructor + * + * @param nodeRef The NodeRef this Node wrapper represents + * @param services The ServiceRegistry the Node can use to access services + * @param resolver Image resolver to use to retrieve icons + */ + public Node(NodeRef nodeRef, ServiceRegistry services, TemplateImageResolver resolver) + { + if (nodeRef == null) + { + throw new IllegalArgumentException("NodeRef must be supplied."); + } + + if (services == null) + { + throw new IllegalArgumentException("The ServiceRegistry must be supplied."); + } + + this.nodeRef = nodeRef; + this.id = nodeRef.getId(); + this.services = services; + this.imageResolver = resolver; + + this.properties = new ScriptableQNameMap(this.services.getNamespaceService()); + } + + /** + * @return The GUID for the node + */ + public String getId() + { + return this.id; + } + + /** + * @return Returns the NodeRef this Node object represents + */ + public NodeRef getNodeRef() + { + return this.nodeRef; + } + + /** + * @return Returns the type. + */ + public QName getType() + { + if (this.type == null) + { + this.type = this.services.getNodeService().getType(this.nodeRef); + } + + return type; + } + + /** + * @return The display name for the node + */ + public String getName() + { + if (this.name == null) + { + // try and get the name from the properties first + this.name = (String)getProperties().get("cm:name"); + + // if we didn't find it as a property get the name from the association name + if (this.name == null) + { + ChildAssociationRef parentRef = this.services.getNodeService().getPrimaryParent(this.nodeRef); + if (parentRef != null && parentRef.getQName() != null) + { + this.name = parentRef.getQName().getLocalName(); + } + else + { + this.name = ""; + } + } + } + + return this.name; + } + + /** + * @return The children of this Node as Node wrappers + */ + public Node[] getChildren() + { + if (this.children == null) + { + List childRefs = this.services.getNodeService().getChildAssocs(this.nodeRef); + this.children = new Node[childRefs.size()]; + for (int i=0; inode.getAssocs()["translations"][0] + * + * @return associations as a Map of assoc name to an Array of Nodes. + */ + public Map getAssocs() + { + if (this.assocs == null) + { + List refs = this.services.getNodeService().getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL); + this.assocs = new ScriptableQNameMap(this.services.getNamespaceService()); + for (AssociationRef ref : refs) + { + String qname = ref.getTypeQName().toString(); + Node[] nodes = (Node[])assocs.get(qname); + if (nodes == null) + { + // first access for the list for this qname + nodes = new Node[1]; + } + else + { + Node[] newNodes = new Node[nodes.length + 1]; + System.arraycopy(nodes, 0, newNodes, 0, nodes.length); + nodes = newNodes; + } + nodes[nodes.length] = new Node(ref.getTargetRef(), this.services, this.imageResolver); + + this.assocs.put(ref.getTypeQName().toString(), nodes); + } + } + + return this.assocs; + } + + /** + * Return all the properties known about this node. + * + * The Map returned implements the Scriptable interface to allow access to the properties via + * JavaScript associative array access. This means properties of a node can be access thus: + * node.getProperties()["name"] + * + * @return Map of properties for this Node. + */ + public Map getProperties() + { + if (this.propsRetrieved == false) + { + Map props = this.services.getNodeService().getProperties(this.nodeRef); + + for (QName qname : props.keySet()) + { + Serializable propValue = props.get(qname); + if (propValue instanceof NodeRef) + { + // NodeRef object properties are converted to new Node objects + // so they can be used as objects within a template + propValue = new Node(((NodeRef)propValue), this.services, this.imageResolver); + } + else if (propValue instanceof ContentData) + { + // ContentData object properties are converted to ScriptContentData objects + // so the content and other properties of those objects can be accessed + propValue = new ScriptContentData((ContentData)propValue, qname); + } + this.properties.put(qname.toString(), propValue); + } + + this.propsRetrieved = true; + } + + return this.properties; + } + + /** + * @return true if this Node is a container (i.e. a folder) + */ + public boolean isContainer() + { + if (isContainer == null) + { + DictionaryService dd = this.services.getDictionaryService(); + isContainer = Boolean.valueOf( (dd.isSubClass(getType(), ContentModel.TYPE_FOLDER) == true && + dd.isSubClass(getType(), ContentModel.TYPE_SYSTEM_FOLDER) == false) ); + } + + return isContainer.booleanValue(); + } + + /** + * @return true if this Node is a Document (i.e. with content) + */ + public boolean isDocument() + { + if (isDocument == null) + { + DictionaryService dd = this.services.getDictionaryService(); + isDocument = Boolean.valueOf(dd.isSubClass(getType(), ContentModel.TYPE_CONTENT)); + } + + return isDocument.booleanValue(); + } + + /** + * @return The list of aspects applied to this node + */ + public Set getAspects() + { + if (this.aspects == null) + { + this.aspects = this.services.getNodeService().getAspects(this.nodeRef); + } + + return this.aspects; + } + + /** + * @param aspect The aspect name to test for + * + * @return true if the node has the aspect false otherwise + */ + public boolean hasAspect(String aspect) + { + if (this.aspects == null) + { + this.aspects = this.services.getNodeService().getAspects(this.nodeRef); + } + + if (aspect.startsWith(NAMESPACE_BEGIN)) + { + return aspects.contains((QName.createQName(aspect))); + } + else + { + boolean found = false; + for (QName qname : this.aspects) + { + if (qname.toPrefixString(this.services.getNamespaceService()).equals(aspect)) + { + found = true; + break; + } + } + return found; + } + } + + /** + * @return Display path to this node + */ + public String getDisplayPath() + { + if (displayPath == null) + { + try + { + displayPath = this.services.getNodeService().getPath(this.nodeRef).toDisplayPath(this.services.getNodeService()); + } + catch (AccessDeniedException err) + { + displayPath = ""; + } + } + + return displayPath; + } + + /** + * @return the small icon image for this node + */ + public String getIcon16() + { + if (this.imageResolver != null) + { + if (isDocument()) + { + return this.imageResolver.resolveImagePathForName(getName(), true); + } + else + { + return "/images/icons/space_small.gif"; + } + } + else + { + return "/images/filetypes/_default.gif"; + } + } + + /** + * @return the large icon image for this node + */ + public String getIcon32() + { + if (this.imageResolver != null) + { + if (isDocument()) + { + return this.imageResolver.resolveImagePathForName(getName(), false); + } + else + { + String icon = (String)getProperties().get("app:icon"); + if (icon != null) + { + return "/images/icons/" + icon + ".gif"; + } + else + { + return "/images/icons/space-icon-default.gif"; + } + } + } + else + { + return "/images/filetypes32/_default.gif"; + } + } + + /** + * @return true if the node is currently locked + */ + public boolean isLocked() + { + boolean locked = false; + + if (getAspects().contains(ContentModel.ASPECT_LOCKABLE)) + { + LockStatus lockStatus = this.services.getLockService().getLockStatus(this.nodeRef); + if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) + { + locked = true; + } + } + + return locked; + } + + /** + * @return the parent node + */ + public Node getParent() + { + if (parent == null) + { + NodeRef parentRef = this.services.getNodeService().getPrimaryParent(nodeRef).getParentRef(); + // handle root node (no parent!) + if (parentRef != null) + { + parent = new Node(parentRef, this.services, this.imageResolver); + } + } + + return parent; + } + + /** + * + * @return the primary parent association so we can get at the association QName and the association type QName. + */ + public ChildAssociationRef getPrimaryParentAssoc() + { + if (primaryParentAssoc == null) + { + primaryParentAssoc = this.services.getNodeService().getPrimaryParent(nodeRef); + } + return primaryParentAssoc; + } + + /** + * @return the content String for this node from the default content property + * (@see ContentModel.PROP_CONTENT) + */ + public String getContent() + { + ContentService contentService = this.services.getContentService(); + ContentReader reader = contentService.getReader(this.nodeRef, ContentModel.PROP_CONTENT); + return (reader != null && reader.exists()) ? reader.getContentString() : ""; + } + + /** + * @return For a content document, this method returns the URL to the content stream for + * the default content property (@see ContentModel.PROP_CONTENT) + *

+ * For a container node, this method return the URL to browse to the folder in the web-client + */ + public String getUrl() + { + if (isDocument() == true) + { + try + { + return MessageFormat.format(CONTENT_DEFAULT_URL, new Object[] { + nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), + nodeRef.getId(), + StringUtils.replace(URLEncoder.encode(getName(), "UTF-8"), "+", "%20") } ); + } + catch (UnsupportedEncodingException err) + { + throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + nodeRef, err); + } + } + else + { + return MessageFormat.format(FOLDER_BROWSE_URL, new Object[] { + nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), + nodeRef.getId() } ); + } + } + + /** + * @return The mimetype encoding for content attached to the node from the default content property + * (@see ContentModel.PROP_CONTENT) + */ + public String getMimetype() + { + if (mimetype == null) + { + ScriptContentData content = (ScriptContentData)this.getProperties().get(ContentModel.PROP_CONTENT); + if (content != null) + { + mimetype = content.getMimetype(); + } + } + + return mimetype; + } + + /** + * @return The size in bytes of the content attached to the node from the default content property + * (@see ContentModel.PROP_CONTENT) + */ + public long getSize() + { + if (size == null) + { + ScriptContentData content = (ScriptContentData)this.getProperties().get(ContentModel.PROP_CONTENT); + if (content != null) + { + size = content.getSize(); + } + } + + return size != null ? size.longValue() : 0L; + } + + /** + * @return the image resolver instance used by this node + */ + public TemplateImageResolver getImageResolver() + { + return this.imageResolver; + } + + /** + * Override Object.toString() to provide useful debug output + */ + public String toString() + { + if (this.services.getNodeService().exists(nodeRef)) + { + return "Node Type: " + getType() + + "\nNode Properties: " + this.getProperties().toString() + + "\nNode Aspects: " + this.getAspects().toString(); + } + else + { + return "Node no longer exists: " + nodeRef; + } + } + + + /** + * Inner class wrapping and providing access to a ContentData property + */ + public class ScriptContentData implements Serializable + { + /** + * Constructor + * + * @param contentData The ContentData object this object wraps + * @param property The property the ContentData is attached too + */ + public ScriptContentData(ContentData contentData, QName property) + { + this.contentData = contentData; + this.property = property; + } + + /** + * @return the content stream + */ + public String getContent() + { + ContentService contentService = services.getContentService(); + ContentReader reader = contentService.getReader(nodeRef, property); + + return (reader != null && reader.exists()) ? reader.getContentString() : ""; + } + + /** + * @return + */ + public String getUrl() + { + try + { + return MessageFormat.format(CONTENT_PROP_URL, new Object[] { + nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), + nodeRef.getId(), + StringUtils.replace(URLEncoder.encode(getName(), "UTF-8"), "+", "%20"), + StringUtils.replace(URLEncoder.encode(property.toString(), "UTF-8"), "+", "%20") } ); + } + catch (UnsupportedEncodingException err) + { + throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + nodeRef, err); + } + } + + public long getSize() + { + return contentData.getSize(); + } + + public String getMimetype() + { + return contentData.getMimetype(); + } + + private ContentData contentData; + private QName property; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/jscript/RhinoScriptService.java b/source/java/org/alfresco/repo/jscript/RhinoScriptService.java new file mode 100644 index 0000000000..ee842eeb6b --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/RhinoScriptService.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.jscript; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.ScriptException; +import org.alfresco.service.cmr.repository.ScriptService; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; + +/** + * Implementation of the ScriptService using the Rhino JavaScript engine. + * + * @author Kevin Roast + */ +public class RhinoScriptService implements ScriptService +{ + /** The permission-safe node service */ + private NodeService nodeService; + + /** The Content Service to use */ + private ContentService contentService; + + /** + * Set the node service + * + * @param nodeService The permission-safe node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the content service + * + * @param contentService The ContentService to use + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptService#executeScript(java.lang.String, java.util.Map) + */ + public Object executeScript(String scriptClasspath, Map model) throws ScriptException + { + if (scriptClasspath == null) + { + throw new IllegalArgumentException("Script ClassPath is mandatory."); + } + + Reader reader = null; + try + { + InputStream stream = getClass().getClassLoader().getResourceAsStream(scriptClasspath); + if (stream == null) + { + throw new AlfrescoRuntimeException("Unable to load classpath resource: " + scriptClasspath); + } + reader = new InputStreamReader(stream); + + return executeScriptImpl(reader, model); + } + catch (Throwable err) + { + throw new ScriptException("Failed to execute script '" + scriptClasspath + "': " + err.getMessage(), err); + } + finally + { + if (reader != null) + { + try {reader.close();} catch (IOException ioErr) {} + } + } + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptService#executeScript(org.alfresco.service.cmr.repository.NodeRef, java.util.Map) + */ + public Object executeScript(NodeRef scriptRef, Map model) throws ScriptException + { + if (scriptRef == null) + { + throw new IllegalArgumentException("Script NodeRef is mandatory."); + } + + Reader reader = null; + try + { + if (this.nodeService.exists(scriptRef) == false) + { + throw new AlfrescoRuntimeException("Script Node does not exist: " + scriptRef); + } + ContentReader cr = this.contentService.getReader(scriptRef, ContentModel.PROP_CONTENT); + if (cr == null || cr.exists() == false) + { + throw new AlfrescoRuntimeException("Script Node content not found: " + scriptRef); + } + reader = new InputStreamReader(cr.getContentInputStream()); + + return executeScriptImpl(reader, model); + } + catch (Throwable err) + { + throw new ScriptException("Failed to execute script '" + scriptRef.toString() + "': " + err.getMessage(), err); + } + finally + { + if (reader != null) + { + try {reader.close();} catch (IOException ioErr) {} + } + } + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptService#executeScriptString(java.lang.String, java.util.Map) + */ + public Object executeScriptString(String script, Map model) throws ScriptException + { + if (script == null || script.length() == 0) + { + throw new IllegalArgumentException("Script argument is mandatory."); + } + + Reader reader = null; + try + { + reader = new StringReader(script); + + return executeScriptImpl(reader, model); + } + catch (Throwable err) + { + throw new ScriptException("Failed to execute supplied script: " + err.getMessage(), err); + } + } + + /** + * Execute the script content from the supplied Reader. Adds the data model into the default + * root scope for access by the script. + * + * @param reader Reader referencing the script to execute. + * @param model Data model containing objects to be added to the root scope. + * + * @return result of the script execution, can be null. + * + * @throws AlfrescoRuntimeException + */ + private Object executeScriptImpl(Reader reader, Map model) + throws AlfrescoRuntimeException + { + // check that rhino script engine is available + Context cx = Context.enter(); + try + { + // The easiest way to embed Rhino is just to create a new scope this way whenever + // you need one. However, initStandardObjects is an expensive method to call and it + // allocates a fair amount of memory. + Scriptable scope = cx.initStandardObjects(); + + // insert supplied object model into root of the default scope + if (model != null) + { + for (String key : model.keySet()) + { + ScriptableObject.putProperty(scope, key, model.get(key)); + } + } + + // execute the script + Object result = cx.evaluateReader(scope, reader, "AlfrescoScript", 1, null); + + return result; + } + catch (Throwable err) + { + throw new AlfrescoRuntimeException(err.getMessage(), err); + } + finally + { + cx.exit(); + } + } +} diff --git a/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java b/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java new file mode 100644 index 0000000000..72132b6c90 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.jscript; + +import java.io.InputStream; +import java.util.List; + +import junit.framework.TestCase; + +import org.alfresco.repo.dictionary.DictionaryComponent; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.node.BaseNodeServiceTest; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.springframework.context.ApplicationContext; + +/** + * @author Kevin Roast + */ +public class RhinoScriptTest extends TestCase +{ + private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private ContentService contentService; + private TemplateService templateService; + private NodeService nodeService; + private TransactionService transactionService; + private ServiceRegistry serviceRegistry; + private AuthenticationComponent authenticationComponent; + + /* + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + + transactionService = (TransactionService)this.ctx.getBean("transactionComponent"); + contentService = (ContentService)this.ctx.getBean("contentService"); + nodeService = (NodeService)this.ctx.getBean("nodeService"); + templateService = (TemplateService)this.ctx.getBean("templateService"); + serviceRegistry = (ServiceRegistry)this.ctx.getBean("ServiceRegistry"); + + this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); + this.authenticationComponent.setSystemUserAsCurrentUser(); + + DictionaryDAO dictionaryDao = (DictionaryDAO)ctx.getBean("dictionaryDAO"); + + // load the system model + ClassLoader cl = BaseNodeServiceTest.class.getClassLoader(); + InputStream modelStream = cl.getResourceAsStream("alfresco/model/contentModel.xml"); + assertNotNull(modelStream); + M2Model model = M2Model.createModel(modelStream); + dictionaryDao.putModel(model); + + // load the test model + modelStream = cl.getResourceAsStream("org/alfresco/repo/node/BaseNodeServiceTest_model.xml"); + assertNotNull(modelStream); + model = M2Model.createModel(modelStream); + dictionaryDao.putModel(model); + + DictionaryComponent dictionary = new DictionaryComponent(); + dictionary.setDictionaryDAO(dictionaryDao); + BaseNodeServiceTest.loadModel(ctx); + } + + @Override + protected void tearDown() throws Exception + { + authenticationComponent.clearCurrentSecurityContext(); + super.tearDown(); + } + + public void testRhino() + { + TransactionUtil.executeInUserTransaction( + transactionService, + new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + StoreRef store = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "rhino_" + System.currentTimeMillis()); + NodeRef root = nodeService.getRootNode(store); + BaseNodeServiceTest.buildNodeGraph(nodeService, root); + + // check that rhino script engine is available + Context cx = Context.enter(); + try + { + // The easiest way to embed Rhino is just to create a new scope this way whenever + // you need one. However, initStandardObjects is an expensive method to call and it + // allocates a fair amount of memory. + Scriptable scope = cx.initStandardObjects(); + + Object wrappedOut = Context.javaToJS(System.out, scope); + ScriptableObject.putProperty(scope, "out", wrappedOut); + + // Now we can evaluate a script. Let's create a new associative array object + // using the object literal notation to create the members + Object result = cx.evaluateString(scope, "obj = {a:1, b:['x','y']}", "TestJS1", 1, null); + + Scriptable obj = (Scriptable)scope.get("obj", scope); + + // Should resolve a non-null value + assertNotNull(obj.get("a", obj)); + // should resolve "obj.a == 1" - JavaScript objects come back as Number + assertEquals(new Double(1.0), obj.get("a", obj)); + + // try another script eval + result = cx.evaluateString(scope, "function f(x) {return x+1} f(7)", "TestJS2", 1, null); + assertEquals(8.0, cx.toNumber(result)); + + // wrap a simple Alfresco NodeRef object + List childAssocs = nodeService.getChildAssocs(root); + NodeRef ref1 = childAssocs.get(0).getChildRef(); + Object wrappedNodeRef = Context.javaToJS(ref1, scope); + ScriptableObject.putProperty(scope, "rootref", wrappedNodeRef); + + // evaluate script that touches the wrapped NodeRef + result = cx.evaluateString(scope, "obj = rootref.getId()", "TestJS3", 1, null); + assertEquals(ref1.getId(), cx.toString(result)); + + // wrap a script Alfresco Node object - the Node object is a wrapper like TemplateNode + Node node1 = new Node(root, serviceRegistry, null); + Object wrappedNode = Context.javaToJS(node1, scope); + ScriptableObject.putProperty(scope, "root", wrappedNode); + + // evaluate scripts that perform methods on the wrapped Node + result = cx.evaluateString(scope, TESTSCRIPT1, "TestJS4", 1, null); + } + catch (Throwable err) + { + err.printStackTrace(); + fail(err.getMessage()); + } + finally + { + cx.exit(); + } + + return null; + } + }); + } + + //private static final String TEMPLATE_1 = "org/alfresco/repo/template/test_template1.ftl"; + private static final String TESTSCRIPT1 = + "var id = root.getId();\r\n" + + "out.println(id);\r\n" + + "var name = root.getName();\r\n" + + "out.println(name);\r\n" + + "var type = root.getType();\r\n" + + "out.println(type);\r\n" + + "var childList = root.getChildren();\r\n" + + "out.println(\"zero index node: \" + childList[0].getName());\r\n" + + "out.println(\"properties: \" + childList[0].getProperties()[\"name\"] );"; +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java b/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java new file mode 100644 index 0000000000..bf4afec1ee --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.jscript; + +import java.util.HashMap; + +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QNameMap; +import org.mozilla.javascript.Scriptable; + +/** + * @author Kevin Roast + */ +public class ScriptableHashMap extends HashMap implements Scriptable +{ + /** + * @see org.mozilla.javascript.Scriptable#getClassName() + */ + public String getClassName() + { + return "ScriptableHashMap"; + } + + /** + * @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable) + */ + public Object get(String name, Scriptable start) + { + // get the property from the underlying map + return get(name); + } + + /** + * @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable) + */ + public Object get(int index, Scriptable start) + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable) + */ + public boolean has(String name, Scriptable start) + { + // locate the property in the underlying map + return containsKey(name); + } + + /** + * @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable) + */ + public boolean has(int index, Scriptable start) + { + return false; + } + + /** + * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + public void put(String name, Scriptable start, Object value) + { + // add the property to the underlying QName map + put(name, value); + } + + /** + * @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + public void put(int index, Scriptable start, Object value) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) + */ + public void delete(String name) + { + // remove the property from the underlying QName map + remove(name); + } + + /** + * @see org.mozilla.javascript.Scriptable#delete(int) + */ + public void delete(int index) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#getPrototype() + */ + public Scriptable getPrototype() + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable) + */ + public void setPrototype(Scriptable prototype) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#getParentScope() + */ + public Scriptable getParentScope() + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable) + */ + public void setParentScope(Scriptable parent) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#getIds() + */ + public Object[] getIds() + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class) + */ + public Object getDefaultValue(Class hint) + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable) + */ + public boolean hasInstance(Scriptable instance) + { + return instance instanceof ScriptableHashMap; + } +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java b/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java new file mode 100644 index 0000000000..2830e667bd --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.repo.jscript; + +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QNameMap; +import org.mozilla.javascript.Scriptable; + +/** + * @author Kevin Roast + */ +public class ScriptableQNameMap extends QNameMap implements Scriptable +{ + /** + * @param resolver + */ + public ScriptableQNameMap(NamespacePrefixResolver resolver) + { + super(resolver); + } + + /** + * @see org.mozilla.javascript.Scriptable#getClassName() + */ + public String getClassName() + { + return "ScriptableQNameMap"; + } + + /** + * @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable) + */ + public Object get(String name, Scriptable start) + { + // get the property from the underlying QName map + return get(name); + } + + /** + * @see org.mozilla.javascript.Scriptable#get(int, org.mozilla.javascript.Scriptable) + */ + public Object get(int index, Scriptable start) + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#has(java.lang.String, org.mozilla.javascript.Scriptable) + */ + public boolean has(String name, Scriptable start) + { + // locate the property in the underlying QName map + return containsKey(name); + } + + /** + * @see org.mozilla.javascript.Scriptable#has(int, org.mozilla.javascript.Scriptable) + */ + public boolean has(int index, Scriptable start) + { + return false; + } + + /** + * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + public void put(String name, Scriptable start, Object value) + { + // add the property to the underlying QName map + put(name, value); + } + + /** + * @see org.mozilla.javascript.Scriptable#put(int, org.mozilla.javascript.Scriptable, java.lang.Object) + */ + public void put(int index, Scriptable start, Object value) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) + */ + public void delete(String name) + { + // remove the property from the underlying QName map + remove(name); + } + + /** + * @see org.mozilla.javascript.Scriptable#delete(int) + */ + public void delete(int index) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#getPrototype() + */ + public Scriptable getPrototype() + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#setPrototype(org.mozilla.javascript.Scriptable) + */ + public void setPrototype(Scriptable prototype) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#getParentScope() + */ + public Scriptable getParentScope() + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#setParentScope(org.mozilla.javascript.Scriptable) + */ + public void setParentScope(Scriptable parent) + { + } + + /** + * @see org.mozilla.javascript.Scriptable#getIds() + */ + public Object[] getIds() + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#getDefaultValue(java.lang.Class) + */ + public Object getDefaultValue(Class hint) + { + return null; + } + + /** + * @see org.mozilla.javascript.Scriptable#hasInstance(org.mozilla.javascript.Scriptable) + */ + public boolean hasInstance(Scriptable instance) + { + return instance instanceof ScriptableQNameMap; + } +}