All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.admin.rest.resources.TemplateRestResource Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2009-2017 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

// Portions Copyright [2016-2021] [Payara Foundation and/or its affiliates]

package org.glassfish.admin.rest.resources;

import com.sun.enterprise.config.modularity.ConfigModularityUtils;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.io.FileUtils;
import org.glassfish.admin.rest.provider.MethodMetaData;
import org.glassfish.admin.rest.results.ActionReportResult;
import org.glassfish.admin.rest.results.OptionsResult;
import org.glassfish.admin.rest.utils.ResourceUtil;
import org.glassfish.admin.rest.utils.Util;
import org.glassfish.admin.rest.utils.xml.RestActionReporter;
import org.glassfish.api.ActionReport;
import org.glassfish.api.admin.RestRedirect;
import org.glassfish.config.support.Delete;
import org.glassfish.hk2.api.MultiException;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.jvnet.hk2.config.ConfigBean;
import org.jvnet.hk2.config.ConfigBeanProxy;
import org.jvnet.hk2.config.ConfigModel;
import org.jvnet.hk2.config.ConfigSupport;
import org.jvnet.hk2.config.Dom;
import org.jvnet.hk2.config.TransactionFailure;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.OPTIONS;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import jakarta.ws.rs.core.Response.Status;
import org.glassfish.admin.rest.Constants;
import org.glassfish.admin.rest.OptionsCapable;
import org.glassfish.admin.rest.RestLogging;
import org.glassfish.admin.rest.composite.metadata.RestResourceMetadata;
import org.glassfish.api.ActionReport.ExitCode;

import static org.glassfish.admin.rest.utils.Util.eleminateHypen;
import java.net.URLDecoder;

/**
 * @author Ludovic Champenois [email protected]
 * @author Rajeshwar Patil
 */
@Produces({MediaType.TEXT_HTML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.APPLICATION_FORM_URLENCODED})
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.APPLICATION_FORM_URLENCODED})
public class TemplateRestResource extends AbstractResource implements OptionsCapable {
    protected Dom entity;  //may be null when not created yet...
    protected Dom parent;
    protected String tagName;
    protected ConfigModel childModel; //good model even if the child entity is null
    protected String childID; // id of the current child if part of a list, might be null
    public static final LocalStringManagerImpl localStrings = new LocalStringManagerImpl(TemplateRestResource.class);
    private static final List attributesToSkip = new ArrayList() {

        {
            add("parent");
            add("name");
            add("children");
            add("submit");
        }
    };

    /**
     * Creates a new instance of xxxResource
     */
    public TemplateRestResource() {
    }

    @GET
    public ActionReportResult getEntityLegacyFormat(@QueryParam("expandLevel") @DefaultValue("1") int expandLevel) {
        if (childModel == null) {//wrong entity name at this point
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }

        return buildActionReportResult(true);
    }

    @GET
    @Produces(Constants.MEDIA_TYPE_JSON)
    public Map getEntity(@QueryParam("expandLevel") @DefaultValue("1") int expandLevel) {
        if (childModel == null) {//wrong entity name at this point
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }

        return getAttributes((ConfigBean) getEntity());
    }

    @POST
    //create or update
    public Response createOrUpdateEntityLegacyFormat(HashMap data) {
        return Response.ok(ResourceUtil.getActionReportResult(doCreateOrUpdate(data),
                localStrings.getLocalString("rest.resource.update.message",
                "\"{0}\" updated successfully.", uriInfo.getAbsolutePath()),
                requestHeaders, uriInfo)).build();
    }

    @POST
    @Produces(Constants.MEDIA_TYPE_JSON)
    public Response createOrUpdateEntity(HashMap data) {
        doCreateOrUpdate(data);
        return Response.status(Status.CREATED).build();
    }

    /**
     * allows for remote files to be put in a tmp area and we pass the
     * local location of this file to the corresponding command instead of the content of the file
     * * Yu need to add  enctype="multipart/form-data" in the form
     * for ex:
     * {@code 
} * then any param of type="file" will be uploaded, stored locally and the param will use the local location * on the server side (ie. just the path) */ @POST @Consumes(MediaType.MULTIPART_FORM_DATA) public Object postLegacyFormat(FormDataMultiPart formData) { return createOrUpdateEntityLegacyFormat(createDataBasedOnForm(formData)); //execute the deploy command with a copy of the file locally } @POST @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(Constants.MEDIA_TYPE_JSON) public Object post(FormDataMultiPart formData) { return createOrUpdateEntity(createDataBasedOnForm(formData)); //execute the deploy command with a copy of the file locally } @DELETE public Response delete(Map data) { return Response.ok(ResourceUtil.getActionReportResult(doDelete(data), localStrings.getLocalString("rest.resource.delete.message", "\"{0}\" deleted successfully.", uriInfo.getAbsolutePath()), requestHeaders, uriInfo)) .build(); //200 - ok } @OPTIONS @Produces(MediaType.APPLICATION_JSON) public ActionReportResult optionsLegacyFormat() { return buildActionReportResult(false); } @OPTIONS @Produces(Constants.MEDIA_TYPE_JSON) public RestResourceMetadata options() { return new RestResourceMetadata(this); } /** * This method performs the creation or updating of an entity, regardless of the * request's mime type. If an error occurs, a WebApplicationException * is thrown, so if the method returns, the create/update was successful. * @param data * @return */ protected RestActionReporter doCreateOrUpdate(Map data) { if (data == null) { data = new HashMap(); } try { //data.remove("submit"); removeAttributesToBeSkipped(data); if (data.containsKey("error")) { throw new WebApplicationException(Response.status(400) .entity(ResourceUtil.getActionReportResult(ActionReport.ExitCode.FAILURE, localStrings.getLocalString("rest.request.parsing.error", "Unable to parse the input entity. Please check the syntax."), requestHeaders, uriInfo)).build()); } ResourceUtil.purgeEmptyEntries(data); //hack-1 : support delete method for html //Currently, browsers do not support delete method. For html media, //delete operations can be supported through POST. Redirect html //client POST request for delete operation to DELETE method. if ("__deleteoperation".equals(data.get("operation"))) { data.remove("operation"); delete(data); return new RestActionReporter(); } //just update it. data = ResourceUtil.translateCamelCasedNamesToXMLNames(data); RestActionReporter ar = Util.applyChanges(data, uriInfo, getSubject()); if (ar.getActionExitCode() != ActionReport.ExitCode.SUCCESS) { throwError(Status.BAD_REQUEST, "Could not apply changes: " + ar.getMessage()); // i18n } return ar; } catch(WebApplicationException wae) { throw wae; } catch (Exception ex) { throw new WebApplicationException(ex, Response.Status.INTERNAL_SERVER_ERROR); } } protected ExitCode doDelete(Map data) { if (data == null) { data = new HashMap(); } if (entity == null) {//wrong resource // return Response.status(404).entity(ResourceUtil.getActionReportResult(ActionReport.ExitCode.FAILURE, errorMessage, requestHeaders, uriInfo)).build(); throwError(Status.NOT_FOUND, localStrings.getLocalString("rest.resource.erromessage.noentity", "Resource not found.")); } if (getDeleteCommand() == null) { String message = localStrings.getLocalString("rest.resource.delete.forbidden", "DELETE on \"{0}\" is forbidden.", uriInfo.getAbsolutePath()); throwError(Status.FORBIDDEN, message); } if (getDeleteCommand().equals("GENERIC-DELETE")) { try { ConfigBean p = (ConfigBean) parent; if (parent == null) { p = (ConfigBean) entity.parent(); } ConfigSupport.deleteChild(p, (ConfigBean) entity); return ExitCode.SUCCESS; } catch (TransactionFailure ex) { throw new WebApplicationException(ex, Response.Status.INTERNAL_SERVER_ERROR); } } //do the delete via the command: if (data.containsKey("error")) { throwError(Status.BAD_REQUEST, localStrings.getLocalString("rest.request.parsing.error", "Unable to parse the input entity. Please check the syntax.")); } ResourceUtil.addQueryString(uriInfo.getQueryParameters(), data); ResourceUtil.purgeEmptyEntries(data); ResourceUtil.adjustParameters(data); if (data.get("DEFAULT") == null) { addDefaultParameter(data); } else { String resourceName = getResourceName(uriInfo.getAbsolutePath().getPath(), "/"); if (!data.get("DEFAULT").equals(resourceName)) { throwError(Status.FORBIDDEN, localStrings.getLocalString("rest.resource.not.deleted", "Resource not deleted. Value of \"name\" should be the name of this resource.")); } } RestActionReporter actionReport = runCommand(getDeleteCommand(), data); if (actionReport != null) { ActionReport.ExitCode exitCode = actionReport.getActionExitCode(); if (exitCode != ActionReport.ExitCode.FAILURE) { return exitCode; } throwError(Status.BAD_REQUEST, actionReport.getMessage()); } throw new WebApplicationException(handleError(Status.BAD_REQUEST, localStrings.getLocalString("rest.resource.delete.forbidden", "DELETE on \"{0}\" is forbidden.", uriInfo.getAbsolutePath()))); } @Override public UriInfo getUriInfo() { return this.uriInfo; } @Override public void setUriInfo(UriInfo uriInfo) { this.uriInfo = uriInfo; } public void setEntity(Dom p) { entity = p; childModel = p.model; } public Dom getEntity() { return entity; } public void setParentAndTagName(Dom parent, String tagName) { if (parent == null) { //prevent https://glassfish.dev.java.net/issues/show_bug.cgi?id=14125 throw new WebApplicationException(Response.Status.NOT_FOUND); } this.parent = parent; this.tagName = tagName; synchronized (parent) { entity = parent.nodeElement(tagName); } if (entity == null) { // In some cases, the tagName requested is not found in the DOM tree. This is true, // for example, for the various ZeroConf elements (e.g., transaction-service). If // the zero conf element is not in domain.xml, then it won't be in the Dom tree // returned by HK2. If that's the case, we can use ConfigModularityUtils.getOwningObject() // to find the ConfigBean matching the path requested, which will add the node to // the Dom tree. Once that's done, we can return that node and proceed as normal String location = buildPath(parent) + "/" + tagName; if (location.startsWith("domain/configs")) { final ConfigModularityUtils cmu = locatorBridge.getRemoteLocator().getService(ConfigModularityUtils.class); ConfigBeanProxy cbp = cmu.getOwningObject(location); if (cbp == null) { cbp = cmu.getConfigBeanInstanceFor(cmu.getOwningClassForLocation(location)); } if (cbp != null) { entity = Dom.unwrap(cbp); childModel = entity.model; } } //throw new WebApplicationException(new Exception("Trying to create an entity using generic create"),Response.Status.INTERNAL_SERVER_ERROR); } else { childModel = entity.model; } } /** * This method will build the path string as needed by ConfigModularityUtils.getOwningObject(). * There is a mismatch between what the method expects and the way the REST URIs are constructed. * For example, for the transaction-service element, the REST URI, stripped of the HTTP and * server context information, looks like this: * /domain/configs/config/server-config/transaction-service. The format expected by the * getOwningObject(), however, looks like this: * domain/configs/server-config/transaction-service. In the REST URIs, if there is a collection of * Named items, the type of the collection is inserted into the URI ("config" here) followed by * the name of the particular instance ("server-config"). In building the path, we must identify * Named instances and insert the name of the instance rather than the type. We apply this logic * as we recurse up to the top of the Dom tree to finish building the path desired. * @param node * @return */ private String buildPath (Dom node) { final Dom parentNode = node.parent(); String part = node.model.getTagName(); String name = node.attribute("name"); if (name != null) { part = name; } return (parentNode != null) ? (buildPath(parentNode) + "/" + part) : part; } /** * allows for remote files to be put in a tmp area and we pass the * local location of this file to the corresponding command instead of the content of the file * * Yu need to add enctype="multipart/form-data" in the form * for ex: * then any param of type="file" will be uploaded, stored locally and the param will use the local location * on the server side (ie. just the path) */ public static HashMap createDataBasedOnForm(FormDataMultiPart formData) { HashMap data = new HashMap(); try { //data passed to the generic command running Map> m1 = formData.getFields(); Set ss = m1.keySet(); for (String fieldName : ss) { for (FormDataBodyPart bodyPart : formData.getFields(fieldName)) { if (bodyPart.getContentDisposition().getFileName() != null) {//we have a file //save it and mark it as delete on exit. InputStream fileStream = bodyPart.getValueAs(InputStream.class); String mimeType = bodyPart.getMediaType().toString(); //Use just the filename without complete path. File creation //in case of remote deployment failing because fo this. String fileName = bodyPart.getContentDisposition().getFileName(); if (fileName.contains("/")) { fileName = Util.getName(fileName, '/'); } else { if (fileName.contains("\\")) { fileName = Util.getName(fileName, '\\'); } } File f = Util.saveFile(fileName, mimeType, fileStream); FileUtils.deleteOnExit(f); //put only the local path of the file in the same field. data.put(fieldName, f.getAbsolutePath()); } else { data.put(fieldName, bodyPart.getValue()); } } } } catch (Exception ex) { RestLogging.restLogger.log(Level.SEVERE, null, ex); } finally { formData.cleanup(); } return data; } /* * This method is called by the ASM generated code change very carefully */ public void setBeanByKey(List parentList, String id, String tag) { this.tagName = tag; try { childID = URLDecoder.decode(id, "UTF-8"); } catch (UnsupportedEncodingException ex) { childID = id; } if (parentList != null) { // Believe it or not, this can happen for (Dom c : parentList) { String keyAttributeName = null; ConfigModel model = c.model; if (model.key == null) { try { for (String s : model.getAttributeNames()) {//no key, by default use the name attr if (s.equals("name")) { keyAttributeName = s; } } if (keyAttributeName == null) {//nothing, so pick the first one keyAttributeName = model.getAttributeNames().iterator().next(); } } catch (Exception e) { keyAttributeName = "ThisIsAModelBug:NoKeyAttr"; //no attr choice fo a key!!! Error!!! } //firstone } else { keyAttributeName = model.key.substring(1, model.key.length()); } String keyvalue = c.attribute(keyAttributeName.toLowerCase(Locale.US)); if (keyvalue != null && keyvalue.equals(childID)) { setEntity((ConfigBean) c); } } } } protected ActionReportResult buildActionReportResult(boolean showEntityValues) { RestActionReporter ar = new RestActionReporter(); ar.setExtraProperties(new Properties()); ConfigBean entity = (ConfigBean) getEntity(); if (childID != null) { ar.setActionDescription(childID); } else if (childModel != null) { ar.setActionDescription(childModel.getTagName()); } if (showEntityValues && entity != null) { ar.getExtraProperties().put("entity", getAttributes(entity)); } OptionsResult optionsResult = new OptionsResult(Util.getResourceName(uriInfo)); Map mmd = getMethodMetaData(); optionsResult.putMethodMetaData("GET", mmd.get("GET")); optionsResult.putMethodMetaData("POST", mmd.get("POST")); optionsResult.putMethodMetaData("DELETE", mmd.get("DELETE")); ResourceUtil.addMethodMetaData(ar, mmd); if (entity != null) { ar.getExtraProperties().put("childResources", ResourceUtil.getResourceLinks(entity, uriInfo, ResourceUtil.canShowDeprecatedItems(locatorBridge.getRemoteLocator()))); } ar.getExtraProperties().put("commands", ResourceUtil.getCommandLinks(getCommandResourcesPaths())); return new ActionReportResult(ar, entity, optionsResult); } protected void removeAttributesToBeSkipped(Map data) { for (String item : attributesToSkip) { data.remove(item); } } protected String[][] getCommandResourcesPaths() { return new String[][]{}; } protected String getDeleteCommand() { if (entity == null) { return null; } String result = ResourceUtil.getCommand(RestRedirect.OpType.DELETE, getEntity().model); if ((result == null) && (entity.parent() != null)) { //trying @Delete annotation that as a generic CRUD delete command, possibly... Class cbp = null; try { cbp = (Class) entity.parent().model.classLoaderHolder.loadClass(entity.parent().model.targetTypeName); } catch (MultiException e) { return null;// } Delete del = null; for (Method m : cbp.getMethods()) { ConfigModel.Property pp = entity.parent().model.toProperty(m); if ((pp != null) && (pp.xmlName.equals(tagName)) && m.isAnnotationPresent(Delete.class)) { del = m.getAnnotation(Delete.class); break; } } if (del != null) { return del.value(); } } return result; } /** * Returns the list of command resource paths [command, http method, url/path] * * @return */ private RestActionReporter runCommand(String commandName, Map data) { if (commandName != null) { return ResourceUtil.runCommand(commandName, data, getSubject()); } return null;//not processed } // This has to be smarter, since we are encoding / in resource names now private void addDefaultParameter(Map data) { String defaultParameterValue = getEntity().getKey(); if (defaultParameterValue == null) {// no primary key //we take the parent key. // see for example delete-ssl that that the parent key name as ssl does not have a key defaultParameterValue = parent.getKey(); } data.put("DEFAULT", defaultParameterValue); } private String getResourceName(String absoluteName, String delimiter) { if (null == absoluteName) { return absoluteName; } int index = absoluteName.lastIndexOf(delimiter); if (index != -1) { index = index + delimiter.length(); return absoluteName.substring(index); } else { return absoluteName; } } //****************************************************************************************************************** private Map getAttributes(Dom entity) { Map result = new TreeMap(); Set attributeNames = entity.model.getAttributeNames(); for (String attributeName : attributeNames) { result.put(eleminateHypen(attributeName), entity.attribute(attributeName)); } return result; } private Map getMethodMetaData() { Map map = new TreeMap(); //GET meta data map.put("GET", new MethodMetaData()); /////optionsResult.putMethodMetaData("POST", new MethodMetaData()); MethodMetaData postMethodMetaData = ResourceUtil.getMethodMetaData(childModel); map.put("POST", postMethodMetaData); //DELETE meta data String command = getDeleteCommand(); if (command != null) { MethodMetaData deleteMethodMetaData; if (command.equals("GENERIC-DELETE")) { deleteMethodMetaData = new MethodMetaData(); } else { deleteMethodMetaData = ResourceUtil.getMethodMetaData( command, locatorBridge.getRemoteLocator()); //In case of delete operation(command), do not display/provide id attribute. deleteMethodMetaData.removeParamMetaData("id"); } map.put("DELETE", deleteMethodMetaData); } return map; } protected void throwError(final Status error, final String message) throws WebApplicationException { throw new WebApplicationException(handleError(error, message)); } protected Response handleError(final Status error, final String message) throws WebApplicationException { //TODO better error handling. return Response.status(error).entity(ResourceUtil.getActionReportResult(ActionReport.ExitCode.FAILURE, message, requestHeaders, uriInfo)).build(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy