/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.translator.odata; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import javax.ws.rs.core.Response.Status; import org.odata4j.core.OEntities; import org.odata4j.core.OEntity; import org.odata4j.core.OProperty; import org.odata4j.edm.EdmDataServices; import org.odata4j.edm.EdmEntitySet; import org.odata4j.format.Entry; import org.odata4j.format.FormatWriter; import org.odata4j.format.FormatWriterFactory; import org.teiid.GeneratedKeys; import org.teiid.language.Command; import org.teiid.metadata.RuntimeMetadata; import org.teiid.metadata.Schema; import org.teiid.metadata.Table; import org.teiid.translator.DataNotAvailableException; import org.teiid.translator.ExecutionContext; import org.teiid.translator.TranslatorException; import org.teiid.translator.UpdateExecution; import org.teiid.translator.WSConnection; import org.teiid.translator.ws.BinaryWSProcedureExecution; public class ODataUpdateExecution extends BaseQueryExecution implements UpdateExecution { private ODataUpdateVisitor visitor; private ODataEntitiesResponse response; public ODataUpdateExecution(Command command, ODataExecutionFactory translator, ExecutionContext executionContext, RuntimeMetadata metadata, WSConnection connection) throws TranslatorException { super(translator, executionContext, metadata, connection); this.visitor = new ODataUpdateVisitor(translator, metadata); this.visitor.visitNode(command); if (!this.visitor.exceptions.isEmpty()) { throw this.visitor.exceptions.get(0); } } @Override public void close() { } @Override public void cancel() throws TranslatorException { } @Override public void execute() throws TranslatorException { if (this.visitor.getMethod().equals("DELETE")) { //$NON-NLS-1$ // DELETE BinaryWSProcedureExecution execution = executeDirect(this.visitor.getMethod(), this.visitor.buildURL(), null, getDefaultHeaders()); if (execution.getResponseCode() != Status.OK.getStatusCode() && (execution.getResponseCode() != Status.NO_CONTENT.getStatusCode())) { throw buildError(execution); } } else if(this.visitor.getMethod().equals("PUT")) { //$NON-NLS-1$ // UPDATE Schema schema = visitor.getTable().getParent(); EdmDataServices edm = new TeiidEdmMetadata(schema.getName(), ODataEntitySchemaBuilder.buildMetadata(schema)); BinaryWSProcedureExecution execution = executeDirect("GET", this.visitor.buildURL(), null, getDefaultHeaders()); //$NON-NLS-1$ if (execution.getResponseCode() == Status.OK.getStatusCode()) { String etag = getHeader(execution, "ETag"); //$NON-NLS-1$ String payload = buildPayload(this.visitor.getTable().getName(), this.visitor.getPayload(), edm); this.response = executeWithReturnEntity(this.visitor.getMethod(), this.visitor.buildURL(), payload, this.visitor.getTable().getName(), edm, etag, Status.OK, Status.NO_CONTENT); if (this.response != null) { if (this.response.hasError()) { throw this.response.getError(); } } } } else if (this.visitor.getMethod().equals("POST")) { //$NON-NLS-1$ // INSERT Schema schema = visitor.getTable().getParent(); EdmDataServices edm = new TeiidEdmMetadata(schema.getName(), ODataEntitySchemaBuilder.buildMetadata( schema)); String payload = buildPayload(this.visitor.getTable().getName(), this.visitor.getPayload(), edm); this.response = executeWithReturnEntity(this.visitor.getMethod(), this.visitor.buildURL(), payload, this.visitor.getTable().getName(), edm, null, Status.CREATED); if (this.response != null) { if (this.response.hasError()) { throw this.response.getError(); } } } } private String buildPayload(String entitySet, final List<OProperty<?>> props, EdmDataServices edm) { // this is remove the teiid specific model name from the entity type name. final EdmEntitySet ees = ODataEntitySchemaBuilder.removeModelName(edm.getEdmEntitySet(entitySet)); Entry entry = new Entry() { public String getUri() { return null; } public OEntity getEntity() { return OEntities.createRequest(ees, props, null); } }; StringWriter sw = new StringWriter(); FormatWriter<Entry> fw = FormatWriterFactory.getFormatWriter(Entry.class, null, "ATOM", null); //$NON-NLS-1$ fw.write(null, sw, entry); return sw.toString(); } @Override public int[] getUpdateCounts() throws DataNotAvailableException, TranslatorException { if (this.visitor.getMethod().equals("DELETE")) { //$NON-NLS-1$ //DELETE return (this.response != null)?new int[]{1}:new int[]{0}; } else if(this.visitor.getMethod().equals("PUT")) { //$NON-NLS-1$ // UPDATE; // conflicting implementation found where some sent 200 with content; other with 204 no-content return (this.response != null)?new int[]{1}:new int[]{0}; } else if (this.visitor.getMethod().equals("POST")) { //$NON-NLS-1$ //INSERT if (this.response != null && this.response.hasRow()) { if (this.executionContext.getCommandContext().isReturnAutoGeneratedKeys()) { addAutoGeneretedKeys(); } return new int[]{1}; } } return new int[] {0}; } private void addAutoGeneretedKeys() { OEntity entity = this.response.getResultsIter().next().getEntity(); Table table = this.visitor.getEnityTable(); int cols = table.getPrimaryKey().getColumns().size(); Class<?>[] columnDataTypes = new Class<?>[cols]; String[] columnNames = new String[cols]; //this is typically expected to be an int/long, but we'll be general here. we may eventual need the type logic off of the metadata importer for (int i = 0; i < cols; i++) { columnDataTypes[i] = table.getPrimaryKey().getColumns().get(i).getJavaType(); columnNames[i] = table.getPrimaryKey().getColumns().get(i).getName(); } GeneratedKeys generatedKeys = this.executionContext.getCommandContext().returnGeneratedKeys(columnNames, columnDataTypes); List<Object> vals = new ArrayList<Object>(columnDataTypes.length); for (int i = 0; i < columnDataTypes.length; i++) { OProperty<?> prop = entity.getProperty(columnNames[i]); Object value = this.translator.retrieveValue(prop.getValue(), columnDataTypes[i]); vals.add(value); } generatedKeys.addKey(vals); } }