// Copyright 2006 Google Inc. // // 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 com.google.enterprise.connector.manager; import com.google.enterprise.connector.common.AlternateContentFilterInputStream; import com.google.enterprise.connector.common.BigEmptyDocumentFilterInputStream; import com.google.enterprise.connector.common.I18NUtil; import com.google.enterprise.connector.common.PropertiesUtils; import com.google.enterprise.connector.instantiator.Configuration; import com.google.enterprise.connector.instantiator.DocumentFilterFactoryFactory; import com.google.enterprise.connector.instantiator.ExtendedConfigureResponse; import com.google.enterprise.connector.instantiator.Instantiator; import com.google.enterprise.connector.instantiator.InstantiatorException; import com.google.enterprise.connector.persist.ConnectorNotFoundException; import com.google.enterprise.connector.persist.ConnectorTypeNotFoundException; import com.google.enterprise.connector.persist.PersistentStoreException; import com.google.enterprise.connector.pusher.AclTransformFilter; import com.google.enterprise.connector.pusher.DocUtils; import com.google.enterprise.connector.pusher.FeedConnection; import com.google.enterprise.connector.pusher.InheritFromExtractedAclDocumentFilter; import com.google.enterprise.connector.pusher.UrlConstructor; import com.google.enterprise.connector.scheduler.Schedule; import com.google.enterprise.connector.spi.AuthenticationIdentity; import com.google.enterprise.connector.spi.AuthenticationManager; import com.google.enterprise.connector.spi.AuthenticationResponse; import com.google.enterprise.connector.spi.AuthorizationManager; import com.google.enterprise.connector.spi.AuthorizationResponse; import com.google.enterprise.connector.spi.ConfigureResponse; import com.google.enterprise.connector.spi.ConnectorType; import com.google.enterprise.connector.spi.Document; import com.google.enterprise.connector.spi.RepositoryException; import com.google.enterprise.connector.spi.RepositoryLoginException; import com.google.enterprise.connector.spi.Retriever; import com.google.enterprise.connector.spi.SpiConstants.FeedType; import com.google.enterprise.connector.util.EofFilterInputStream; import com.google.enterprise.connector.util.filter.DocumentFilterFactory; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * */ public class ProductionManager implements Manager { private static final Logger LOGGER = Logger.getLogger(ProductionManager.class.getName()); Instantiator instantiator; private DocumentFilterFactoryFactory documentFilterFactoryFactory = null; public ProductionManager() { } /** * @param instantiator the instantiator to set */ public void setInstantiator(Instantiator instantiator) { this.instantiator = instantiator; } /** * Specify the document filter factory that should be applied to documents. * * @param documentFilterFactoryFactory document filter factory to use */ public void setDocumentFilterFactoryFactory( DocumentFilterFactoryFactory documentFilterFactoryFactory) { this.documentFilterFactoryFactory = documentFilterFactoryFactory; } /** * This was used previously to determine whether feeds supported * inherited ACLs. We now assume they do. */ public void setFeedConnection(FeedConnection feedConnection) { LOGGER.warning("Deprecated feedConnection property, set to " + feedConnection + ", will be ignored"); } @Override public AuthenticationResponse authenticate(String connectorName, AuthenticationIdentity identity) { try { AuthenticationManager authnManager = instantiator.getAuthenticationManager(connectorName); // Some connectors don't implement the AuthenticationManager interface so // we need to check. if (authnManager != null) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("AUTHENTICATE: " + identity); } AuthenticationResponse response = authnManager.authenticate(identity); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("AUTHENTICATION " + (response.isValid() ? "SUCCEEDED" : "FAILED") + ": " + identity + ": " + response); } return response; } } catch (ConnectorNotFoundException e) { LOGGER.log(Level.WARNING, "Connector " + connectorName + " not found", e); } catch (RepositoryLoginException e) { LOGGER.log(Level.WARNING, "Authentication failed for connector " + connectorName + ": " + identity , e); } catch (RepositoryException e) { LOGGER.log(Level.WARNING, "Authentication failed for connector " + connectorName + ": " + identity, e); } catch (Exception e) { LOGGER.log(Level.WARNING, "Authentication failed for connector " + connectorName + ": " + identity, e); } return new AuthenticationResponse(false, null); } @Override public Collection<AuthorizationResponse> authorizeDocids(String connectorName, List<String> docidList, AuthenticationIdentity identity) { try { AuthorizationManager authzManager = instantiator.getAuthorizationManager(connectorName); if (authzManager == null) { // This is a bad situation. This means the Connector has feed the // content in such a way that it is being asked to authorize access to // that content and yet it doesn't implement the AuthorizationManager // interface. Log the situation and return the empty result. LOGGER.warning("Connector " + connectorName + " is being asked to authorize documents but has not implemented" + " the AuthorizationManager interface."); return null; } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("AUTHORIZE: " + identity + ": docids = " + docidList); } Collection<AuthorizationResponse> results = authzManager.authorizeDocids(docidList, identity); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("AUTHORIZATION: " + identity + ": authorized for " + results.size() + " of " + docidList.size() + " documents."); } if (LOGGER.isLoggable(Level.FINEST)) { for (AuthorizationResponse response : results) { LOGGER.finest("AUTHORIZATION: " + response.getDocid() + ": " + response.getStatus()); } } return results; } catch (ConnectorNotFoundException e) { LOGGER.log(Level.WARNING, "Connector " + connectorName + " not found", e); } catch (RepositoryException e) { LOGGER.log(Level.WARNING, "Authorization failed for connector " + connectorName + ": " + identity, e); } catch (Exception e) { LOGGER.log(Level.WARNING, "Authorization failed for connector " + connectorName + ": " + identity, e); } return null; } @Override public InputStream getDocumentContent(String connectorName, String docid) throws ConnectorNotFoundException, InstantiatorException, RepositoryException { if (LOGGER.isLoggable(Level.FINER)) { LOGGER.finer("RETRIEVER: Retrieving content from connector " + connectorName + " for document " + docid); } Retriever retriever = instantiator.getRetriever(connectorName); if (retriever == null) { // We are borked here. This should not happen. LOGGER.warning("GetDocumentContent request for connector " + connectorName + " that does not support the Retriever interface."); return null; } InputStream in = retriever.getContent(docid); if (in == null) { LOGGER.finer("RETRIEVER: Document has no content."); } // The GSA can't handle meta-and-url feeds with no content, so we // provide some minimal content of a single space, if none is available. // We are only detecting empty content here, not large documents. // TODO: Figure out how to handle CONTENT morphing document filters. return new AlternateContentFilterInputStream( new BigEmptyDocumentFilterInputStream( (in == null) ? in : new EofFilterInputStream(in), Long.MAX_VALUE), null); } @Override public Document getDocumentMetaData(String connectorName, String docid) throws ConnectorNotFoundException, InstantiatorException, RepositoryException { if (LOGGER.isLoggable(Level.FINER)) { LOGGER.finer("RETRIEVER: Retrieving metadata from connector " + connectorName + " for document " + docid); } Retriever retriever = instantiator.getRetriever(connectorName); if (retriever == null) { // We are borked here. This should not happen. LOGGER.warning("GetDocumentMetaData request for connector " + connectorName + " that does not support the Retriever interface."); return null; } Document metaDoc = retriever.getMetaData(docid); if (metaDoc == null) { LOGGER.finer("RETRIEVER: Document has no metadata."); // TODO: Create empty Document? } else { if (documentFilterFactoryFactory != null) { DocumentFilterFactory documentFilterFactory = documentFilterFactoryFactory.getDocumentFilterFactory(connectorName); metaDoc = documentFilterFactory.newDocumentFilter(metaDoc); } // GSA 7.0 does not support case-sensitivity or namespaces in ACLs // during crawl-time. So we have to send the ACLs at feed-time. // But the crawl-time metadata overwrites the feed-time ACLs. // The proposed escape is to send a named resource ACL in the feed for // each document, and at crawl-time return an empty ACL that inherits // from the corresponding named resource ACL. if (DocUtils.hasAclProperties(metaDoc)) { metaDoc = new InheritFromExtractedAclDocumentFilter() .newDocumentFilter(metaDoc); } // Configure the dynamic ACL transformation filters for the documents. // TODO(bmj): Is FeedType.CONTENTURL a reasonable assumption here? AclTransformFilter aclTransformFilter = new AclTransformFilter( new UrlConstructor(connectorName, FeedType.CONTENTURL)); metaDoc = aclTransformFilter.newDocumentFilter(metaDoc); } return metaDoc; } @Override public ConfigureResponse getConfigForm(String connectorTypeName, String language) throws ConnectorTypeNotFoundException, InstantiatorException { ConnectorType connectorType = instantiator.getConnectorType(connectorTypeName); Locale locale = I18NUtil.getLocaleFromStandardLocaleString(language); if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.config("GET CONFIG FORM: Fetching configuration form for connector" + " type " + connectorTypeName + ", locale = " + locale); } ConfigureResponse response; try { response = connectorType.getConfigForm(locale); } catch (Exception e) { throw new InstantiatorException("Failed to get configuration form.", e); } // Include the connectorInstance.xml in the response. if (response != null) { response = new ExtendedConfigureResponse(response, instantiator.getConnectorInstancePrototype(connectorTypeName)); } return response; } @Override public ConfigureResponse getConfigFormForConnector(String connectorName, String language) throws ConnectorNotFoundException, InstantiatorException { String connectorTypeName = instantiator.getConnectorTypeName(connectorName); Locale locale = I18NUtil.getLocaleFromStandardLocaleString(language); if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.config("GET CONFIG FORM: Fetching configuration form for " + "connector " + connectorName + ", locale = " + locale); } ConfigureResponse response = instantiator.getConfigFormForConnector( connectorName, connectorTypeName, locale); return response; } @Override public ConnectorStatus getConnectorStatus(String connectorName) throws ConnectorNotFoundException { String connectorTypeName = instantiator.getConnectorTypeName(connectorName); Schedule schedule = instantiator.getConnectorSchedule(connectorName); Configuration config = getConnectorConfiguration(connectorName); String globalNamespace = null; String localNamespace = null; if (config != null) { Map<String, String> configData = config.getMap(); globalNamespace = configData.get(PropertiesUtils.GOOGLE_GLOBAL_NAMESPACE); localNamespace = configData.get(PropertiesUtils.GOOGLE_LOCAL_NAMESPACE); } // Note: We do not return a null or empty Schedule, as most GSAs cannot // handle it. // TODO: resolve the third parameter - we need to give status a meaning return new ConnectorStatus(connectorName, connectorTypeName, 0, Schedule.toString(schedule), globalNamespace, localNamespace); } @Override public List<ConnectorStatus> getConnectorStatuses() { List<ConnectorStatus> result = new ArrayList<ConnectorStatus>(); for (String connectorName : instantiator.getConnectorNames()) { try { result.add(getConnectorStatus(connectorName)); } catch (ConnectorNotFoundException e) { // This is unlikely to happen, but skip this one anyway. LOGGER.finest("Connector not found: " + connectorName); } } return result; } @Override public Set<String> getConnectorTypeNames() { return instantiator.getConnectorTypeNames(); } @Override public ConnectorType getConnectorType(String typeName) throws ConnectorTypeNotFoundException { return instantiator.getConnectorType(typeName); } @Override public ConfigureResponse setConnectorConfiguration(String connectorName, Configuration configuration, String language, boolean update) throws ConnectorNotFoundException, PersistentStoreException, InstantiatorException { return instantiator.setConnectorConfiguration(connectorName, configuration, I18NUtil.getLocaleFromStandardLocaleString(language), update); } @Override public Properties getConnectorManagerConfig() { return Context.getInstance().getConnectorManagerConfig(); } @Override public void setConnectorManagerConfig(String feederGateProtocol, String feederGateHost, int feederGatePort, int feederGateSecurePort, String connectorManagerUrl) throws PersistentStoreException { try { Context.getInstance().setConnectorManagerConfig(feederGateProtocol, feederGateHost, feederGatePort, feederGateSecurePort, connectorManagerUrl); } catch (InstantiatorException e) { throw new PersistentStoreException(e); } } @Override public void setSchedule(String connectorName, String schedule) throws ConnectorNotFoundException, PersistentStoreException { instantiator.setConnectorSchedule(connectorName, Schedule.of(schedule)); } @Override public void removeConnector(String connectorName) throws InstantiatorException { instantiator.removeConnector(connectorName); } @Override public void restartConnectorTraversal(String connectorName) throws ConnectorNotFoundException, InstantiatorException { instantiator.restartConnectorTraversal(connectorName); } @Override public Configuration getConnectorConfiguration(String connectorName) throws ConnectorNotFoundException { return instantiator.getConnectorConfiguration(connectorName); } @Override public boolean isLocked() { return Context.getInstance().getIsManagerLocked(); } }