/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
package org.mulgara.resolver;
// Java 2 standard packages
import java.net.URI;
import java.util.*;
// Third party packages
import org.apache.log4j.Logger;
import org.jrdf.graph.*;
// Local packages
import org.mulgara.query.*;
import org.mulgara.query.rdf.*;
import org.mulgara.resolver.spi.*;
import org.mulgara.store.nodepool.NodePool;
import org.mulgara.util.QueryParams;
/**
* An {@link Operation} that implements the {@link org.mulgara.server.Session#createModel(URI, URI)} method.
*
* @created 2004-11-24
* @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
* @version $Revision: 1.9 $
* @modified $Date: 2005/02/22 08:16:08 $ by $Author: newmana $
* @maintenanceAuthor $Author: newmana $
* @copyright ©2004 <a href="http://www.tucanatech.com/">Tucana Technology, Inc</a>
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
class CreateGraphOperation implements Operation {
/** Logger. This is named after the class. */
private static final Logger logger = Logger.getLogger(CreateGraphOperation.class.getName());
/** The parameter used for describing subgraphs within a URI. */
static final String GRAPH = "graph";
/** The URI of the model to be created. */
private final URI graphURI;
/** The URI of the type of the model to be created. */
private URI graphTypeURI;
/**
* Sole constructor.
*
* @param graphURI the {@link URI} of the graph to be created, never <code>null</code>
* @param graphTypeURI thie {@link URI} of the type of graph to create, or
* <code>null</code> for the same type as the system graph (<code>#</code>)
* @throws IllegalArgumentException if <var>graphURI</var> is <code>null</code>
*/
CreateGraphOperation(URI graphURI, URI graphTypeURI) throws QueryException {
// Validate "graphURI" parameter
if (graphURI == null) throw new IllegalArgumentException("Null \"graphURI\" parameter");
if (!graphURI.isOpaque() && fragmentScheme(graphURI) && graphURI.getFragment() == null) {
throw new QueryException("Graph URI does not have a fragment (graphURI:\"" + graphURI + "\")");
}
// Initialize fields
this.graphURI = graphURI;
this.graphTypeURI = graphTypeURI;
}
//
// Methods implementing Operation
//
public void execute(OperationContext operationContext,
SystemResolver systemResolver,
DatabaseMetadata metadata) throws Exception {
// Default to the system graph type
if (graphTypeURI == null) graphTypeURI = metadata.getSystemModelTypeURI();
verifyGraphUri(graphURI, metadata);
// Look up the resolver factory for the model type
ResolverFactory resolverFactory = operationContext.findModelTypeResolverFactory(graphTypeURI);
if (resolverFactory == null) {
throw new QueryException("Couldn't find resolver factory in internal resolver map " + graphTypeURI);
}
// PREVIOUSLY WITHIN TRANSACTION
// Obtain an appropriate resolver bound to this session
Resolver resolver = operationContext.obtainResolver(resolverFactory);
assert resolver != null;
// Find the local node identifying the model
long graph = systemResolver.localizePersistent(new URIReferenceImpl(graphURI));
assert graph != NodePool.NONE;
// Check model does not already exist with a different model type.
// TODO: there's a node leak here, if the model has already been created.
Resolution resolution = systemResolver.resolve(new ConstraintImpl(
new LocalNode(graph),
new LocalNode(metadata.getRdfTypeNode()),
new Variable("x"),
new LocalNode(metadata.getSystemModelNode())));
boolean success = false;
try {
resolution.beforeFirst();
if (resolution.next()) {
Node eNode = systemResolver.globalize(resolution.getColumnValue(0));
try {
URIReferenceImpl existing = (URIReferenceImpl)eNode;
if (!new URIReferenceImpl(graphTypeURI).equals(existing)) {
throw new QueryException(graphURI + " already exists with model type " + existing +
" in attempt to create it with type " + graphTypeURI);
}
} catch (ClassCastException ec) {
throw new QueryException("Invalid model type entry in system model: " + graphURI + " <rdf:type> " + eNode);
}
}
success = true;
} finally {
try {
resolution.close();
} catch (TuplesException e) {
if (success) throw e; // This is a new exception, need to re-throw it.
else logger.info("Suppressing exception cleaning up from failed read", e); // Log suppressed exception.
}
}
// TODO: there's a node leak here, because the model node was created
// persistently, but may never end up linked into the graph if the
// following security check doesn't succeed
// Make sure security adapters are satisfied
for (Iterator<SecurityAdapter> i = operationContext.getSecurityAdapterList().iterator(); i.hasNext();) {
SecurityAdapter securityAdapter = i.next();
// Tell the truth to the user
if (!securityAdapter.canCreateModel(graph, systemResolver) || !securityAdapter.canSeeModel(graph, systemResolver)) {
throw new QueryException("You aren't allowed to create " + graphURI);
}
}
// Use the session to create the model
resolver.createModel(graph, graphTypeURI);
}
/**
* @return <code>true</code>
*/
public boolean isWriteOperation() {
return true;
}
/**
* Verify that the graph URI is relative to the database URI. The graph
* URI can use one of the hostname aliases instead of the canonical
* hostname of the database URI. No checking of the scheme specific part
* of the graph URI is performed if the database URI is opaque.
* @param graphURI
* @param metadata
* @throws QueryException
*/
private void verifyGraphUri(URI graphURI, DatabaseMetadata metadata) throws QueryException {
// only check if this is a scheme which can use fragments - for the moment only RMI
if (!fragmentSchemes.contains(graphURI)) return;
boolean badModelURI = true;
URI databaseURI = metadata.getURI();
String scheme = graphURI.getScheme();
String fragment = graphURI.getFragment();
if (scheme != null && scheme.equals(databaseURI.getScheme())) {
if (databaseURI.isOpaque()) {
// databaseURI is opaque.
if (graphURI.isOpaque() && fragment != null) {
// Strip out the query string.
String ssp = graphURI.getSchemeSpecificPart();
int qIndex = ssp.indexOf('?');
if (qIndex >= 0) ssp = ssp.substring(0, qIndex);
if (ssp.equals(databaseURI.getSchemeSpecificPart())) {
// graphURI is relative to databaseURI.
badModelURI = false;
}
}
} else {
// databaseURI is hierarchial.
String path;
String host;
if (
!graphURI.isOpaque() && (
graphURI.getSchemeSpecificPart().equals(
databaseURI.getSchemeSpecificPart()
) || (
(host = graphURI.getHost()) != null &&
graphURI.getPort() == databaseURI.getPort() &&
(path = graphURI.getPath()) != null &&
path.equals(databaseURI.getPath()) &&
metadata.getHostnameAliases().contains(host.toLowerCase())
)
)
) {
// graphURI is relative to databaseURI.
// only good if we have a fragment OR we have a graph parameter
if (fragment != null || hasSubgraph(graphURI)) badModelURI = false;
}
}
} else {
badModelURI = !graphURI.isOpaque();
}
if (badModelURI) {
throw new QueryException(
"Graph URI is not relative to the database URI (graphURI:\"" +
graphURI + "\", databaseURI:\"" + databaseURI + "\")"
);
}
}
/** Schemes with fragments are handled for backward compatibility */
private static final Set<String> fragmentSchemes = new HashSet<String>();
static {
fragmentSchemes.add("rmi");
fragmentSchemes.add("beep");
}
/**
* Test if the given URI is in a scheme which differentiates graphs based on fragments
* and there is no sub-graph name encoded in the URI.
* @param u The URI to test for the graph.
* @return <code>true</code> only of the URI is in the known graph schemes.
*/
private static boolean fragmentScheme(URI u) {
return fragmentSchemes.contains(u.getScheme()) && !hasSubgraph(u);
}
/**
* Check if a graph URI contains another graph name.
* @param graphURI The URI to test.
* @return <code>true</code> if the URI contains the name of another graph.
*/
private static boolean hasSubgraph(URI graphURI) {
return QueryParams.decode(graphURI).getNames().contains(GRAPH);
}
}